@zibby/skills 0.2.23 → 0.2.24

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 (92) hide show
  1. package/dist/browser.d.ts +0 -17
  2. package/dist/browser.js +3 -3
  3. package/dist/code-scan.d.ts +1 -1
  4. package/dist/index.d.ts +0 -1
  5. package/dist/index.js +97 -97
  6. package/dist/jira.d.ts +75 -9
  7. package/dist/jira.js +4 -4
  8. package/dist/package.json +1 -1
  9. package/dist/report.d.ts +51 -276
  10. package/dist/trackers/index.js +12 -12
  11. package/dist/trackers/jira-adapter.js +11 -11
  12. package/package.json +1 -1
  13. package/docs/analysis.md +0 -109
  14. package/docs/apps/agent-ops.md +0 -130
  15. package/docs/apps/auth.md +0 -158
  16. package/docs/apps/deploy.md +0 -207
  17. package/docs/apps/goal-mode.md +0 -175
  18. package/docs/apps/index.md +0 -149
  19. package/docs/apps/managing.md +0 -121
  20. package/docs/cli-reference.md +0 -494
  21. package/docs/cloning-repositories.md +0 -285
  22. package/docs/cloud/bundles.md +0 -92
  23. package/docs/cloud/dedicated-egress.md +0 -140
  24. package/docs/cloud/editing-prompts.md +0 -79
  25. package/docs/cloud/env-vars.md +0 -144
  26. package/docs/cloud/limits.md +0 -81
  27. package/docs/cloud/logs.md +0 -104
  28. package/docs/cloud/triggering.md +0 -128
  29. package/docs/concepts/agents.md +0 -112
  30. package/docs/concepts/designing-agents.md +0 -247
  31. package/docs/concepts/graph.md +0 -83
  32. package/docs/concepts/sessions.md +0 -70
  33. package/docs/concepts/skills.md +0 -86
  34. package/docs/concepts/state.md +0 -106
  35. package/docs/concepts/sub-graphs.md +0 -227
  36. package/docs/custom-workflows.md +0 -358
  37. package/docs/get-started/deploy.md +0 -75
  38. package/docs/get-started/install.md +0 -60
  39. package/docs/get-started/run-locally.md +0 -94
  40. package/docs/get-started/trigger-and-logs.md +0 -90
  41. package/docs/get-started/use-from-agents.md +0 -153
  42. package/docs/get-started/your-first-workflow.md +0 -69
  43. package/docs/getting-started.md +0 -108
  44. package/docs/installation.md +0 -127
  45. package/docs/integrations/github.md +0 -73
  46. package/docs/integrations/gitlab.md +0 -43
  47. package/docs/integrations/jira.md +0 -71
  48. package/docs/integrations/lark.md +0 -41
  49. package/docs/integrations/linear.md +0 -43
  50. package/docs/integrations/notion.md +0 -33
  51. package/docs/integrations/plane.md +0 -46
  52. package/docs/integrations/sentry.md +0 -42
  53. package/docs/integrations/slack.md +0 -33
  54. package/docs/intro.md +0 -86
  55. package/docs/legacy/test-automation.md +0 -111
  56. package/docs/packages/agent-workflow.md +0 -88
  57. package/docs/packages/cli.md +0 -73
  58. package/docs/packages/core.md +0 -72
  59. package/docs/packages/mcp-browser.md +0 -110
  60. package/docs/packages/mcp-cli.md +0 -176
  61. package/docs/packages/memory.md +0 -223
  62. package/docs/packages/skills.md +0 -218
  63. package/docs/packages/ui-memory.md +0 -245
  64. package/docs/recipes/bug-autofix.md +0 -85
  65. package/docs/recipes/github-ai-scout.md +0 -61
  66. package/docs/recipes/index.md +0 -68
  67. package/docs/recipes/pipeline-supervisor.md +0 -57
  68. package/docs/recipes/sentry-triage.md +0 -93
  69. package/docs/recipes/test.md +0 -190
  70. package/docs/reviewing-results.md +0 -114
  71. package/docs/running-tests.md +0 -134
  72. package/docs/self-host/backup-restore.md +0 -53
  73. package/docs/self-host/custom-sidecars.md +0 -136
  74. package/docs/self-host/index.md +0 -75
  75. package/docs/self-host/storage.md +0 -50
  76. package/docs/self-host/troubleshooting.md +0 -48
  77. package/docs/self-host/upgrade.md +0 -81
  78. package/docs/skills/browser.md +0 -97
  79. package/docs/skills/chat-memory.md +0 -122
  80. package/docs/skills/core-tools.md +0 -80
  81. package/docs/skills/function-skill.md +0 -93
  82. package/docs/skills/github.md +0 -91
  83. package/docs/skills/index.md +0 -46
  84. package/docs/skills/jira.md +0 -99
  85. package/docs/skills/lark.md +0 -85
  86. package/docs/skills/memory.md +0 -92
  87. package/docs/skills/sentry.md +0 -80
  88. package/docs/skills/slack.md +0 -89
  89. package/docs/tests/memory.md +0 -131
  90. package/docs/triggering-workflows.md +0 -552
  91. package/docs/workflow-artifact-layout-evaluation.md +0 -119
  92. package/docs/workflow.md +0 -558
@@ -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(o=>`- ${w(o.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((o,n)=>`${n+1}. ${w(o.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),o=a(t),n=0,c=0,l=0;for(let u of i.values())c+=u;for(let u of o.values())l+=u;for(let[u,d]of i.entries()){let m=o.get(u)||0;n+=Math.min(d,m)}return 2*n/Math.max(1,c+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 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)===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 o=["task","story","bug","improvement","epic"];for(let n of o){let c=a.find(l=>b(l.name)===n);if(c)return{requested:s||null,resolved:c,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}`),o=new Map;for(let n of i.issues||[])for(let c of n.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((n,c)=>{let l={active:0,future:1,closed:2},u=(l[n.state]??3)-(l[c.state]??3);return u!==0?u:String(c.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 o=String(e).trim(),n=a.find(l=>String(l.name||"").toLowerCase()===o.toLowerCase());if(n)return{sprint:n,selectedBy:"name-exact"};let c=a.map(l=>({s:l,score:I(o,l.name||"")})).sort((l,u)=>u.score-l.score);return c[0]&&c[0].score>=.5?{sprint:c[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 o=0;o<t;o++){try{let n=`project = ${r} AND key = ${s} AND sprint = ${e}`,c=`jql=${encodeURIComponent(n)}&maxResults=1&fields=key,status`,l=await y(`/rest/api/3/search/jql?${c}`);if(Number(l?.total||0)>0)return i.push({attempt:o+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:o+1,jql:!1,issueField:m}),m)return{ok:!0,method:"issue_field",traces:i}}catch(n){i.push({attempt:o+1,error:String(n?.message||n)})}o<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 o=await P(i,"active");if(!o.length)return{ok:!1,error:`No assignable active sprint found for project ${i}`};let{sprint:n,selectedBy:c}=G(o,{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: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(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:c,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(/\/+$/,""),o=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(!o)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}:${o}`).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),o=await fetch(a,{method:r.method||"GET",headers:i,body:r.body?JSON.stringify(r.body):void 0});if(!o.ok){let c=await o.text().catch(()=>"");throw new Error(`Jira API ${o.status}: ${c.slice(0,300)}`)}let n=await o.text().catch(()=>"");if(!n||!n.trim())return{};try{return JSON.parse(n)}catch{return{raw:n}}};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 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`),o=Array.isArray(i)?i:[],n=new Map;for(let l of o)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 c=[...n.values()].sort((l,u)=>String(l.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(i=>({id:i.id,name:i.name,category:i.statusCategory?.name||null})).sort((i,o)=>String(i.name).localeCompare(String(o.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(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: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:o,labels:n,assigneeId:c,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}]}]}),o&&(f.priority={name:o}),n?.length&&(f.labels=n),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 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:o}=r||{},n=await N({issueKey:e,projectKey:t,sprintId:a,sprintName:i,target:o||"current"});return JSON.stringify(n)}case"jira_move_issue_to_sprint":{let{issueKey:e,projectKey:t,sprintId:a,sprintName:i,target:o}=r||{},n=await N({issueKey:e,projectKey:t,sprintId:a,sprintName:i,target:o});return JSON.stringify(n)}case"jira_get_sprint_issues":{let{sprintName:e,sprintId:t,projectKey:a,status:i,maxResults:o}=r;if(!e&&!t)return JSON.stringify({error:"sprintName or sprintId is required"});let n=o||50,c=t?`sprint = ${t}`:`sprint = "${e}"`,l=a?`project = ${a} AND `:"",u=i?` AND status = "${i}"`:"",d=`${l}${c}${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`),o=(i.comments||[]).map(n=>{let c="";return n.body?.content&&(c=w(n.body.content)),{id:n.id,author:n.author?.displayName||"Unknown",body:c,created:n.created,updated:n.updated}});return JSON.stringify({count:o.length,total:i.total||o.length,comments:o})}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:o}=r;if(!e)return JSON.stringify({error:"issueKey is required"});let n=String(a||i||o||"").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 c=t;if(!c){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}))});c=g.id}await y(`/rest/api/3/issue/${e}/transitions`,{method:"POST",body:{transition:{id:c}}});let l=await y(`/rest/api/3/issue/${e}?fields=status`);return JSON.stringify({ok:!0,issueKey:e,transitionId:c,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 o=Array.isArray(s.labels)?s.labels:[s.labels];for(let n of o)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};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/skills",
3
- "version": "0.2.23",
3
+ "version": "0.2.24",
4
4
  "description": "Built-in skill definitions for the Zibby agent-workflow framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/docs/analysis.md DELETED
@@ -1,109 +0,0 @@
1
- ---
2
- sidebar_position: 4
3
- title: Running Analysis
4
- ---
5
-
6
- # Running Analysis
7
-
8
- Analysis is where Zibby's AI reads your Jira ticket, studies your codebase, and produces code changes and test cases.
9
-
10
- ## Starting an Analysis
11
-
12
- 1. Go to your project's **Tickets** page
13
- 2. Click on a ticket to open its details
14
- 3. Click **Analyze**
15
- 4. Optionally add **additional context** or select an AI model
16
- 5. The analysis runs in the cloud (AWS Fargate)
17
-
18
- ## Analysis Pipeline
19
-
20
- The AI runs through these steps automatically:
21
-
22
- ### 1. Setup
23
-
24
- - Clones your GitHub repository into a secure container
25
- - Creates a git baseline for tracking changes
26
-
27
- ### 2. Analyze Ticket
28
-
29
- The AI reads:
30
- - The Jira ticket summary and description
31
- - Acceptance criteria and requirements
32
- - Your codebase structure and existing code
33
-
34
- It produces a **structured analysis** with:
35
- - Understanding of the requirement
36
- - Affected files and components
37
- - Implementation approach
38
- - Edge cases and considerations
39
-
40
- If the ticket lacks sufficient detail, the analysis may stop here with a **"needs clarification"** status and explain what information is missing.
41
-
42
- ### 3. Generate Code
43
-
44
- Based on the analysis, the AI:
45
- - Writes or modifies code to implement the ticket
46
- - Captures all changes as a git diff
47
- - Lists affected files
48
-
49
- You can review the diff side-by-side in the dashboard and create a **Pull Request** directly.
50
-
51
- ### 4. Generate Test Cases
52
-
53
- The AI produces structured test cases including:
54
- - Test case title and description
55
- - Step-by-step instructions
56
- - Test data and credentials
57
- - Expected outcomes
58
- - Preconditions and postconditions
59
-
60
- ### 5. Finalize
61
-
62
- - Compiles the final report
63
- - Uploads all artifacts to the dashboard
64
- - Updates the execution status
65
-
66
- ## Custom Instructions
67
-
68
- You can add custom prompt instructions for each AI node in **Project Settings > Node Configuration**:
69
-
70
- - **analyze_ticket** — additional context about your architecture
71
- - **generate_code** — coding standards, framework preferences
72
- - **generate_test_cases** — test credentials, login flows, specific data to use
73
-
74
- For example, to ensure tests use specific credentials:
75
-
76
- ```
77
- Use test account for login:
78
- Email: test@example.com
79
- Password: TestPass123
80
- ```
81
-
82
- ## Analysis Statuses
83
-
84
- | Status | Meaning |
85
- |---|---|
86
- | **running** | Analysis is in progress |
87
- | **completed** | All steps finished successfully |
88
- | **failed** | A step encountered an error |
89
- | **blocked** | Ticket lacks information to proceed |
90
-
91
- ## Reviewing Analysis Results
92
-
93
- After completion, the analysis page shows:
94
-
95
- - **Pipeline progress** — status of each step with logs
96
- - **Code Changes** — side-by-side diff with file tree
97
- - **Test Cases** — editable test cases you can refine
98
- - **Create PR** — one-click pull request creation
99
- - **Suggested Changes** — AI suggestions for the ticket itself
100
-
101
- ## Running Tests from Analysis
102
-
103
- Once test cases are generated, you can run them locally using the CLI:
104
-
105
- ```bash
106
- zibby test --sources TC001,TC002 --execution <execution-id> --sync
107
- ```
108
-
109
- Or copy the full command from the **Code** button in the dashboard.
@@ -1,130 +0,0 @@
1
- ---
2
- sidebar_position: 5
3
- title: Agent operator
4
- ---
5
-
6
- # The agent-ops sidecar
7
-
8
- Every Zibby Managed App ships with **agent-ops**, an autonomous daemon sidecar that runs alongside the app container. It does what a human operator would do — checks health, restarts on crash, prunes disk, rolls upgrades — only it does it every hour, never sleeps, and never forgets to file a runbook.
9
-
10
- This is the structural difference between "deploy button on a VM" and **Zibby**.
11
-
12
- ## What it does
13
-
14
- | Task | Cadence | What happens |
15
- |---|---|---|
16
- | **Hourly health check** | every 60 min | HTTP probe + container state + EFS usage. Recorded as a structured run record. |
17
- | **Self-heal on OOM** | event-driven | Container exits with OOMKilled → agent-ops triggers an ECS restart, records the recovery. |
18
- | **Disk-pressure prune** | when EFS > 90% | Removes safe-to-delete caches (e.g. n8n execution history older than 30 days). Configurable. |
19
- | **Upgrade orchestration** | on schedule | When a new app version lands in the catalog, agent-ops can run the in-place upgrade on a cron you set. |
20
- | **Activity log** | every action | One row in the app's "Agent activity" tab, with structured fields you can grep / chart. |
21
-
22
- Every action lands in DynamoDB as an `app-runs` record — queryable by anything from an agent node to a Grafana dashboard.
23
-
24
- ## See it in action
25
-
26
- ```bash
27
- zibby app activity a1b2c3d4
28
- ```
29
-
30
- ```
31
- Time Action Status Duration Notes
32
- 14:00:01 hourly_health_check ok 1.2s
33
- 13:00:01 hourly_health_check ok 1.1s
34
- 12:04:38 oom_recovery ok 4.8s restarted container after OOMKilled
35
- 12:00:01 hourly_health_check warn 2.1s container restarting
36
- 11:00:01 hourly_health_check ok 1.3s
37
- 10:00:01 hourly_health_check ok 1.0s
38
- ```
39
-
40
- The dashboard's "Agent activity" tab shows the same records with extra context (HTTP status codes, container logs at failure time, recovery diff).
41
-
42
- ## Run records ≠ logs
43
-
44
- A run record is **structured metadata** about something agent-ops did:
45
-
46
- ```json
47
- {
48
- "instanceId": "a1b2c3d4",
49
- "runId": "01J9KZQF...",
50
- "action": "hourly_health_check",
51
- "status": "ok",
52
- "startedAt": "2026-05-30T14:00:01Z",
53
- "duration_ms": 1234,
54
- "httpStatus": 200,
55
- "containerState": "RUNNING"
56
- }
57
- ```
58
-
59
- Logs are unstructured text. Run records are queryable, chartable, and aggregatable — that's why the Agent activity tab can show a 30-day uptime % without you running grep.
60
-
61
- The CLI exposes them:
62
-
63
- ```bash
64
- zibby app activity a1b2c3d4 --since 7d
65
- zibby app activity a1b2c3d4 --action oom_recovery
66
- zibby app activity a1b2c3d4 --json | jq '.[] | select(.status == "warn")'
67
- ```
68
-
69
- ## Why a sidecar, not a centralized controller?
70
-
71
- Three properties only this shape gives you:
72
-
73
- 1. **No noisy-neighbor failure mode** — your instance's agent-ops can't be blocked by another instance's slow health check
74
- 2. **Per-instance customization without a central feature flag** — env vars (`AGENT_OPS_CHECK_INTERVAL_MIN=15`, `AGENT_OPS_PRUNE_THRESHOLD=80`) live on your instance and just work
75
- 3. **Egress identity matches the app** — outbound calls from agent-ops use the same ENI / NAT path as the app itself, so when the app has a dedicated egress IP, agent-ops's webhook callbacks come from the same IP
76
-
77
- ## Customize via env
78
-
79
- Per-instance agent-ops behavior is tunable via env vars (set on the app instance — Apps → ENV tab — or via `zibby app env set`):
80
-
81
- | Env var | Default | What it controls |
82
- |---|---|---|
83
- | `AGENT_OPS_CHECK_INTERVAL_MIN` | 60 | Minutes between hourly health checks |
84
- | `AGENT_OPS_PRUNE_THRESHOLD` | 90 | EFS usage % that triggers disk-pressure prune |
85
- | `AGENT_OPS_AUTO_UPGRADE` | `false` | If `true`, upgrade automatically when catalog publishes a new version |
86
- | `AGENT_OPS_NOTIFY_WEBHOOK` | — | URL to POST run records to (any HTTPS endpoint — your own backend, n8n, etc.) |
87
-
88
- `AGENT_OPS_NOTIFY_WEBHOOK` is how you wire agent-ops into your existing observability stack — fire every run record into your team's #ops Slack via an agent trigger, into Datadog via their webhook receiver, or into your own database.
89
-
90
- ## Hooking agent-ops into an agent
91
-
92
- The most powerful pattern: a Zibby agent that runs **on agent-ops events**.
93
-
94
- Example: when an `oom_recovery` fires, run an agent that pulls the container's last-100-lines, classifies the crash, and pages whoever owns this app:
95
-
96
- ```bash
97
- zibby app env set a1b2c3d4 \
98
- AGENT_OPS_NOTIFY_WEBHOOK=https://api-prod.zibby.app/v1/workflows/<wf-uuid>/trigger
99
- ```
100
-
101
- The agent receives the run record as `input`, can call back to `zibby app logs` / `zibby app status`, and decides what to do. Agent-ops + agents compose into a self-operating fleet — humans only get pinged for genuinely novel failure modes.
102
-
103
- ## Upgrade orchestration
104
-
105
- When you `zibby app upgrade <id>` manually, agent-ops watches the rollout and rolls back if the new task fails health checks twice in a row. With `AGENT_OPS_AUTO_UPGRADE=true` set, the upgrade fires on a cron (default: weekly, Sunday 04:00 UTC) — agent-ops runs the same flow:
106
-
107
- 1. Register new task definition revision (catalog's latest)
108
- 2. Update service, watch the rollout
109
- 3. If 2 consecutive health checks pass on the new revision → keep it
110
- 4. If 2 fail → roll back to the previous revision, log a `failed_upgrade` run record
111
-
112
- The activity log shows the full attempted upgrade timeline so you can see why a rollback happened.
113
-
114
- ## How goal-mode deploys are supervised
115
-
116
- When you `zibby app deploy --goal "..."` (see [Goal-mode deploys](./goal-mode)), agent-ops switches into a different mode at task start: `AGENT_OPS_BOOTSTRAP_MODE=agent_script`. Instead of "run a known image and health-check it", it does:
117
-
118
- 1. **Plan** — Claude (Write+Read tools only, no Bash) writes a complete `/tmp/install.sh` from the customer's goal text + the house rules. ~2 turns, ~$0.05 in tokens.
119
- 2. **Supervise** — agent-ops execs the script in a process group, then every 30s sends Claude (text-only) a snapshot of stdout/stderr tail + proc status. Claude returns one JSON line: `continue`, `done`, or `intervene`. On `intervene`, agent-ops SIGTERMs the process group, gives it 5s grace, SIGKILLs, and replans with the previous failure context.
120
- 3. **Auto-short-circuit** — if the script exits with code 0 AND the verify port returns 2xx-499, agent-ops declares `done` without consulting the supervisor. This catches the false-positive `intervene` that fires when the planner backgrounds the app with nohup and Claude can't see "startup logs" in the snapshot.
121
-
122
- Hard caps: 5 iterations, 30 min wall-clock, $1.00 token budget — whichever hits first wins. Every iteration's script, supervisor turns, and final status are persisted under `/var/lib/agent-ops/agent_script-state/` on the per-instance EFS volume — `zibby app logs <id>` surfaces the timeline.
123
-
124
- ### House rules
125
-
126
- agent-ops reads `AGENT_OPS_BOOTSTRAP_SYSTEM_RULES` at task start and prepends it to the planner's system prompt. We curate ~6 rules in the backend covering things like: "Write COMPLETE bash scripts per turn, never run individual commands", "Never background installs; only the final long-running app process", "End with a curl health-check loop on the target port", "If install > 15 min, switch to a pre-built distribution". The rules are env-driven so we can iterate wording without rebuilding agent-ops — and it's why goal-mode deploys tend to converge reliably rather than oscillate between "almost done" and "container exited".
127
-
128
- You don't set these yourself; they're operator-curated. But they're the reason the planner consistently emits an install script that ends in a working `curl localhost:<port>` health check, not a half-finished session of individual commands.
129
-
130
- → Done with apps. See [Goal-mode deploys](./goal-mode), [Auth proxy](./auth), or [CLI Reference](../cli-reference#app-commands) for the full `zibby app` command surface.
package/docs/apps/auth.md DELETED
@@ -1,158 +0,0 @@
1
- ---
2
- sidebar_position: 4
3
- title: Auth proxy
4
- ---
5
-
6
- # Auth proxy
7
-
8
- Most self-hosted apps either ship with weak default credentials (`admin/admin` Grafana) or no app-level auth at all (n8n's free tier, raw webhook receivers). The auth proxy is a per-instance opt-in **Caddy sidecar** that fronts the ALB and validates a credential before letting the request reach the app.
9
-
10
- It's not a replacement for the app's own login system — it's a pre-filter. The app keeps its own session model; the proxy just makes sure the request never gets there without a valid credential.
11
-
12
- ## Architecture
13
-
14
- ```
15
- without auth:
16
- client → ALB → task:<appPort>
17
-
18
- with --auth-type basic:
19
- client → ALB → task:8888 (caddy)
20
- │ basic-auth check (bcrypt hash)
21
-
22
- localhost:<appPort> (app)
23
-
24
- with --auth-type token:
25
- client → ALB → task:8888 (caddy)
26
- │ Authorization: Bearer <token>
27
-
28
- localhost:<appPort> (app)
29
- ```
30
-
31
- The Caddy image is `caddy:2-alpine` direct (no custom ECR build). The entrypoint is an inline `sh -c` script that templates the Caddyfile from env at boot. The app keeps listening on its own internal port (unchanged); ALB just routes to Caddy instead.
32
-
33
- Cost: zero. Same Fargate task, +1 small container (~30 MB RAM, ~0 CPU).
34
-
35
- ## Storage
36
-
37
- - **Basic auth.** Password is bcrypt-hashed at the backend; the hash lives in the encrypted env bag. Plaintext is never persisted server-side.
38
- - **Token auth.** Token is KMS-encrypted at rest. Backend keeps the last 4 characters for audit (so `zibby app status` can show `Auth: token (…a3f9)` without leaking the secret). If the customer doesn't supply a token, the backend generates a 32-char URL-safe random one and returns it ONCE in the deploy response.
39
-
40
- You can rotate either at any time without redeploying the app (see `set-auth` below) — only the Caddy container restarts.
41
-
42
- ## Deploying with auth
43
-
44
- ### Basic auth
45
-
46
- ```bash
47
- zibby app deploy grafana \
48
- --project <project-id> \
49
- --name metrics \
50
- --auth-type basic \
51
- --auth-user admin \
52
- --auth-password 'S0me-long-passphrase!'
53
- ```
54
-
55
- `--auth-user` is printable ASCII (no spaces), 1-64 chars. `--auth-password` is 8-256 chars and can come from `ZIBBY_APP_AUTH_PASSWORD` env to keep it out of shell history.
56
-
57
- Verify:
58
-
59
- ```bash
60
- curl -I https://a1b2c3d4.apps.zibby.dev
61
- # HTTP/2 401 ← Caddy bounces unauthenticated request
62
-
63
- curl -I -u 'admin:S0me-long-passphrase!' https://a1b2c3d4.apps.zibby.dev
64
- # HTTP/2 302 ← Grafana redirects to /login (the app's own auth, behind the proxy)
65
- ```
66
-
67
- ### Token auth (auto-generated)
68
-
69
- ```bash
70
- zibby app deploy gotify --project <id> --name notify --auth-type token
71
- ```
72
-
73
- Response includes:
74
-
75
- ```
76
- ✔ Deployed (instanceId: f1e2d3c4)
77
- → Public URL: https://f1e2d3c4.apps.zibby.dev
78
- → Auth token: 7Kf3uL9pXmQ2vR8sT4nW6yE1bH5dG0aC
79
- ^^^ shown ONCE — save it now, you can't retrieve it
80
- ```
81
-
82
- Verify:
83
-
84
- ```bash
85
- curl -I https://f1e2d3c4.apps.zibby.dev
86
- # HTTP/2 401
87
-
88
- curl -I -H 'Authorization: Bearer 7Kf3uL9pXmQ2vR8sT4nW6yE1bH5dG0aC' \
89
- https://f1e2d3c4.apps.zibby.dev
90
- # HTTP/2 200
91
- ```
92
-
93
- ### Token auth (caller-supplied)
94
-
95
- If you already have a token (e.g. minted by your own auth system), pass it explicitly:
96
-
97
- ```bash
98
- zibby app deploy gotify \
99
- --project <id> --name notify \
100
- --auth-type token \
101
- --auth-token "$(cat ~/.secrets/gotify-bearer.txt)"
102
- ```
103
-
104
- Also accepts `ZIBBY_APP_AUTH_TOKEN` env.
105
-
106
- ### No auth (the default)
107
-
108
- ```bash
109
- zibby app deploy grafana --project <id> --name metrics
110
- # No Caddy container; ALB routes straight to the app's port.
111
- ```
112
-
113
- ## Changing auth after deploy
114
-
115
- `zibby app set-auth <instanceId>` is the rotate/replace endpoint. It has the same auth flags as `deploy`, with PATCH semantics — omitted flags preserve current state.
116
-
117
- ### Rotate just the password
118
-
119
- ```bash
120
- zibby app set-auth a1b2c3d4 --auth-password 'N3w-passphrase-2026!'
121
- ```
122
-
123
- Only the password changes — auth type and username are preserved. The ECS task rolls (~60-90s) to pick up the new bcrypt hash; the app container keeps its data.
124
-
125
- ### Switch from basic to token
126
-
127
- ```bash
128
- zibby app set-auth a1b2c3d4 --auth-type token --auth-token 'mY-shared-bearer-token'
129
- ```
130
-
131
- ### Switch from no-auth to basic
132
-
133
- ```bash
134
- zibby app set-auth a1b2c3d4 \
135
- --auth-type basic \
136
- --auth-user admin \
137
- --auth-password 'S0me-long-passphrase!'
138
- ```
139
-
140
- This is the path most operators take: deploy quickly without auth, verify the app works, then put Caddy in front before exposing it to the world.
141
-
142
- ### Remove auth entirely
143
-
144
- ```bash
145
- zibby app set-auth a1b2c3d4 --off
146
- ```
147
-
148
- The Caddy container is stripped from the next task definition revision; the ALB target group flips back to the app's port. ~60-90s rolling replace.
149
-
150
- ## When to use which
151
-
152
- - **Basic auth** — humans hitting a dashboard (Grafana, Uptime Kuma, draw.io, OpenObserve). One shared credential the team knows.
153
- - **Token auth** — machines hitting an endpoint (webhook receivers, internal APIs, anything you're going to put in another tool's "header to send" box). Long random string, rotated on schedule.
154
- - **No auth** — only when the app has its own SSO already wired up (Authentik, Zitadel) and you actively want the open URL.
155
-
156
- The proxy doesn't replace per-user accounts inside the app — for that you still need the app's own auth (e.g. Grafana's user database, n8n's user table). Treat the proxy as a perimeter, not an identity system.
157
-
158
- → Next: [Goal-mode deploys](./goal-mode) or [Agent operator](./agent-ops)