@zibby/skills 2.0.3 → 2.0.5

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.
@@ -1,22 +1,22 @@
1
- import{createRequire as K}from"module";import{resolveIntegrationToken as L,clearTokenCache as E}from"@zibby/core/backend-client.js";var C=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",LARK_DOCS:"lark_docs",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",VIKUNJA:"vikunja",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),Q=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark Chat",connectPath:"/integrations?provider=lark"},lark_docs:{id:"lark_docs",name:"Lark App",connectPath:"/integrations?provider=lark_docs"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},google:{id:"google",name:"Google Docs",connectPath:"/integrations?provider=google"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"},vikunja:{id:"vikunja",name:"Vikunja",connectPath:"/integrations?provider=vikunja"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"},hubspot:{id:"hubspot",name:"HubSpot",connectPath:"/integrations?provider=hubspot"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});var x=K(import.meta.url);function D(){if(process.env.MCP_JIRA_PATH)return process.env.MCP_JIRA_PATH;try{return x.resolve("@zibby/mcp-jira/index.js")}catch{return null}}var B=new Set(["paragraph","heading","bulletList","orderedList","listItem","blockquote","codeBlock","rule","table","tableRow","tableCell","tableHeader","mediaSingle","panel"]);function U(s,i){if(!i||!i.length)return s;let e=s;for(let t of i)t.type==="strong"?e=`**${e}**`:t.type==="em"?e=`_${e}_`:t.type==="code"?e=`\`${e}\``:t.type==="strike"?e=`~~${e}~~`:t.type==="link"&&t.attrs?.href&&(e=`[${e}](${t.attrs.href})`);return e}function w(s,i=0){if(!Array.isArray(s))return"";let e=[];for(let t of s){if(t.type==="text"){e.push(U(t.text||"",t.marks));continue}if(t.type==="hardBreak"){e.push(`
1
+ import{createRequire as K}from"module";import{resolveIntegrationToken as E,clearTokenCache as L}from"@zibby/core/backend-client.js";var $=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",LARK_DOCS:"lark_docs",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",VIKUNJA:"vikunja",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),Z=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark Chat",connectPath:"/integrations?provider=lark"},lark_docs:{id:"lark_docs",name:"Lark App",connectPath:"/integrations?provider=lark_docs"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},google:{id:"google",name:"Google Docs",connectPath:"/integrations?provider=google"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"},vikunja:{id:"vikunja",name:"Vikunja",connectPath:"/integrations?provider=vikunja"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"},hubspot:{id:"hubspot",name:"HubSpot",connectPath:"/integrations?provider=hubspot"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});var x=K(import.meta.url);function D(){if(process.env.MCP_JIRA_PATH)return process.env.MCP_JIRA_PATH;try{return x.resolve("@zibby/mcp-jira/index.js")}catch{return null}}var B=new Set(["paragraph","heading","bulletList","orderedList","listItem","blockquote","codeBlock","rule","table","tableRow","tableCell","tableHeader","mediaSingle","panel"]);function U(s,r){if(!r||!r.length)return s;let e=s;for(let t of r)t.type==="strong"?e=`**${e}**`:t.type==="em"?e=`_${e}_`:t.type==="code"?e=`\`${e}\``:t.type==="strike"?e=`~~${e}~~`:t.type==="link"&&t.attrs?.href&&(e=`[${e}](${t.attrs.href})`);return e}function w(s,r=0){if(!Array.isArray(s))return"";let e=[];for(let t of s){if(t.type==="text"){e.push(U(t.text||"",t.marks));continue}if(t.type==="hardBreak"){e.push(`
2
2
  `);continue}if(t.type==="rule"){e.push(`
3
3
  ---
4
- `);continue}let a=t.content?w(t.content,i+1):"";if(t.type==="listItem")e.push(a);else if(t.type==="bulletList"){let n=(t.content||[]).map(o=>`- ${w(o.content||[],i+1).trim()}`);e.push(`
5
- ${n.join(`
4
+ `);continue}let a=t.content?w(t.content,r+1):"";if(t.type==="listItem")e.push(a);else if(t.type==="bulletList"){let i=(t.content||[]).map(c=>`- ${w(c.content||[],r+1).trim()}`);e.push(`
5
+ ${i.join(`
6
6
  `)}
7
- `)}else if(t.type==="orderedList"){let n=(t.content||[]).map((o,r)=>`${r+1}. ${w(o.content||[],i+1).trim()}`);e.push(`
8
- ${n.join(`
7
+ `)}else if(t.type==="orderedList"){let i=(t.content||[]).map((c,n)=>`${n+1}. ${w(c.content||[],r+1).trim()}`);e.push(`
8
+ ${i.join(`
9
9
  `)}
10
- `)}else if(t.type==="heading"){let n=t.attrs?.level||2;e.push(`
10
+ `)}else if(t.type==="heading"){let i=t.attrs?.level||2;e.push(`
11
11
 
12
- ${"#".repeat(n)} ${a.trim()}
12
+ ${"#".repeat(i)} ${a.trim()}
13
13
 
14
14
  `)}else B.has(t.type)?e.push(`
15
15
 
16
16
  ${a}
17
17
  `):e.push(a)}return e.join("").replace(/\n{3,}/g,`
18
18
 
19
- `)}function v(s){return String(s||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function A(s){return v(s).replace(/[a-z0-9]+/g,"")}function I(s,i){let e=v(s),t=v(i);if(!e||!t)return 0;if(e===t)return 1;if(e.length===1||t.length===1)return e===t?1:0;let a=u=>{let d=new Map;for(let m=0;m<u.length-1;m++){let g=u.slice(m,m+2);d.set(g,(d.get(g)||0)+1)}return d},n=a(e),o=a(t),r=0,c=0,p=0;for(let u of n.values())c+=u;for(let u of o.values())p+=u;for(let[u,d]of n.entries()){let m=o.get(u)||0;r+=Math.min(d,m)}return 2*r/Math.max(1,c+p)}function b(s){return String(s||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function M(s,i=[]){let e=Array.isArray(i)?i:[];if(e.length===0)return{requested:s||null,resolved:null,strategy:"none"};let t=e.filter(r=>!r.subtask),a=t.length>0?t:e,n=b(s);if(n){let r=a.find(u=>b(u.name)===n);if(r)return{requested:s,resolved:r,strategy:"exact"};let c={task:["task","\u4EFB\u52A1","\u4E8B\u9879","to do","todo"],story:["story","\u7528\u6237\u6545\u4E8B","\u9700\u6C42"],bug:["bug","\u7F3A\u9677","\u95EE\u9898"],improvement:["improvement","\u4F18\u5316","\u6539\u8FDB"],epic:["epic","\u53F2\u8BD7"]};for(let u of Object.values(c)){if(!u.some(m=>b(m)===n))continue;let d=a.find(m=>u.some(g=>b(g)===b(m.name)));if(d)return{requested:s,resolved:d,strategy:"alias"}}let p=a.map(u=>({t:u,score:I(s,u.name)})).sort((u,d)=>d.score-u.score);if(p[0]&&p[0].score>=.5)return{requested:s,resolved:p[0].t,strategy:"fuzzy"}}let o=["task","story","bug","improvement","epic"];for(let r of o){let c=a.find(p=>b(p.name)===r);if(c)return{requested:s||null,resolved:c,strategy:"default-preferred"}}return{requested:s||null,resolved:a[0],strategy:"default-first"}}async function P(s){let i=`projectKeys=${encodeURIComponent(s)}&expand=projects.issuetypes`,e=await y(`/rest/api/3/issue/createmeta?${i}`),t=Array.isArray(e?.projects)?e.projects:[],n=t.find(r=>String(r?.key||"").toUpperCase()===String(s||"").toUpperCase())||t[0]||null;return(Array.isArray(n?.issuetypes)?n.issuetypes:[]).map(r=>({id:r.id,name:r.name,subtask:!!r.subtask,description:r.description||null}))}async function J(s,i){if(!s)throw new Error("projectKey is required");let e="sprint is not EMPTY";i==="active"?e="sprint in openSprints()":i==="closed"?e="sprint in closedSprints()":i==="future"&&(e="sprint in futureSprints()");let t=`project = ${s} AND ${e} ORDER BY updated DESC`,a=`jql=${encodeURIComponent(t)}&maxResults=100&fields=customfield_10020`,n=await y(`/rest/api/3/search/jql?${a}`),o=new Map;for(let r of n.issues||[])for(let c of r.fields?.customfield_10020||[])c&&!o.has(c.id)&&o.set(c.id,{id:c.id,name:c.name,state:c.state,boardId:c.boardId||null,startDate:c.startDate||null,endDate:c.endDate||null,goal:c.goal||null});return[...o.values()].sort((r,c)=>{let p={active:0,future:1,closed:2},u=(p[r.state]??3)-(p[c.state]??3);return u!==0?u:String(c.startDate||"").localeCompare(String(r.startDate||""))})}function G(s,{sprintId:i,sprintName:e,target:t}={}){let a=Array.isArray(s)?s:[];if(!a.length)return{sprint:null,selectedBy:"none"};if(i!=null&&String(i).trim()!=="")return{sprint:a.find(r=>String(r.id)===String(i))||null,selectedBy:"id"};if(e&&String(e).trim()){let o=String(e).trim(),r=a.find(p=>String(p.name||"").toLowerCase()===o.toLowerCase());if(r)return{sprint:r,selectedBy:"name-exact"};let c=a.map(p=>({s:p,score:I(o,p.name||"")})).sort((p,u)=>u.score-p.score);return c[0]&&c[0].score>=.5?{sprint:c[0].s,selectedBy:"name-fuzzy"}:{sprint:null,selectedBy:"name-none"}}let n=String(t||"current").trim().toLowerCase();return n==="active"||n==="current"||n==="latest"?{sprint:a[0],selectedBy:n}:{sprint:a[0],selectedBy:"default"}}function Y(s,i){let e=s?.fields?.customfield_10020;return Array.isArray(e)?e.some(t=>String(t?.id)===String(i)):!1}async function F({issueKey:s,projectKey:i,sprintId:e,attempts:t=3,delayMs:a=450}){let n=[];for(let o=0;o<t;o++){try{let r=`project = ${i} AND key = ${s} AND sprint = ${e}`,c=`jql=${encodeURIComponent(r)}&maxResults=1&fields=key,status`,p=await y(`/rest/api/3/search/jql?${c}`);if(Number(p?.total||0)>0)return n.push({attempt:o+1,jql:!0,issueField:null}),{ok:!0,method:"jql",traces:n};let d=await y(`/rest/api/3/issue/${s}?fields=customfield_10020,status`),m=Y(d,e);if(n.push({attempt:o+1,jql:!1,issueField:m}),m)return{ok:!0,method:"issue_field",traces:n}}catch(r){n.push({attempt:o+1,error:String(r?.message||r)})}o<t-1&&await new Promise(r=>setTimeout(r,a))}return{ok:!1,method:"none",traces:n}}async function N({issueKey:s,projectKey:i,sprintId:e,sprintName:t,target:a}){if(!s)return{ok:!1,error:"issueKey is required"};let n=i;if(!n&&(n=(await y(`/rest/api/3/issue/${s}?fields=project`))?.fields?.project?.key||null,!n))return{ok:!1,error:`Could not resolve project for ${s}`};let o=await J(n,"active");if(!o.length)return{ok:!1,error:`No assignable active sprint found for project ${n}`};let{sprint:r,selectedBy:c}=G(o,{sprintId:e,sprintName:t,target:a});if(!r)return{ok:!1,error:`No matching sprint found in ${n}`,requested:{sprintId:e??null,sprintName:t??null,target:a??"current"},availableSprints:o.map(d=>({id:d.id,name:d.name,state:d.state}))};await y(`/rest/api/3/issue/${s}`,{method:"PUT",body:{fields:{customfield_10020:Number(r.id)}}});let p=await F({issueKey:s,projectKey:n,sprintId:r.id}),u=p.ok;return{ok:u,issueKey:s,projectKey:n,sprintId:r.id,sprintName:r.name,selectedBy:c,verifiedBy:p.method,verified:u,verificationTrace:p.traces,warning:u?null:`Sprint assignment attempted but verification did not find ${s} in sprint ${r.id}`}}async function y(s,i={}){let e=async()=>{let{token:t,cloudId:a}=await L("jira");if(typeof t!="string"||!t)throw new Error(`Invalid jira token type: ${typeof t}`);if(!a)throw new Error("Invalid jira cloudId: missing");let n=`https://api.atlassian.com/ex/jira/${a}${s}`,o=await fetch(n,{method:i.method||"GET",headers:{Authorization:`Bearer ${t}`,Accept:"application/json",...i.body?{"Content-Type":"application/json"}:{},...i.headers},body:i.body?JSON.stringify(i.body):void 0});if(!o.ok){let c=await o.text().catch(()=>"");throw new Error(`Jira API ${o.status}: ${c.slice(0,300)}`)}let r=await o.text().catch(()=>"");if(!r||!r.trim())return{};try{return JSON.parse(r)}catch{return{raw:r}}};try{return await e()}catch(t){let a=String(t?.message||t||"").toLowerCase();if(!(a.includes("token")||a.includes("401")||a.includes("403")||a.includes("substring")))throw t;return E("jira"),e()}}var S={id:"jira",callsBackend:!0,serverName:"jira",allowedTools:["mcp__jira__*"],requiresIntegration:C.JIRA,envKeys:["ATLASSIAN_ACCESS_TOKEN","ATLASSIAN_CLOUD_ID"],description:"Zibby Jira MCP Server (OAuth Bearer)",promptFragment:`## Jira
19
+ `)}function v(s){return String(s||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function A(s){return v(s).replace(/[a-z0-9]+/g,"")}function I(s,r){let e=v(s),t=v(r);if(!e||!t)return 0;if(e===t)return 1;if(e.length===1||t.length===1)return e===t?1:0;let a=u=>{let d=new Map;for(let m=0;m<u.length-1;m++){let g=u.slice(m,m+2);d.set(g,(d.get(g)||0)+1)}return d},i=a(e),c=a(t),n=0,o=0,l=0;for(let u of i.values())o+=u;for(let u of c.values())l+=u;for(let[u,d]of i.entries()){let m=c.get(u)||0;n+=Math.min(d,m)}return 2*n/Math.max(1,o+l)}function b(s){return String(s||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function M(s,r=[]){let e=Array.isArray(r)?r:[];if(e.length===0)return{requested:s||null,resolved:null,strategy:"none"};let t=e.filter(n=>!n.subtask),a=t.length>0?t:e,i=b(s);if(i){let n=a.find(u=>b(u.name)===i);if(n)return{requested:s,resolved:n,strategy:"exact"};let o={task:["task","\u4EFB\u52A1","\u4E8B\u9879","to do","todo"],story:["story","\u7528\u6237\u6545\u4E8B","\u9700\u6C42"],bug:["bug","\u7F3A\u9677","\u95EE\u9898"],improvement:["improvement","\u4F18\u5316","\u6539\u8FDB"],epic:["epic","\u53F2\u8BD7"]};for(let u of Object.values(o)){if(!u.some(m=>b(m)===i))continue;let d=a.find(m=>u.some(g=>b(g)===b(m.name)));if(d)return{requested:s,resolved:d,strategy:"alias"}}let l=a.map(u=>({t:u,score:I(s,u.name)})).sort((u,d)=>d.score-u.score);if(l[0]&&l[0].score>=.5)return{requested:s,resolved:l[0].t,strategy:"fuzzy"}}let c=["task","story","bug","improvement","epic"];for(let n of c){let o=a.find(l=>b(l.name)===n);if(o)return{requested:s||null,resolved:o,strategy:"default-preferred"}}return{requested:s||null,resolved:a[0],strategy:"default-first"}}async function C(s){let r=`projectKeys=${encodeURIComponent(s)}&expand=projects.issuetypes`,e=await y(`/rest/api/3/issue/createmeta?${r}`),t=Array.isArray(e?.projects)?e.projects:[],i=t.find(n=>String(n?.key||"").toUpperCase()===String(s||"").toUpperCase())||t[0]||null;return(Array.isArray(i?.issuetypes)?i.issuetypes:[]).map(n=>({id:n.id,name:n.name,subtask:!!n.subtask,description:n.description||null}))}async function P(s,r){if(!s)throw new Error("projectKey is required");let e="sprint is not EMPTY";r==="active"?e="sprint in openSprints()":r==="closed"?e="sprint in closedSprints()":r==="future"&&(e="sprint in futureSprints()");let t=`project = ${s} AND ${e} ORDER BY updated DESC`,a=`jql=${encodeURIComponent(t)}&maxResults=100&fields=customfield_10020`,i=await y(`/rest/api/3/search/jql?${a}`),c=new Map;for(let n of i.issues||[])for(let o of n.fields?.customfield_10020||[])o&&!c.has(o.id)&&c.set(o.id,{id:o.id,name:o.name,state:o.state,boardId:o.boardId||null,startDate:o.startDate||null,endDate:o.endDate||null,goal:o.goal||null});return[...c.values()].sort((n,o)=>{let l={active:0,future:1,closed:2},u=(l[n.state]??3)-(l[o.state]??3);return u!==0?u:String(o.startDate||"").localeCompare(String(n.startDate||""))})}function G(s,{sprintId:r,sprintName:e,target:t}={}){let a=Array.isArray(s)?s:[];if(!a.length)return{sprint:null,selectedBy:"none"};if(r!=null&&String(r).trim()!=="")return{sprint:a.find(n=>String(n.id)===String(r))||null,selectedBy:"id"};if(e&&String(e).trim()){let c=String(e).trim(),n=a.find(l=>String(l.name||"").toLowerCase()===c.toLowerCase());if(n)return{sprint:n,selectedBy:"name-exact"};let o=a.map(l=>({s:l,score:I(c,l.name||"")})).sort((l,u)=>u.score-l.score);return o[0]&&o[0].score>=.5?{sprint:o[0].s,selectedBy:"name-fuzzy"}:{sprint:null,selectedBy:"name-none"}}let i=String(t||"current").trim().toLowerCase();return i==="active"||i==="current"||i==="latest"?{sprint:a[0],selectedBy:i}:{sprint:a[0],selectedBy:"default"}}function Y(s,r){let e=s?.fields?.customfield_10020;return Array.isArray(e)?e.some(t=>String(t?.id)===String(r)):!1}async function F({issueKey:s,projectKey:r,sprintId:e,attempts:t=3,delayMs:a=450}){let i=[];for(let c=0;c<t;c++){try{let n=`project = ${r} AND key = ${s} AND sprint = ${e}`,o=`jql=${encodeURIComponent(n)}&maxResults=1&fields=key,status`,l=await y(`/rest/api/3/search/jql?${o}`);if(Number(l?.total||0)>0)return i.push({attempt:c+1,jql:!0,issueField:null}),{ok:!0,method:"jql",traces:i};let d=await y(`/rest/api/3/issue/${s}?fields=customfield_10020,status`),m=Y(d,e);if(i.push({attempt:c+1,jql:!1,issueField:m}),m)return{ok:!0,method:"issue_field",traces:i}}catch(n){i.push({attempt:c+1,error:String(n?.message||n)})}c<t-1&&await new Promise(n=>setTimeout(n,a))}return{ok:!1,method:"none",traces:i}}async function N({issueKey:s,projectKey:r,sprintId:e,sprintName:t,target:a}){if(!s)return{ok:!1,error:"issueKey is required"};let i=r;if(!i&&(i=(await y(`/rest/api/3/issue/${s}?fields=project`))?.fields?.project?.key||null,!i))return{ok:!1,error:`Could not resolve project for ${s}`};let c=await P(i,"active");if(!c.length)return{ok:!1,error:`No assignable active sprint found for project ${i}`};let{sprint:n,selectedBy:o}=G(c,{sprintId:e,sprintName:t,target:a});if(!n)return{ok:!1,error:`No matching sprint found in ${i}`,requested:{sprintId:e??null,sprintName:t??null,target:a??"current"},availableSprints:c.map(d=>({id:d.id,name:d.name,state:d.state}))};await y(`/rest/api/3/issue/${s}`,{method:"PUT",body:{fields:{customfield_10020:Number(n.id)}}});let l=await F({issueKey:s,projectKey:i,sprintId:n.id}),u=l.ok;return{ok:u,issueKey:s,projectKey:i,sprintId:n.id,sprintName:n.name,selectedBy:o,verifiedBy:l.method,verified:u,verificationTrace:l.traces,warning:u?null:`Sprint assignment attempted but verification did not find ${s} in sprint ${n.id}`}}async function z(){let s=process.env.JIRA_API_TOKEN,r=process.env.JIRA_EMAIL,e=process.env.JIRA_BASE_URL;if(s&&r&&e)return{authType:"token",apiToken:s,email:r,baseUrl:e};let t=await E("jira");return t?.cloudId?{authType:"oauth",accessToken:t.token,cloudId:t.cloudId}:{authType:"token",apiToken:t?.token,email:r||t?.email||"",baseUrl:e||t?.instanceUrl||t?.baseUrl||process.env.ATLASSIAN_INSTANCE_URL||""}}function H(s,r,e={}){let t=s||{},a={Accept:"application/json",...e.body?{"Content-Type":"application/json"}:{},...e.headers};if(t.authType==="token"){let i=String(t.baseUrl||t.instanceUrl||"").trim().replace(/\/+$/,""),c=t.apiToken||t.accessToken||"";if(!i)throw new Error("Jira token connection has no base URL \u2014 reconnect Jira with its instance URL, or set JIRA_BASE_URL.");if(!t.email)throw new Error("Jira token connection has no account email \u2014 reconnect Jira with the account email, or set JIRA_EMAIL.");if(!c)throw new Error("Jira token connection has no API token \u2014 reconnect Jira, or set JIRA_API_TOKEN.");return{url:`${i}${r}`,headers:{Authorization:"Basic "+Buffer.from(`${t.email}:${c}`).toString("base64"),...a}}}if(typeof t.accessToken!="string"||!t.accessToken)throw new Error(`Invalid jira token type: ${typeof t.accessToken}`);if(!t.cloudId)throw new Error("Invalid jira cloudId: missing");return{url:`https://api.atlassian.com/ex/jira/${t.cloudId}${r}`,headers:{Authorization:`Bearer ${t.accessToken}`,...a}}}async function y(s,r={}){let e=async()=>{let t=await z(),{url:a,headers:i}=H(t,s,r),c=await fetch(a,{method:r.method||"GET",headers:i,body:r.body?JSON.stringify(r.body):void 0,signal:r.signal}),n=()=>c.text().catch(l=>{if(r.signal?.aborted)throw l;return""});if(!c.ok){let l=await n();throw new Error(`Jira API ${c.status}: ${l.slice(0,300)}`)}let o=await n();if(!o||!o.trim())return{};try{return JSON.parse(o)}catch{return{raw:o}}};try{return await e()}catch(t){if(r.signal?.aborted)throw t;let a=String(t?.message||t||"").toLowerCase();if(!(a.includes("token")||a.includes("401")||a.includes("403")||a.includes("substring")))throw t;return L("jira"),e()}}var S={id:"jira",callsBackend:!0,serverName:"jira",allowedTools:["mcp__jira__*"],requiresIntegration:$.JIRA,envKeys:["ATLASSIAN_ACCESS_TOKEN","ATLASSIAN_CLOUD_ID"],description:"Zibby Jira MCP Server (OAuth Bearer)",promptFragment:`## Jira
20
20
  You have direct access to the user's Jira. Use these tools proactively:
21
21
 
22
22
  ### Issue tools
@@ -66,6 +66,6 @@ When user asks to move/transition ticket status:
66
66
  3. Pick the correct transition from returned list (match by "to" status name, not guesswork), then call jira_transition_issue with transitionId.
67
67
  4. Call jira_get_issue(issueKey) to verify final status before claiming success.
68
68
  5. If target wording differs (e.g. \u5DF2\u7ECF\u9A8C\u6536 vs \u5DF2\u9A8C\u6536), try toStatus first; only ask user to confirm when no reasonable match exists.
69
- 6. IMPORTANT: When target is clear, complete transition + verification in SAME turn. Do NOT stop after listing options.`,resolve(){let s=D();if(!s)return null;let i={};for(let e of this.envKeys)process.env[e]&&(i[e]=process.env[e]);process.env.ATLASSIAN_INSTANCE_URL&&(i.ATLASSIAN_INSTANCE_URL=process.env.ATLASSIAN_INSTANCE_URL);for(let e of["JIRA_API_TOKEN","JIRA_EMAIL","JIRA_BASE_URL"])process.env[e]&&(i[e]=process.env[e]);return{command:"node",args:[s],env:i,description:this.description}},async handleToolCall(s,i){try{switch(s){case"jira_list_projects":{let e=await y("/rest/api/3/project"),t=(Array.isArray(e)?e:[]).map(a=>({id:a.id,key:a.key,name:a.name,style:a.style}));return JSON.stringify({count:t.length,projects:t})}case"jira_list_statuses":{let{projectKey:e}=i||{};if(e){let n=await y(`/rest/api/3/project/${encodeURIComponent(e)}/statuses`),o=Array.isArray(n)?n:[],r=new Map;for(let p of o)for(let u of p.statuses||[])u?.id&&(r.has(u.id)||r.set(u.id,{id:u.id,name:u.name,category:u.statusCategory?.name||null}));let c=[...r.values()].sort((p,u)=>String(p.name).localeCompare(String(u.name)));return JSON.stringify({scope:"project",projectKey:e,count:c.length,statuses:c})}let t=await y("/rest/api/3/status"),a=(Array.isArray(t)?t:[]).map(n=>({id:n.id,name:n.name,category:n.statusCategory?.name||null})).sort((n,o)=>String(n.name).localeCompare(String(o.name)));return JSON.stringify({scope:"global",count:a.length,statuses:a})}case"jira_list_issue_types":{let{projectKey:e}=i||{};if(!e)return JSON.stringify({error:"projectKey is required"});let t=await P(e);return JSON.stringify({projectKey:e,count:t.length,issueTypes:t})}case"jira_search":{let e=i.jql||"",t=i.maxResults||20;e.replace(/\s*ORDER\s+BY\s+.*/i,"").trim()||(e=`created >= -365d ${e}`.trim());let n=`jql=${encodeURIComponent(e)}&maxResults=${t}&fields=summary,status,assignee,priority,updated,issuetype,project`,r=((await y(`/rest/api/3/search/jql?${n}`)).issues||[]).map(c=>({key:c.key,project:c.fields?.project?.key,summary:c.fields?.summary,status:c.fields?.status?.name,assignee:c.fields?.assignee?.displayName||"Unassigned",priority:c.fields?.priority?.name,type:c.fields?.issuetype?.name}));return JSON.stringify({count:r.length,issues:r})}case"jira_get_issue":{let e=i.issueKey;if(!e)return JSON.stringify({error:"issueKey is required"});let t=await y(`/rest/api/3/issue/${e}`);return JSON.stringify({key:t.key,project:t.fields?.project?.key,summary:t.fields?.summary,description:t.fields?.description,status:t.fields?.status?.name,assignee:t.fields?.assignee?.displayName||"Unassigned",priority:t.fields?.priority?.name,type:t.fields?.issuetype?.name,labels:t.fields?.labels,created:t.fields?.created,updated:t.fields?.updated})}case"jira_create_issue":{let{projectKey:e,summary:t,issueType:a,description:n,priority:o,labels:r,assigneeId:c,moveToSprint:p,moveToActiveSprint:u,sprintId:d,sprintName:m,target:g}=i;if(!e||!t)return JSON.stringify({error:"projectKey and summary are required"});let l={requested:a||null,resolved:null,strategy:"none"},h=[];try{h=await P(e),l=M(a,h)}catch{}let f={project:{key:e},summary:t,issuetype:l?.resolved?.id?{id:l.resolved.id}:{name:a||"Task"}};n&&(f.description={type:"doc",version:1,content:[{type:"paragraph",content:[{type:"text",text:n}]}]}),o&&(f.priority={name:o}),r?.length&&(f.labels=r),c&&(f.assignee={id:c});let j=await y("/rest/api/3/issue",{method:"POST",body:{fields:f}}),_={ok:!0,key:j.key,id:j.id,self:j.self};return l?.resolved&&(_.issueType=l.resolved.name,_.issueTypeResolution=l.strategy,l.strategy!=="exact"&&l.requested&&b(l.requested)!==b(l.resolved.name)&&(_.issueTypeWarning=`Requested "${l.requested}" is not available in ${e}; used "${l.resolved.name}" instead.`)),h.length>0&&(_.availableIssueTypes=h.map(k=>k.name)),(p||u)&&(_.sprintMove=await N({issueKey:j.key,projectKey:e,sprintId:d,sprintName:m,target:g})),JSON.stringify(_)}case"jira_list_sprints":{let{projectKey:e,state:t}=i,a=await J(e,t);return JSON.stringify({count:a.length,sprints:a})}case"jira_move_to_active_sprint":{let{issueKey:e,projectKey:t,sprintId:a,sprintName:n,target:o}=i||{},r=await N({issueKey:e,projectKey:t,sprintId:a,sprintName:n,target:o||"current"});return JSON.stringify(r)}case"jira_move_issue_to_sprint":{let{issueKey:e,projectKey:t,sprintId:a,sprintName:n,target:o}=i||{},r=await N({issueKey:e,projectKey:t,sprintId:a,sprintName:n,target:o});return JSON.stringify(r)}case"jira_get_sprint_issues":{let{sprintName:e,sprintId:t,projectKey:a,status:n,maxResults:o}=i;if(!e&&!t)return JSON.stringify({error:"sprintName or sprintId is required"});let r=o||50,c=t?`sprint = ${t}`:`sprint = "${e}"`,p=a?`project = ${a} AND `:"",u=n?` AND status = "${n}"`:"",d=`${p}${c}${u} ORDER BY status ASC, priority DESC`,m=`jql=${encodeURIComponent(d)}&maxResults=${r}&fields=summary,status,assignee,priority,issuetype,project`,g=await y(`/rest/api/3/search/jql?${m}`),l=(g.issues||[]).map(f=>({key:f.key,project:f.fields?.project?.key,summary:f.fields?.summary,status:f.fields?.status?.name,assignee:f.fields?.assignee?.displayName||"Unassigned",priority:f.fields?.priority?.name,type:f.fields?.issuetype?.name})),h={};for(let f of l)h[f.status]=(h[f.status]||0)+1;return JSON.stringify({count:l.length,total:g.total||l.length,statusCounts:h,issues:l})}case"jira_get_comments":{let{issueKey:e,maxResults:t}=i;if(!e)return JSON.stringify({error:"issueKey is required"});let n=await y(`/rest/api/3/issue/${e}/comment?maxResults=${t||50}&orderBy=-created`),o=(n.comments||[]).map(r=>{let c="";return r.body?.content&&(c=w(r.body.content)),{id:r.id,author:r.author?.displayName||"Unknown",body:c,created:r.created,updated:r.updated}});return JSON.stringify({count:o.length,total:n.total||o.length,comments:o})}case"jira_add_comment":{let{issueKey:e,body:t}=i;return!e||!t?JSON.stringify({error:"issueKey and body are required"}):(await y(`/rest/api/3/issue/${e}/comment`,{method:"POST",body:{body:{type:"doc",version:1,content:[{type:"paragraph",content:[{type:"text",text:t}]}]}}}),JSON.stringify({ok:!0,issueKey:e}))}case"jira_edit_issue":{let{issueKey:e,fields:t}=i;return!e||!t?JSON.stringify({error:"issueKey and fields are required"}):(await y(`/rest/api/3/issue/${e}`,{method:"PUT",body:{fields:t}}),JSON.stringify({ok:!0,issueKey:e}))}case"jira_transition_issue":{let{issueKey:e,transitionId:t,toStatus:a,statusName:n,status:o}=i;if(!e)return JSON.stringify({error:"issueKey is required"});let r=String(a||n||o||"").trim();if(!t&&!r){let d=((await y(`/rest/api/3/issue/${e}/transitions`)).transitions||[]).map(m=>({id:m.id,name:m.name,to:m.to?.name}));return JSON.stringify({ok:!1,error:"transitionId or toStatus is required",issueKey:e,availableTransitions:d})}let c=t;if(!c){let d=(await y(`/rest/api/3/issue/${e}/transitions`)).transitions||[],m=v(r),g=d.find(l=>v(l?.name||"")===m||v(l?.to?.name||"")===m);if(!g){let l=A(r);l.length>=2&&(g=d.find(h=>{let f=A(h?.name||""),j=A(h?.to?.name||""),_=f.length>=2&&(f.includes(l)||l.includes(f)),k=j.length>=2&&(j.includes(l)||l.includes(j));return _||k}))}if(!g){let l=d.map(_=>{let k=I(r,_?.name||""),q=I(r,_?.to?.name||"");return{t:_,score:Math.max(k,q)}}).sort((_,k)=>k.score-_.score),h=l[0],f=l[1];h&&h.score>=.45&&(!f||h.score-f.score>=.12)&&(g=h.t)}if(!g?.id)return JSON.stringify({ok:!1,error:`No transition matches target status: "${r}"`,issueKey:e,availableTransitions:d.map(l=>({id:l.id,name:l.name,to:l.to?.name}))});c=g.id}await y(`/rest/api/3/issue/${e}/transitions`,{method:"POST",body:{transition:{id:c}}});let p=await y(`/rest/api/3/issue/${e}?fields=status`);return JSON.stringify({ok:!0,issueKey:e,transitionId:c,statusAfter:p?.fields?.status?.name||null})}default:return JSON.stringify({error:`Unknown tool: ${s}`})}}catch(e){return JSON.stringify({error:e.message})}},tools:[{name:"jira_list_projects",description:"List all Jira projects accessible to the user",input_schema:{type:"object",properties:{}}},{name:"jira_list_statuses",description:"List Jira statuses. Use projectKey to get statuses applicable in that project workflow.",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Optional project key (e.g. PROJ). If omitted, returns global status catalog."}}}},{name:"jira_list_issue_types",description:"List issue types allowed for issue creation in the given project.",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"}},required:["projectKey"]}},{name:"jira_search",description:"Search Jira issues using JQL",input_schema:{type:"object",properties:{jql:{type:"string",description:'JQL query string, e.g. "project = PROJ AND status = Open"'},maxResults:{type:"number",description:"Max results to return (default 20)"}},required:["jql"]}},{name:"jira_get_issue",description:"Get details of a specific Jira issue",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"}},required:["issueKey"]}},{name:"jira_create_issue",description:"Create a new Jira issue",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"},summary:{type:"string",description:"Issue title/summary"},issueType:{type:"string",description:"Issue type (default: Task). Common: Task, Bug, Story, Epic"},description:{type:"string",description:"Issue description (plain text)"},priority:{type:"string",description:"Priority name, e.g. High, Medium, Low"},labels:{type:"array",items:{type:"string"},description:"Array of label strings"},assigneeId:{type:"string",description:"Atlassian account ID to assign to"},moveToSprint:{type:"boolean",description:"If true, move created issue to a sprint and verify."},moveToActiveSprint:{type:"boolean",description:"Backward-compatible alias for moveToSprint."},sprintId:{type:"number",description:"Optional sprint id for placement."},sprintName:{type:"string",description:"Optional sprint name for placement."},target:{type:"string",description:"Placement target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["projectKey","summary"]}},{name:"jira_list_sprints",description:"List sprints for a Jira project (returns sprint names, IDs, states, dates)",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"},state:{type:"string",description:"Filter: active, closed, future. Omit for all."}},required:["projectKey"]}},{name:"jira_get_sprint_issues",description:"Get all issues in a sprint, optionally filtered by status column name",input_schema:{type:"object",properties:{sprintName:{type:"string",description:"Sprint name (from jira_list_sprints). Use this OR sprintId."},sprintId:{type:"number",description:"Sprint ID (from jira_list_sprints). Use this OR sprintName."},projectKey:{type:"string",description:"Project key to scope the search (optional)"},status:{type:"string",description:'Filter by status name (e.g. "\u8FDB\u884C\u4E2D", "\u6D4B\u8BD5", "Done")'},maxResults:{type:"number",description:"Max issues to return (default 50)"}}}},{name:"jira_move_to_active_sprint",description:"Backward-compatible alias: move issue to sprint target and verify membership.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},projectKey:{type:"string",description:"Optional project key. If omitted, inferred from issue."},sprintId:{type:"number",description:"Optional sprint id."},sprintName:{type:"string",description:"Optional sprint name."},target:{type:"string",description:"Target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["issueKey"]}},{name:"jira_move_issue_to_sprint",description:"Move an issue to a sprint by id/name/target and verify membership.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},projectKey:{type:"string",description:"Optional project key. If omitted, inferred from issue."},sprintId:{type:"number",description:"Optional sprint id."},sprintName:{type:"string",description:"Optional sprint name."},target:{type:"string",description:"Target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["issueKey"]}},{name:"jira_get_comments",description:"Get comments on a Jira issue (newest first)",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},maxResults:{type:"number",description:"Max comments to return (default 50)"}},required:["issueKey"]}},{name:"jira_add_comment",description:"Add a comment to a Jira issue",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},body:{type:"string",description:"Comment text (plain text)"}},required:["issueKey","body"]}},{name:"jira_edit_issue",description:"Update fields on a Jira issue (summary, story points, labels, priority)",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},fields:{type:"object",description:"Object of field names to values",additionalProperties:!0}},required:["issueKey","fields"]}},{name:"jira_transition_issue",description:"Move a Jira issue to a different status. Always pass toStatus when user gave a target; only pass issueKey alone when you explicitly need to list transitions.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},transitionId:{type:"string",description:"Transition ID to perform (optional if toStatus is provided)"},toStatus:{type:"string",description:'Target status/column name (e.g. "\u5DF2\u7ECF\u9A8C\u6536", "Done", "In Progress"). If provided, tool resolves matching transition automatically.'}},required:["issueKey"]}}]};var z={new:"todo",indeterminate:"in_progress",done:"done"},H=/\b(blocked|on[\s-]?hold|waiting|stuck)\b/i;function T(s){if(!s)return"unknown";if(s.name&&H.test(s.name))return"blocked";let i=s.statusCategory?.key;return z[i]||"unknown"}function $(s){if(!s)return"";if(Array.isArray(s))return s.map($).join("");if(s.type==="text")return s.text||"";if(s.type==="hardBreak"||s.type==="rule")return`
70
- `;let i=s.content?$(s.content):"";return/^(paragraph|heading|listItem|blockquote|codeBlock|panel)$/.test(s.type)&&(i+=`
71
- `),i}function O(s,i){let e=s.fields||{},t=e.status||null,a=null;if(s.self&&s.key)try{a=`${new URL(s.self).origin}/browse/${s.key}`}catch{a=null}return!a&&i&&s.key&&(a=`${String(i).replace(/\/+$/,"")}/browse/${s.key}`),{id:String(s.id??s.key??""),key:s.key,title:e.summary||"",body:typeof e.description=="string"?e.description:$(e.description?.content).trim(),state:t?.name||null,stateCategory:T(t),assignee:e.assignee?.displayName||null,url:a,_raw:s}}function R(s){let i=JSON.parse(s);if(i&&i.error)throw new Error(i.error);return i}var W={id:"jira",toStateCategory:T,toNeutral:O,async listCandidates(s={}){let i=s.query||"";if(!i.trim()){let n=[];if(s.state&&n.push(`status = "${s.state}"`),s.labels){let o=Array.isArray(s.labels)?s.labels:[s.labels];for(let r of o)n.push(`labels = "${r}"`)}s.updatedAfter&&n.push(`updated >= "${s.updatedAfter}"`),i=n.length?`${n.join(" AND ")} ORDER BY updated DESC`:"ORDER BY updated DESC"}let e=Number(s.limit)||30,t=`jql=${encodeURIComponent(i)}&maxResults=${e}&fields=summary,description,status,assignee,labels,project,updated,created`;return((await y(`/rest/api/3/search/jql?${t}`)).issues||[]).map(n=>O(n))},async getTicket(s){if(!s)throw new Error("key is required");let i=await y(`/rest/api/3/issue/${encodeURIComponent(s)}`);return!i||!i.key?null:O(i)},async getComments(s){if(!s)throw new Error("key is required");return(R(await S.handleToolCall("jira_get_comments",{issueKey:s})).comments||[]).map(e=>({id:String(e.id),author:e.author||"Unknown",body:e.body||"",createdAt:e.created||null,updatedAt:e.updated||null,_raw:e}))},async addComment(s,i){if(!s||!i)throw new Error("key and body are required");return{ok:!!R(await S.handleToolCall("jira_add_comment",{issueKey:s,body:i})).ok,id:null}},async transition(s,i){if(!s)throw new Error("key is required");let e=JSON.parse(await S.handleToolCall("jira_transition_issue",{issueKey:s,toStatus:i}));if(!e.ok)return{ok:!1,error:e.error||"transition failed",_raw:e};let t=e.statusAfter||null;return{ok:!0,stateAfter:t,stateCategoryAfter:T(t?{name:t}:null),_raw:e}},async linkPullRequest(s,i,e){if(!s||!i)throw new Error("key and prUrl are required");try{return await y(`/rest/api/3/issue/${encodeURIComponent(s)}/remotelink`,{method:"POST",body:{object:{url:i,title:e||i,icon:{url16x16:"https://github.com/favicon.ico",title:"GitHub"}}}}),{ok:!0,via:"remotelink"}}catch(t){let a=`${e?`${e}: `:"Linked PR: "}${i}`;return{ok:!!R(await S.handleToolCall("jira_add_comment",{issueKey:s,body:a})).ok,via:"comment",error:String(t?.message||t)}}}},ie=W;export{ie as default,W as jiraAdapter};
69
+ 6. IMPORTANT: When target is clear, complete transition + verification in SAME turn. Do NOT stop after listing options.`,resolve(){let s=D();if(!s)return null;let r={};for(let e of this.envKeys)process.env[e]&&(r[e]=process.env[e]);process.env.ATLASSIAN_INSTANCE_URL&&(r.ATLASSIAN_INSTANCE_URL=process.env.ATLASSIAN_INSTANCE_URL);for(let e of["JIRA_API_TOKEN","JIRA_EMAIL","JIRA_BASE_URL"])process.env[e]&&(r[e]=process.env[e]);return{command:"node",args:[s],env:r,description:this.description}},async handleToolCall(s,r){try{switch(s){case"jira_list_projects":{let e=await y("/rest/api/3/project"),t=(Array.isArray(e)?e:[]).map(a=>({id:a.id,key:a.key,name:a.name,style:a.style}));return JSON.stringify({count:t.length,projects:t})}case"jira_list_statuses":{let{projectKey:e}=r||{};if(e){let i=await y(`/rest/api/3/project/${encodeURIComponent(e)}/statuses`),c=Array.isArray(i)?i:[],n=new Map;for(let l of c)for(let u of l.statuses||[])u?.id&&(n.has(u.id)||n.set(u.id,{id:u.id,name:u.name,category:u.statusCategory?.name||null}));let o=[...n.values()].sort((l,u)=>String(l.name).localeCompare(String(u.name)));return JSON.stringify({scope:"project",projectKey:e,count:o.length,statuses:o})}let t=await y("/rest/api/3/status"),a=(Array.isArray(t)?t:[]).map(i=>({id:i.id,name:i.name,category:i.statusCategory?.name||null})).sort((i,c)=>String(i.name).localeCompare(String(c.name)));return JSON.stringify({scope:"global",count:a.length,statuses:a})}case"jira_list_issue_types":{let{projectKey:e}=r||{};if(!e)return JSON.stringify({error:"projectKey is required"});let t=await C(e);return JSON.stringify({projectKey:e,count:t.length,issueTypes:t})}case"jira_search":{let e=r.jql||"",t=r.maxResults||20;e.replace(/\s*ORDER\s+BY\s+.*/i,"").trim()||(e=`created >= -365d ${e}`.trim());let i=`jql=${encodeURIComponent(e)}&maxResults=${t}&fields=summary,status,assignee,priority,updated,issuetype,project`,n=((await y(`/rest/api/3/search/jql?${i}`)).issues||[]).map(o=>({key:o.key,project:o.fields?.project?.key,summary:o.fields?.summary,status:o.fields?.status?.name,assignee:o.fields?.assignee?.displayName||"Unassigned",priority:o.fields?.priority?.name,type:o.fields?.issuetype?.name}));return JSON.stringify({count:n.length,issues:n})}case"jira_get_issue":{let e=r.issueKey;if(!e)return JSON.stringify({error:"issueKey is required"});let t=await y(`/rest/api/3/issue/${e}`);return JSON.stringify({key:t.key,project:t.fields?.project?.key,summary:t.fields?.summary,description:t.fields?.description,status:t.fields?.status?.name,assignee:t.fields?.assignee?.displayName||"Unassigned",priority:t.fields?.priority?.name,type:t.fields?.issuetype?.name,labels:t.fields?.labels,created:t.fields?.created,updated:t.fields?.updated})}case"jira_create_issue":{let{projectKey:e,summary:t,issueType:a,description:i,priority:c,labels:n,assigneeId:o,moveToSprint:l,moveToActiveSprint:u,sprintId:d,sprintName:m,target:g}=r;if(!e||!t)return JSON.stringify({error:"projectKey and summary are required"});let p={requested:a||null,resolved:null,strategy:"none"},h=[];try{h=await C(e),p=M(a,h)}catch{}let f={project:{key:e},summary:t,issuetype:p?.resolved?.id?{id:p.resolved.id}:{name:a||"Task"}};i&&(f.description={type:"doc",version:1,content:[{type:"paragraph",content:[{type:"text",text:i}]}]}),c&&(f.priority={name:c}),n?.length&&(f.labels=n),o&&(f.assignee={id:o});let j=await y("/rest/api/3/issue",{method:"POST",body:{fields:f}}),_={ok:!0,key:j.key,id:j.id,self:j.self};return p?.resolved&&(_.issueType=p.resolved.name,_.issueTypeResolution=p.strategy,p.strategy!=="exact"&&p.requested&&b(p.requested)!==b(p.resolved.name)&&(_.issueTypeWarning=`Requested "${p.requested}" is not available in ${e}; used "${p.resolved.name}" instead.`)),h.length>0&&(_.availableIssueTypes=h.map(k=>k.name)),(l||u)&&(_.sprintMove=await N({issueKey:j.key,projectKey:e,sprintId:d,sprintName:m,target:g})),JSON.stringify(_)}case"jira_list_sprints":{let{projectKey:e,state:t}=r,a=await P(e,t);return JSON.stringify({count:a.length,sprints:a})}case"jira_move_to_active_sprint":{let{issueKey:e,projectKey:t,sprintId:a,sprintName:i,target:c}=r||{},n=await N({issueKey:e,projectKey:t,sprintId:a,sprintName:i,target:c||"current"});return JSON.stringify(n)}case"jira_move_issue_to_sprint":{let{issueKey:e,projectKey:t,sprintId:a,sprintName:i,target:c}=r||{},n=await N({issueKey:e,projectKey:t,sprintId:a,sprintName:i,target:c});return JSON.stringify(n)}case"jira_get_sprint_issues":{let{sprintName:e,sprintId:t,projectKey:a,status:i,maxResults:c}=r;if(!e&&!t)return JSON.stringify({error:"sprintName or sprintId is required"});let n=c||50,o=t?`sprint = ${t}`:`sprint = "${e}"`,l=a?`project = ${a} AND `:"",u=i?` AND status = "${i}"`:"",d=`${l}${o}${u} ORDER BY status ASC, priority DESC`,m=`jql=${encodeURIComponent(d)}&maxResults=${n}&fields=summary,status,assignee,priority,issuetype,project`,g=await y(`/rest/api/3/search/jql?${m}`),p=(g.issues||[]).map(f=>({key:f.key,project:f.fields?.project?.key,summary:f.fields?.summary,status:f.fields?.status?.name,assignee:f.fields?.assignee?.displayName||"Unassigned",priority:f.fields?.priority?.name,type:f.fields?.issuetype?.name})),h={};for(let f of p)h[f.status]=(h[f.status]||0)+1;return JSON.stringify({count:p.length,total:g.total||p.length,statusCounts:h,issues:p})}case"jira_get_comments":{let{issueKey:e,maxResults:t}=r;if(!e)return JSON.stringify({error:"issueKey is required"});let i=await y(`/rest/api/3/issue/${e}/comment?maxResults=${t||50}&orderBy=-created`),c=(i.comments||[]).map(n=>{let o="";return n.body?.content&&(o=w(n.body.content)),{id:n.id,author:n.author?.displayName||"Unknown",body:o,created:n.created,updated:n.updated}});return JSON.stringify({count:c.length,total:i.total||c.length,comments:c})}case"jira_add_comment":{let{issueKey:e,body:t}=r;return!e||!t?JSON.stringify({error:"issueKey and body are required"}):(await y(`/rest/api/3/issue/${e}/comment`,{method:"POST",body:{body:{type:"doc",version:1,content:[{type:"paragraph",content:[{type:"text",text:t}]}]}}}),JSON.stringify({ok:!0,issueKey:e}))}case"jira_edit_issue":{let{issueKey:e,fields:t}=r;return!e||!t?JSON.stringify({error:"issueKey and fields are required"}):(await y(`/rest/api/3/issue/${e}`,{method:"PUT",body:{fields:t}}),JSON.stringify({ok:!0,issueKey:e}))}case"jira_transition_issue":{let{issueKey:e,transitionId:t,toStatus:a,statusName:i,status:c}=r;if(!e)return JSON.stringify({error:"issueKey is required"});let n=String(a||i||c||"").trim();if(!t&&!n){let d=((await y(`/rest/api/3/issue/${e}/transitions`)).transitions||[]).map(m=>({id:m.id,name:m.name,to:m.to?.name}));return JSON.stringify({ok:!1,error:"transitionId or toStatus is required",issueKey:e,availableTransitions:d})}let o=t;if(!o){let d=(await y(`/rest/api/3/issue/${e}/transitions`)).transitions||[],m=v(n),g=d.find(p=>v(p?.name||"")===m||v(p?.to?.name||"")===m);if(!g){let p=A(n);p.length>=2&&(g=d.find(h=>{let f=A(h?.name||""),j=A(h?.to?.name||""),_=f.length>=2&&(f.includes(p)||p.includes(f)),k=j.length>=2&&(j.includes(p)||p.includes(j));return _||k}))}if(!g){let p=d.map(_=>{let k=I(n,_?.name||""),q=I(n,_?.to?.name||"");return{t:_,score:Math.max(k,q)}}).sort((_,k)=>k.score-_.score),h=p[0],f=p[1];h&&h.score>=.45&&(!f||h.score-f.score>=.12)&&(g=h.t)}if(!g?.id)return JSON.stringify({ok:!1,error:`No transition matches target status: "${n}"`,issueKey:e,availableTransitions:d.map(p=>({id:p.id,name:p.name,to:p.to?.name}))});o=g.id}await y(`/rest/api/3/issue/${e}/transitions`,{method:"POST",body:{transition:{id:o}}});let l=await y(`/rest/api/3/issue/${e}?fields=status`);return JSON.stringify({ok:!0,issueKey:e,transitionId:o,statusAfter:l?.fields?.status?.name||null})}default:return JSON.stringify({error:`Unknown tool: ${s}`})}}catch(e){return JSON.stringify({error:e.message})}},tools:[{name:"jira_list_projects",description:"List all Jira projects accessible to the user",input_schema:{type:"object",properties:{}}},{name:"jira_list_statuses",description:"List Jira statuses. Use projectKey to get statuses applicable in that project workflow.",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Optional project key (e.g. PROJ). If omitted, returns global status catalog."}}}},{name:"jira_list_issue_types",description:"List issue types allowed for issue creation in the given project.",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"}},required:["projectKey"]}},{name:"jira_search",description:"Search Jira issues using JQL",input_schema:{type:"object",properties:{jql:{type:"string",description:'JQL query string, e.g. "project = PROJ AND status = Open"'},maxResults:{type:"number",description:"Max results to return (default 20)"}},required:["jql"]}},{name:"jira_get_issue",description:"Get details of a specific Jira issue",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"}},required:["issueKey"]}},{name:"jira_create_issue",description:"Create a new Jira issue",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"},summary:{type:"string",description:"Issue title/summary"},issueType:{type:"string",description:"Issue type (default: Task). Common: Task, Bug, Story, Epic"},description:{type:"string",description:"Issue description (plain text)"},priority:{type:"string",description:"Priority name, e.g. High, Medium, Low"},labels:{type:"array",items:{type:"string"},description:"Array of label strings"},assigneeId:{type:"string",description:"Atlassian account ID to assign to"},moveToSprint:{type:"boolean",description:"If true, move created issue to a sprint and verify."},moveToActiveSprint:{type:"boolean",description:"Backward-compatible alias for moveToSprint."},sprintId:{type:"number",description:"Optional sprint id for placement."},sprintName:{type:"string",description:"Optional sprint name for placement."},target:{type:"string",description:"Placement target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["projectKey","summary"]}},{name:"jira_list_sprints",description:"List sprints for a Jira project (returns sprint names, IDs, states, dates)",input_schema:{type:"object",properties:{projectKey:{type:"string",description:"Project key, e.g. PROJ"},state:{type:"string",description:"Filter: active, closed, future. Omit for all."}},required:["projectKey"]}},{name:"jira_get_sprint_issues",description:"Get all issues in a sprint, optionally filtered by status column name",input_schema:{type:"object",properties:{sprintName:{type:"string",description:"Sprint name (from jira_list_sprints). Use this OR sprintId."},sprintId:{type:"number",description:"Sprint ID (from jira_list_sprints). Use this OR sprintName."},projectKey:{type:"string",description:"Project key to scope the search (optional)"},status:{type:"string",description:'Filter by status name (e.g. "\u8FDB\u884C\u4E2D", "\u6D4B\u8BD5", "Done")'},maxResults:{type:"number",description:"Max issues to return (default 50)"}}}},{name:"jira_move_to_active_sprint",description:"Backward-compatible alias: move issue to sprint target and verify membership.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},projectKey:{type:"string",description:"Optional project key. If omitted, inferred from issue."},sprintId:{type:"number",description:"Optional sprint id."},sprintName:{type:"string",description:"Optional sprint name."},target:{type:"string",description:"Target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["issueKey"]}},{name:"jira_move_issue_to_sprint",description:"Move an issue to a sprint by id/name/target and verify membership.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},projectKey:{type:"string",description:"Optional project key. If omitted, inferred from issue."},sprintId:{type:"number",description:"Optional sprint id."},sprintName:{type:"string",description:"Optional sprint name."},target:{type:"string",description:"Target when sprintId/sprintName omitted: current|active|latest (default: current)."}},required:["issueKey"]}},{name:"jira_get_comments",description:"Get comments on a Jira issue (newest first)",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},maxResults:{type:"number",description:"Max comments to return (default 50)"}},required:["issueKey"]}},{name:"jira_add_comment",description:"Add a comment to a Jira issue",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},body:{type:"string",description:"Comment text (plain text)"}},required:["issueKey","body"]}},{name:"jira_edit_issue",description:"Update fields on a Jira issue (summary, story points, labels, priority)",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},fields:{type:"object",description:"Object of field names to values",additionalProperties:!0}},required:["issueKey","fields"]}},{name:"jira_transition_issue",description:"Move a Jira issue to a different status. Always pass toStatus when user gave a target; only pass issueKey alone when you explicitly need to list transitions.",input_schema:{type:"object",properties:{issueKey:{type:"string",description:"Issue key, e.g. PROJ-123"},transitionId:{type:"string",description:"Transition ID to perform (optional if toStatus is provided)"},toStatus:{type:"string",description:'Target status/column name (e.g. "\u5DF2\u7ECF\u9A8C\u6536", "Done", "In Progress"). If provided, tool resolves matching transition automatically.'}},required:["issueKey"]}}]};var W={new:"todo",indeterminate:"in_progress",done:"done"},Q=/\b(blocked|on[\s-]?hold|waiting|stuck)\b/i;function T(s){if(!s)return"unknown";if(s.name&&Q.test(s.name))return"blocked";let r=s.statusCategory?.key;return W[r]||"unknown"}function J(s){if(!s)return"";if(Array.isArray(s))return s.map(J).join("");if(s.type==="text")return s.text||"";if(s.type==="hardBreak"||s.type==="rule")return`
70
+ `;let r=s.content?J(s.content):"";return/^(paragraph|heading|listItem|blockquote|codeBlock|panel)$/.test(s.type)&&(r+=`
71
+ `),r}function O(s,r){let e=s.fields||{},t=e.status||null,a=null;if(s.self&&s.key)try{a=`${new URL(s.self).origin}/browse/${s.key}`}catch{a=null}return!a&&r&&s.key&&(a=`${String(r).replace(/\/+$/,"")}/browse/${s.key}`),{id:String(s.id??s.key??""),key:s.key,title:e.summary||"",body:typeof e.description=="string"?e.description:J(e.description?.content).trim(),state:t?.name||null,stateCategory:T(t),assignee:e.assignee?.displayName||null,url:a,_raw:s}}function R(s){let r=JSON.parse(s);if(r&&r.error)throw new Error(r.error);return r}var V={id:"jira",toStateCategory:T,toNeutral:O,async listCandidates(s={}){let r=s.query||"";if(!r.trim()){let i=[];if(s.state&&i.push(`status = "${s.state}"`),s.labels){let c=Array.isArray(s.labels)?s.labels:[s.labels];for(let n of c)i.push(`labels = "${n}"`)}s.updatedAfter&&i.push(`updated >= "${s.updatedAfter}"`),r=i.length?`${i.join(" AND ")} ORDER BY updated DESC`:"ORDER BY updated DESC"}let e=Number(s.limit)||30,t=`jql=${encodeURIComponent(r)}&maxResults=${e}&fields=summary,description,status,assignee,labels,project,updated,created`;return((await y(`/rest/api/3/search/jql?${t}`)).issues||[]).map(i=>O(i))},async getTicket(s){if(!s)throw new Error("key is required");let r=await y(`/rest/api/3/issue/${encodeURIComponent(s)}`);return!r||!r.key?null:O(r)},async getComments(s){if(!s)throw new Error("key is required");return(R(await S.handleToolCall("jira_get_comments",{issueKey:s})).comments||[]).map(e=>({id:String(e.id),author:e.author||"Unknown",body:e.body||"",createdAt:e.created||null,updatedAt:e.updated||null,_raw:e}))},async addComment(s,r){if(!s||!r)throw new Error("key and body are required");return{ok:!!R(await S.handleToolCall("jira_add_comment",{issueKey:s,body:r})).ok,id:null}},async transition(s,r){if(!s)throw new Error("key is required");let e=JSON.parse(await S.handleToolCall("jira_transition_issue",{issueKey:s,toStatus:r}));if(!e.ok)return{ok:!1,error:e.error||"transition failed",_raw:e};let t=e.statusAfter||null;return{ok:!0,stateAfter:t,stateCategoryAfter:T(t?{name:t}:null),_raw:e}},async linkPullRequest(s,r,e){if(!s||!r)throw new Error("key and prUrl are required");try{return await y(`/rest/api/3/issue/${encodeURIComponent(s)}/remotelink`,{method:"POST",body:{object:{url:r,title:e||r,icon:{url16x16:"https://github.com/favicon.ico",title:"GitHub"}}}}),{ok:!0,via:"remotelink"}}catch(t){let a=`${e?`${e}: `:"Linked PR: "}${r}`;return{ok:!!R(await S.handleToolCall("jira_add_comment",{issueKey:s,body:a})).ok,via:"comment",error:String(t?.message||t)}}}},ne=V;export{ne as default,V as jiraAdapter};
@@ -18,7 +18,7 @@ Zibby ships a set of **ready-made agents** — production-ready automations you
18
18
  │ │ uses the same primitives
19
19
  │ │
20
20
  │ ┌──────────────────────┐
21
- │ Marketplace ────►│ bug-autofix │ ◄── Ticket fix PR writeback
21
+ │ Marketplace ────►│ board-runner │ ◄── A whole dev team on a ticket board
22
22
  │ agents built │ github-ai-scout │ ◄── Daily AI-project radar
23
23
  │ on top of the │ pipeline-supervisor │ ◄── Zibby managing Zibby
24
24
  │ same platform │ sentry-triage │ ◄── Incident routing
@@ -32,21 +32,21 @@ You don't have to use the marketplace. You can build whatever agent you want wit
32
32
 
33
33
  | Agent | What it does | Best for |
34
34
  |---|---|---|
35
- | [Bug-autofix](./bug-autofix) | Polls a tracker, triages each bug, opens a fix PR for autofixable ones, writes the result back. Chains three reusable building-block agents (`ticket-triage` `code-fix` → `tracker-writeback`). | End-to-end bug SDLC, automated remediation |
35
+ | MAGNUM (`board-runner`) | A project manager that runs a ticket board on a schedule: triage, a developer and a frontend developer that open PRs, a reviewer, a tester and a product owner — each a reusable building-block agent dispatched as a sub-graph. | Running a team from a ticket board, PRD PR |
36
36
  | [GitHub AI scout](./github-ai-scout) | Daily scan of new/trending AI projects on GitHub, LLM-scored against your rubric, shortlist posted to Slack | Tracking a fast-moving space without manual triage |
37
37
  | [Pipeline supervisor](./pipeline-supervisor) | Watches the project's *other* agents, flags failing/slow ones, posts an improvement proposal to Slack/Lark | Zibby managing Zibby — agent fleet health |
38
38
  | [Sentry triage](./sentry-triage) | Hourly: fetch unresolved Sentry issues, classify by severity, route via Slack/Lark (author DM + usergroup mention) | Automated incident routing without a human triager |
39
39
  | [`zibby test`](./test) | Drives a browser via Cursor or Claude, runs assertions, generates a Playwright script + verification video | E2E test generation from plain-English specs |
40
40
 
41
- Plus reusable **building-block agents** that the orchestrators compose via sub-graph dispatch — `ticket-triage`, `code-fix`, `tracker-writeback`, `notify-slack`, `notify-lark`, `notify-notion`. Each is independently deployable and usable on its own.
41
+ Plus reusable **building-block agents** that the orchestrators compose via sub-graph dispatch — `ticket-triage`, `developer`, `tracker-writeback`, `notify-slack`, `notify-lark`, `notify-notion`. Each is independently deployable and usable on its own.
42
42
 
43
43
  ## Why a marketplace
44
44
 
45
45
  Three reasons we ship ready-made agents alongside the platform:
46
46
 
47
- 1. **Proof of concept** — every marketplace agent IS a Zibby agent. If `bug-autofix` works, the platform works. You can see the actual graph definition and adapt it.
47
+ 1. **Proof of concept** — every marketplace agent IS a Zibby agent. If MAGNUM works, the platform works. You can see the actual graph definition and adapt it.
48
48
  2. **Faster onboarding** — you don't need to design a full graph on day one. Deploy an agent, see the output, then build your own.
49
- 3. **Composable** — the orchestrators (`bug-autofix`) are built from smaller building-block agents dispatched as sub-graphs, so you can reuse the pieces in your own agents.
49
+ 3. **Composable** — the orchestrators (MAGNUM) are built from smaller building-block agents dispatched as sub-graphs, so you can reuse the pieces in your own agents.
50
50
 
51
51
  ## Building your own agent
52
52
 
@@ -63,6 +63,5 @@ If it's broadly useful, we may pull it into the official marketplace. Open an is
63
63
 
64
64
  ## Next
65
65
 
66
- - **[Bug-autofix agent](./bug-autofix)** — the flagship orchestrator, walked through end-to-end
67
66
  - **[Build your own agent](../get-started/your-first-workflow)** — scaffold and customize
68
67
  - **[Concepts: graph](../concepts/graph)** — the primitives every agent is built on
@@ -0,0 +1,101 @@
1
+ ---
2
+ sidebar_position: 11
3
+ title: Catalog metadata
4
+ ---
5
+
6
+ # Catalog metadata
7
+
8
+ Everything here reaches the **card**. Most of it is presentation, and most of it
9
+ is deliberately excluded from the update signal, so improving your copy never
10
+ pops an "Update available" badge on somebody's deployed agent for nothing.
11
+
12
+ ## Identity and presentation
13
+
14
+ | Name | Type | Required | Allowed values | Default | Update behavior |
15
+ |---|---|---|---|---|---|
16
+ | `slug` | string | **Yes** | the agent's identity — the folder name, the deploy route, the row of everybody who already installed it | — | **Never change it** |
17
+ | `tagline` | string | No | one clamped line — on the detail card this *is* the header | `''` | Card only |
18
+ | `tags` | string[] | No | catalog sections; the card is grouped by the first one in section order | `[]` | Card only |
19
+ | `browseRank` | number | No | lower sorts first; no rank sorts last | none | Card only |
20
+ | `capabilities` | string[] | No | around five bullets, each one thing, each starting with a verb | `[]` | Card only |
21
+ | `conversationStarters` | string[] | No | example prompts | `[]` | Card only |
22
+ | `icon` | string | No | `./icon.png` beside your graph, or an `https://` URL | the Zibby mark | Card only |
23
+ | `iconPrompt` / `iconBackground` | string | No | inputs to the offline icon generator | none | Card only |
24
+ | `releaseNotes` | string | No | markdown — see below | `''` | Card only |
25
+ | `defaultSlug` | string | No | pre-fills the "Deploy as" field | the slug | Seeded once |
26
+
27
+ :::tip Tagline vs description
28
+ The detail card reads **tagline first**; the browse cards and the featured hero
29
+ read **description first**. So keep the tagline to a few words and let the
30
+ description be two real sentences. They are not interchangeable.
31
+ :::
32
+
33
+ ## Behaviour and availability
34
+
35
+ | Name | Type | Required | Allowed values | Default | Update behavior |
36
+ |---|---|---|---|---|---|
37
+ | `maxRuntimeMinutes` | number | No | clamped to the platform window; the default is 25 and the ceiling 60 | none | **Seeded once** — a number you set on the Settings tab survives every Update |
38
+ | `selfHostOnly` | boolean | No | `true` when the agent needs a service container | `false` | Re-applied |
39
+ | `hidden` | boolean | No | `true` keeps the card out of browse while remaining installable by slug | `false` | Re-applied |
40
+ | `kind` | string | No | `fleet`, `app` — see [Composition](./composition.md) | none | Re-applied |
41
+ | `deps` | object | No | extra runtime dependencies the agent needs beyond `@zibby/core` | `{}` | **Frozen at deploy** |
42
+
43
+ ### `maxRuntimeMinutes` — measure it, don't guess
44
+
45
+ Declare it when you actually know your agent's cost. One shipped agent measured
46
+ a first pass at 24 minutes 53 seconds against a 25-minute default, which made
47
+ its repair pass unreachable on every fresh install — so it declares 60. Another
48
+ declares 30 because a cap that is too *low* gets the container killed between
49
+ two steps and strands work.
50
+
51
+ A cap that is too low is worse than one that is too high.
52
+
53
+ ### `selfHostOnly` means exactly one thing
54
+
55
+ **"This agent needs a service container."** It does not mean "absent from the
56
+ cloud": the card still ships to the cloud catalog so its documentation stays
57
+ readable everywhere. Only the deploy is refused, and the refusal says why.
58
+
59
+ ## Release notes
60
+
61
+ `releaseNotes` is what the "Update available" dialog shows somebody who already
62
+ runs your agent. It is **mandatory for any user-visible change**, and it is
63
+ short.
64
+
65
+ 1. **This version only.** One line per change.
66
+ 2. **The outcome, not the mechanism.** What is different for them, in their
67
+ words — not what you refactored.
68
+ 3. **Do not accumulate history.** Drop the previous version's notes rather than
69
+ prepending to them.
70
+ 4. **Ten short lines at most.**
71
+
72
+ Good:
73
+
74
+ ```
75
+ Answering the team's question is now just replying, once, like a person.
76
+ • Reply in your own words — any wording, any language.
77
+ • Answer on one ticket of a plan and it counts for the whole plan.
78
+ • You no longer touch labels.
79
+ ```
80
+
81
+ Bad: a changelog going back to the initial release, or a paragraph about which
82
+ module was rewritten.
83
+
84
+ No notes at all gives the user *"No release notes provided by the author"*.
85
+
86
+ ## Gotchas
87
+
88
+ **`hidden` and `selfHostOnly` are different axes.** `hidden` is visibility;
89
+ `selfHostOnly` is a real constraint. A card can be one, the other, or both.
90
+
91
+ **A `kind` the platform does not recognise is dropped.** The section you meant
92
+ to appear in simply never appears — check the allowed values.
93
+
94
+ **Your `spec` block lives outside your template directory**, so a
95
+ metadata-only change moves no source file. The platform tracks it separately, so
96
+ your edit still reaches the card — see [Update behavior](./update-behavior.md).
97
+
98
+ ## See also
99
+
100
+ - [Update behavior](./update-behavior.md)
101
+ - [The declaration model](./index.md)
@@ -0,0 +1,97 @@
1
+ ---
2
+ sidebar_position: 5
3
+ title: Composition & fleets
4
+ ---
5
+
6
+ # Composition — `dispatchesWorkflow` and `kind`
7
+
8
+ ## What it is
9
+
10
+ An agent can run **another** catalog agent as a sub-graph. Deploying the parent
11
+ cascades: every declared member is installed as the parent's own child, bound to
12
+ it, and recorded on its row — so one Deploy gives you a whole team.
13
+
14
+ ## Syntax — three forms
15
+
16
+ **1. The declarative sub-graph node.** The engine records the member for you:
17
+
18
+ ```js
19
+ graph.addNode('build', {
20
+ workflow: 'frontend-specialist',
21
+ input: (state) => ({ ticket: state.ticket }),
22
+ output: (result) => ({ pr: result.prUrl }),
23
+ });
24
+ ```
25
+
26
+ **2. A single member on a custom node:**
27
+
28
+ ```js
29
+ export const poPlanNode = {
30
+ name: 'plan',
31
+ dispatchesWorkflow: 'product-owner',
32
+ execute: async (state, ctx) => { /* … */ },
33
+ };
34
+ ```
35
+
36
+ **3. A roster — one node fanning out over many members:**
37
+
38
+ ```js
39
+ export const dispatchNode = {
40
+ name: 'dispatch',
41
+ dispatchesWorkflow: ['ticket-triage', 'developer', 'frontend-specialist',
42
+ 'gitlab-code-review', 'generate-test-cases', 'product-owner'],
43
+ execute: async (state, ctx) => { /* dispatches whichever the manager chose */ },
44
+ };
45
+ ```
46
+
47
+ ## Properties
48
+
49
+ | Name | Where | Type | Required | Allowed values | Default | Update behavior |
50
+ |---|---|---|---|---|---|---|
51
+ | `dispatchesWorkflow` | node | string \| string[] | No | catalog slugs | absent | Re-applied — a new member is installed on the next deploy |
52
+ | `kind` | `spec` | string | No | `fleet`, `app` | none | `fleet` is card-only; `app` changes the deploy path |
53
+ | `composedOf` | — | — | **derived, never declared** | — | — | Re-applied |
54
+
55
+ `kind: 'fleet'` moves the card into the Fleet section of the catalog and lets it
56
+ say "installs a team of N" *before* you install it. It does **not** change how
57
+ the agent deploys.
58
+
59
+ `kind: 'app'` is different in kind: the card is a hosted application you open in
60
+ a browser, not an agent, and it deploys down a different path.
61
+
62
+ ## What deploy does
63
+
64
+ 1. Builds one ordered member list — sub-graph children first, then anything
65
+ named by [`requires`](./requires.md).
66
+ 2. Installs each member (recursively), in parallel.
67
+ 3. Records the bindings on the parent, so a dispatch at run time resolves to the
68
+ right instance.
69
+ 4. Records the member names on the parent's row for the Agents list.
70
+
71
+ If any member fails, **the whole deploy fails** and the rows this deploy created
72
+ are rolled back. You never end up with half a fleet.
73
+
74
+ ## Gotchas
75
+
76
+ **A member can be a string or an array — always handle both.** A roster node
77
+ declares many members on one node; reading only the string form installs one and
78
+ leaves every other dispatch failing at run time.
79
+
80
+ **An agent cannot dispatch an ancestor.** A cycle is refused at deploy.
81
+
82
+ **A dispatch node runs no model of its own** and is excluded from "apply this
83
+ model to every node".
84
+
85
+ **A parent collects its children's model keys.** If your child runs a different
86
+ vendor, the deploy asks for that key too — and marks it non-blocking, so a
87
+ branch you will not use never stops the install.
88
+
89
+ **A member that subscribes to events is suppressed while it is a member.**
90
+ Otherwise one pull request would fire both the fleet (which runs the member
91
+ in-process) and the standalone member — a double review. Deploy the member
92
+ on its own and it starts responding to events again.
93
+
94
+ ## See also
95
+
96
+ - [`requires`](./requires.md) — the other kind of member.
97
+ - [Deploy-time configuration](./deploy-time-config.md) — event subscriptions.