@zibby/skills 0.1.7 → 0.1.9
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.
- package/dist/browser.js +2 -2
- package/dist/chat-memory.js +15 -15
- package/dist/core-tools.js +2 -2
- package/dist/function-skill.js +1 -1
- package/dist/git.js +2 -2
- package/dist/github.js +3 -3
- package/dist/index.js +646 -1
- package/dist/jira.js +6 -6
- package/dist/memory.js +4 -4
- package/dist/package.json +15 -10
- package/dist/sentry.js +2 -2
- package/dist/skill-installer.js +3 -3
- package/dist/slack.js +2 -2
- package/dist/test-runner.js +13 -13
- package/dist/workflow-builder.js +146 -82
- package/docs/analysis.md +109 -0
- package/docs/cli-reference.md +338 -0
- package/docs/cloning-repositories.md +285 -0
- package/docs/custom-workflows.md +358 -0
- package/docs/getting-started.md +108 -0
- package/docs/installation.md +127 -0
- package/docs/integrations/github.md +73 -0
- package/docs/integrations/jira.md +71 -0
- package/docs/intro.md +87 -0
- package/docs/packages/cli.md +238 -0
- package/docs/packages/core.md +256 -0
- package/docs/packages/mcp-browser.md +110 -0
- package/docs/packages/memory.md +223 -0
- package/docs/packages/skills.md +216 -0
- package/docs/reviewing-results.md +114 -0
- package/docs/running-tests.md +134 -0
- package/docs/triggering-workflows.md +552 -0
- package/docs/workflow-artifact-layout-evaluation.md +119 -0
- package/docs/workflow.md +558 -0
- package/package.json +6 -1
package/dist/jira.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import{createRequire as $}from"module";import{resolveIntegrationToken as q,clearTokenCache as R}from"@zibby/core/backend-client.js";
|
|
1
|
+
import{createRequire as $}from"module";import{resolveIntegrationToken as q,clearTokenCache as R}from"@zibby/core/backend-client.js";var K=$(import.meta.url);function T(){if(process.env.MCP_JIRA_PATH)return process.env.MCP_JIRA_PATH;try{return K.resolve("@zibby/mcp-jira/index.js")}catch{return null}}var C=new Set(["paragraph","heading","bulletList","orderedList","listItem","blockquote","codeBlock","rule","table","tableRow","tableCell","tableHeader","mediaSingle","panel"]);function x(c,a){if(!a||!a.length)return c;let e=c;for(let t of a)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 k(c,a=0){if(!Array.isArray(c))return"";let e=[];for(let t of c){if(t.type==="text"){e.push(x(t.text||"",t.marks));continue}if(t.type==="hardBreak"){e.push(`
|
|
2
2
|
`);continue}if(t.type==="rule"){e.push(`
|
|
3
3
|
---
|
|
4
|
-
`);continue}
|
|
4
|
+
`);continue}let i=t.content?k(t.content,a+1):"";if(t.type==="listItem")e.push(i);else if(t.type==="bulletList"){let r=(t.content||[]).map(o=>`- ${k(o.content||[],a+1).trim()}`);e.push(`
|
|
5
5
|
${r.join(`
|
|
6
6
|
`)}
|
|
7
|
-
`)}else if(t.type==="orderedList"){
|
|
7
|
+
`)}else if(t.type==="orderedList"){let r=(t.content||[]).map((o,s)=>`${s+1}. ${k(o.content||[],a+1).trim()}`);e.push(`
|
|
8
8
|
${r.join(`
|
|
9
9
|
`)}
|
|
10
|
-
`)}else if(t.type==="heading"){
|
|
10
|
+
`)}else if(t.type==="heading"){let r=t.attrs?.level||2;e.push(`
|
|
11
11
|
|
|
12
12
|
${"#".repeat(r)} ${i.trim()}
|
|
13
13
|
|
|
@@ -16,7 +16,7 @@ ${"#".repeat(r)} ${i.trim()}
|
|
|
16
16
|
${i}
|
|
17
17
|
`):e.push(i)}return e.join("").replace(/\n{3,}/g,`
|
|
18
18
|
|
|
19
|
-
`)}function S(c){return String(c||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function I(c){return S(c).replace(/[a-z0-9]+/g,"")}function w(c,a){
|
|
19
|
+
`)}function S(c){return String(c||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function I(c){return S(c).replace(/[a-z0-9]+/g,"")}function w(c,a){let e=S(c),t=S(a);if(!e||!t)return 0;if(e===t)return 1;if(e.length===1||t.length===1)return e===t?1:0;let i=u=>{let d=new Map;for(let y=0;y<u.length-1;y++){let g=u.slice(y,y+2);d.set(g,(d.get(g)||0)+1)}return d},r=i(e),o=i(t),s=0,n=0,p=0;for(let u of r.values())n+=u;for(let u of o.values())p+=u;for(let[u,d]of r.entries()){let y=o.get(u)||0;s+=Math.min(d,y)}return 2*s/Math.max(1,n+p)}function b(c){return String(c||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_::"'`]/g,"")}function P(c,a=[]){let e=Array.isArray(a)?a:[];if(e.length===0)return{requested:c||null,resolved:null,strategy:"none"};let t=e.filter(s=>!s.subtask),i=t.length>0?t:e,r=b(c);if(r){let s=i.find(u=>b(u.name)===r);if(s)return{requested:c,resolved:s,strategy:"exact"};let n={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(n)){if(!u.some(y=>b(y)===r))continue;let d=i.find(y=>u.some(g=>b(g)===b(y.name)));if(d)return{requested:c,resolved:d,strategy:"alias"}}let p=i.map(u=>({t:u,score:w(c,u.name)})).sort((u,d)=>d.score-u.score);if(p[0]&&p[0].score>=.5)return{requested:c,resolved:p[0].t,strategy:"fuzzy"}}let o=["task","story","bug","improvement","epic"];for(let s of o){let n=i.find(p=>b(p.name)===s);if(n)return{requested:c||null,resolved:n,strategy:"default-preferred"}}return{requested:c||null,resolved:i[0],strategy:"default-first"}}async function N(c){let a=`projectKeys=${encodeURIComponent(c)}&expand=projects.issuetypes`,e=await f(`/rest/api/3/issue/createmeta?${a}`),t=Array.isArray(e?.projects)?e.projects:[],r=t.find(s=>String(s?.key||"").toUpperCase()===String(c||"").toUpperCase())||t[0]||null;return(Array.isArray(r?.issuetypes)?r.issuetypes:[]).map(s=>({id:s.id,name:s.name,subtask:!!s.subtask,description:s.description||null}))}async function A(c,a){if(!c)throw new Error("projectKey is required");let e="sprint is not EMPTY";a==="active"?e="sprint in openSprints()":a==="closed"?e="sprint in closedSprints()":a==="future"&&(e="sprint in futureSprints()");let t=`project = ${c} AND ${e} ORDER BY updated DESC`,i=`jql=${encodeURIComponent(t)}&maxResults=100&fields=customfield_10020`,r=await f(`/rest/api/3/search/jql?${i}`),o=new Map;for(let s of r.issues||[])for(let n of s.fields?.customfield_10020||[])n&&!o.has(n.id)&&o.set(n.id,{id:n.id,name:n.name,state:n.state,boardId:n.boardId||null,startDate:n.startDate||null,endDate:n.endDate||null,goal:n.goal||null});return[...o.values()].sort((s,n)=>{let p={active:0,future:1,closed:2},u=(p[s.state]??3)-(p[n.state]??3);return u!==0?u:String(n.startDate||"").localeCompare(String(s.startDate||""))})}function D(c,{sprintId:a,sprintName:e,target:t}={}){let i=Array.isArray(c)?c:[];if(!i.length)return{sprint:null,selectedBy:"none"};if(a!=null&&String(a).trim()!=="")return{sprint:i.find(s=>String(s.id)===String(a))||null,selectedBy:"id"};if(e&&String(e).trim()){let o=String(e).trim(),s=i.find(p=>String(p.name||"").toLowerCase()===o.toLowerCase());if(s)return{sprint:s,selectedBy:"name-exact"};let n=i.map(p=>({s:p,score:w(o,p.name||"")})).sort((p,u)=>u.score-p.score);return n[0]&&n[0].score>=.5?{sprint:n[0].s,selectedBy:"name-fuzzy"}:{sprint:null,selectedBy:"name-none"}}let r=String(t||"current").trim().toLowerCase();return r==="active"||r==="current"||r==="latest"?{sprint:i[0],selectedBy:r}:{sprint:i[0],selectedBy:"default"}}function L(c,a){let e=c?.fields?.customfield_10020;return Array.isArray(e)?e.some(t=>String(t?.id)===String(a)):!1}async function B({issueKey:c,projectKey:a,sprintId:e,attempts:t=3,delayMs:i=450}){let r=[];for(let o=0;o<t;o++){try{let s=`project = ${a} AND key = ${c} AND sprint = ${e}`,n=`jql=${encodeURIComponent(s)}&maxResults=1&fields=key,status`,p=await f(`/rest/api/3/search/jql?${n}`);if(Number(p?.total||0)>0)return r.push({attempt:o+1,jql:!0,issueField:null}),{ok:!0,method:"jql",traces:r};let d=await f(`/rest/api/3/issue/${c}?fields=customfield_10020,status`),y=L(d,e);if(r.push({attempt:o+1,jql:!1,issueField:y}),y)return{ok:!0,method:"issue_field",traces:r}}catch(s){r.push({attempt:o+1,error:String(s?.message||s)})}o<t-1&&await new Promise(s=>setTimeout(s,i))}return{ok:!1,method:"none",traces:r}}async function O({issueKey:c,projectKey:a,sprintId:e,sprintName:t,target:i}){if(!c)return{ok:!1,error:"issueKey is required"};let r=a;if(!r&&(r=(await f(`/rest/api/3/issue/${c}?fields=project`))?.fields?.project?.key||null,!r))return{ok:!1,error:`Could not resolve project for ${c}`};let o=await A(r,"active");if(!o.length)return{ok:!1,error:`No assignable active sprint found for project ${r}`};let{sprint:s,selectedBy:n}=D(o,{sprintId:e,sprintName:t,target:i});if(!s)return{ok:!1,error:`No matching sprint found in ${r}`,requested:{sprintId:e??null,sprintName:t??null,target:i??"current"},availableSprints:o.map(d=>({id:d.id,name:d.name,state:d.state}))};await f(`/rest/api/3/issue/${c}`,{method:"PUT",body:{fields:{customfield_10020:Number(s.id)}}});let p=await B({issueKey:c,projectKey:r,sprintId:s.id}),u=p.ok;return{ok:u,issueKey:c,projectKey:r,sprintId:s.id,sprintName:s.name,selectedBy:n,verifiedBy:p.method,verified:u,verificationTrace:p.traces,warning:u?null:`Sprint assignment attempted but verification did not find ${c} in sprint ${s.id}`}}async function f(c,a={}){let e=async()=>{let{token:t,cloudId:i}=await q("jira");if(typeof t!="string"||!t)throw new Error(`Invalid jira token type: ${typeof t}`);if(!i)throw new Error("Invalid jira cloudId: missing");let r=`https://api.atlassian.com/ex/jira/${i}${c}`,o=await fetch(r,{method:a.method||"GET",headers:{Authorization:`Bearer ${t}`,Accept:"application/json",...a.body?{"Content-Type":"application/json"}:{},...a.headers},body:a.body?JSON.stringify(a.body):void 0});if(!o.ok){let n=await o.text().catch(()=>"");throw new Error(`Jira API ${o.status}: ${n.slice(0,300)}`)}let s=await o.text().catch(()=>"");if(!s||!s.trim())return{};try{return JSON.parse(s)}catch{return{raw:s}}};try{return await e()}catch(t){let i=String(t?.message||t||"").toLowerCase();if(!(i.includes("token")||i.includes("401")||i.includes("403")||i.includes("substring")))throw t;return R("jira"),e()}}var M={id:"jira",serverName:"jira",allowedTools:["mcp__jira__*"],envKeys:["ATLASSIAN_ACCESS_TOKEN","ATLASSIAN_CLOUD_ID"],description:"Zibby Jira MCP Server (OAuth Bearer)",promptFragment:`## Jira (connected)
|
|
20
20
|
You have direct access to the user's Jira. Use these tools proactively:
|
|
21
21
|
|
|
22
22
|
### Issue tools
|
|
@@ -66,4 +66,4 @@ 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(){const c=T();if(!c)return null;const a={};for(const e of this.envKeys)process.env[e]&&(a[e]=process.env[e]);return process.env.ATLASSIAN_INSTANCE_URL&&(a.ATLASSIAN_INSTANCE_URL=process.env.ATLASSIAN_INSTANCE_URL),{command:"node",args:[c],env:a,description:this.description}},async handleToolCall(c,a){try{switch(c){case"jira_list_projects":{const e=await f("/rest/api/3/project"),t=(Array.isArray(e)?e:[]).map(i=>({id:i.id,key:i.key,name:i.name,style:i.style}));return JSON.stringify({count:t.length,projects:t})}case"jira_list_statuses":{const{projectKey:e}=a||{};if(e){const r=await f(`/rest/api/3/project/${encodeURIComponent(e)}/statuses`),o=Array.isArray(r)?r:[],s=new Map;for(const p of o)for(const u of p.statuses||[])u?.id&&(s.has(u.id)||s.set(u.id,{id:u.id,name:u.name,category:u.statusCategory?.name||null}));const n=[...s.values()].sort((p,u)=>String(p.name).localeCompare(String(u.name)));return JSON.stringify({scope:"project",projectKey:e,count:n.length,statuses:n})}const t=await f("/rest/api/3/status"),i=(Array.isArray(t)?t:[]).map(r=>({id:r.id,name:r.name,category:r.statusCategory?.name||null})).sort((r,o)=>String(r.name).localeCompare(String(o.name)));return JSON.stringify({scope:"global",count:i.length,statuses:i})}case"jira_list_issue_types":{const{projectKey:e}=a||{};if(!e)return JSON.stringify({error:"projectKey is required"});const t=await N(e);return JSON.stringify({projectKey:e,count:t.length,issueTypes:t})}case"jira_search":{let e=a.jql||"";const t=a.maxResults||20;e.replace(/\s*ORDER\s+BY\s+.*/i,"").trim()||(e=`created >= -365d ${e}`.trim());const r=`jql=${encodeURIComponent(e)}&maxResults=${t}&fields=summary,status,assignee,priority,updated,issuetype,project`,s=((await f(`/rest/api/3/search/jql?${r}`)).issues||[]).map(n=>({key:n.key,project:n.fields?.project?.key,summary:n.fields?.summary,status:n.fields?.status?.name,assignee:n.fields?.assignee?.displayName||"Unassigned",priority:n.fields?.priority?.name,type:n.fields?.issuetype?.name}));return JSON.stringify({count:s.length,issues:s})}case"jira_get_issue":{const e=a.issueKey;if(!e)return JSON.stringify({error:"issueKey is required"});const t=await f(`/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":{const{projectKey:e,summary:t,issueType:i,description:r,priority:o,labels:s,assigneeId:n,moveToSprint:p,moveToActiveSprint:u,sprintId:d,sprintName:y,target:g}=a;if(!e||!t)return JSON.stringify({error:"projectKey and summary are required"});let l={requested:i||null,resolved:null,strategy:"none"},j=[];try{j=await N(e),l=P(i,j)}catch{}const m={project:{key:e},summary:t,issuetype:l?.resolved?.id?{id:l.resolved.id}:{name:i||"Task"}};r&&(m.description={type:"doc",version:1,content:[{type:"paragraph",content:[{type:"text",text:r}]}]}),o&&(m.priority={name:o}),s?.length&&(m.labels=s),n&&(m.assignee={id:n});const _=await f("/rest/api/3/issue",{method:"POST",body:{fields:m}}),h={ok:!0,key:_.key,id:_.id,self:_.self};return l?.resolved&&(h.issueType=l.resolved.name,h.issueTypeResolution=l.strategy,l.strategy!=="exact"&&l.requested&&b(l.requested)!==b(l.resolved.name)&&(h.issueTypeWarning=`Requested "${l.requested}" is not available in ${e}; used "${l.resolved.name}" instead.`)),j.length>0&&(h.availableIssueTypes=j.map(v=>v.name)),(p||u)&&(h.sprintMove=await O({issueKey:_.key,projectKey:e,sprintId:d,sprintName:y,target:g})),JSON.stringify(h)}case"jira_list_sprints":{const{projectKey:e,state:t}=a,i=await A(e,t);return JSON.stringify({count:i.length,sprints:i})}case"jira_move_to_active_sprint":{const{issueKey:e,projectKey:t,sprintId:i,sprintName:r,target:o}=a||{},s=await O({issueKey:e,projectKey:t,sprintId:i,sprintName:r,target:o||"current"});return JSON.stringify(s)}case"jira_move_issue_to_sprint":{const{issueKey:e,projectKey:t,sprintId:i,sprintName:r,target:o}=a||{},s=await O({issueKey:e,projectKey:t,sprintId:i,sprintName:r,target:o});return JSON.stringify(s)}case"jira_get_sprint_issues":{const{sprintName:e,sprintId:t,projectKey:i,status:r,maxResults:o}=a;if(!e&&!t)return JSON.stringify({error:"sprintName or sprintId is required"});const s=o||50,n=t?`sprint = ${t}`:`sprint = "${e}"`,p=i?`project = ${i} AND `:"",u=r?` AND status = "${r}"`:"",d=`${p}${n}${u} ORDER BY status ASC, priority DESC`,y=`jql=${encodeURIComponent(d)}&maxResults=${s}&fields=summary,status,assignee,priority,issuetype,project`,g=await f(`/rest/api/3/search/jql?${y}`),l=(g.issues||[]).map(m=>({key:m.key,project:m.fields?.project?.key,summary:m.fields?.summary,status:m.fields?.status?.name,assignee:m.fields?.assignee?.displayName||"Unassigned",priority:m.fields?.priority?.name,type:m.fields?.issuetype?.name})),j={};for(const m of l)j[m.status]=(j[m.status]||0)+1;return JSON.stringify({count:l.length,total:g.total||l.length,statusCounts:j,issues:l})}case"jira_get_comments":{const{issueKey:e,maxResults:t}=a;if(!e)return JSON.stringify({error:"issueKey is required"});const r=await f(`/rest/api/3/issue/${e}/comment?maxResults=${t||50}&orderBy=-created`),o=(r.comments||[]).map(s=>{let n="";return s.body?.content&&(n=k(s.body.content)),{id:s.id,author:s.author?.displayName||"Unknown",body:n,created:s.created,updated:s.updated}});return JSON.stringify({count:o.length,total:r.total||o.length,comments:o})}case"jira_add_comment":{const{issueKey:e,body:t}=a;return!e||!t?JSON.stringify({error:"issueKey and body are required"}):(await f(`/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":{const{issueKey:e,fields:t}=a;return!e||!t?JSON.stringify({error:"issueKey and fields are required"}):(await f(`/rest/api/3/issue/${e}`,{method:"PUT",body:{fields:t}}),JSON.stringify({ok:!0,issueKey:e}))}case"jira_transition_issue":{const{issueKey:e,transitionId:t,toStatus:i,statusName:r,status:o}=a;if(!e)return JSON.stringify({error:"issueKey is required"});const s=String(i||r||o||"").trim();if(!t&&!s){const d=((await f(`/rest/api/3/issue/${e}/transitions`)).transitions||[]).map(y=>({id:y.id,name:y.name,to:y.to?.name}));return JSON.stringify({ok:!1,error:"transitionId or toStatus is required",issueKey:e,availableTransitions:d})}let n=t;if(!n){const d=(await f(`/rest/api/3/issue/${e}/transitions`)).transitions||[],y=S(s);let g=d.find(l=>S(l?.name||"")===y||S(l?.to?.name||"")===y);if(!g){const l=I(s);l.length>=2&&(g=d.find(j=>{const m=I(j?.name||""),_=I(j?.to?.name||""),h=m.length>=2&&(m.includes(l)||l.includes(m)),v=_.length>=2&&(_.includes(l)||l.includes(_));return h||v}))}if(!g){const l=d.map(h=>{const v=w(s,h?.name||""),J=w(s,h?.to?.name||"");return{t:h,score:Math.max(v,J)}}).sort((h,v)=>v.score-h.score),j=l[0],m=l[1];j&&j.score>=.45&&(!m||j.score-m.score>=.12)&&(g=j.t)}if(!g?.id)return JSON.stringify({ok:!1,error:`No transition matches target status: "${s}"`,issueKey:e,availableTransitions:d.map(l=>({id:l.id,name:l.name,to:l.to?.name}))});n=g.id}await f(`/rest/api/3/issue/${e}/transitions`,{method:"POST",body:{transition:{id:n}}});const p=await f(`/rest/api/3/issue/${e}?fields=status`);return JSON.stringify({ok:!0,issueKey:e,transitionId:n,statusAfter:p?.fields?.status?.name||null})}default:return JSON.stringify({error:`Unknown tool: ${c}`})}}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"]}}]};export{M as jiraSkill};
|
|
69
|
+
6. IMPORTANT: When target is clear, complete transition + verification in SAME turn. Do NOT stop after listing options.`,resolve(){let c=T();if(!c)return null;let a={};for(let e of this.envKeys)process.env[e]&&(a[e]=process.env[e]);return process.env.ATLASSIAN_INSTANCE_URL&&(a.ATLASSIAN_INSTANCE_URL=process.env.ATLASSIAN_INSTANCE_URL),{command:"node",args:[c],env:a,description:this.description}},async handleToolCall(c,a){try{switch(c){case"jira_list_projects":{let e=await f("/rest/api/3/project"),t=(Array.isArray(e)?e:[]).map(i=>({id:i.id,key:i.key,name:i.name,style:i.style}));return JSON.stringify({count:t.length,projects:t})}case"jira_list_statuses":{let{projectKey:e}=a||{};if(e){let r=await f(`/rest/api/3/project/${encodeURIComponent(e)}/statuses`),o=Array.isArray(r)?r:[],s=new Map;for(let p of o)for(let u of p.statuses||[])u?.id&&(s.has(u.id)||s.set(u.id,{id:u.id,name:u.name,category:u.statusCategory?.name||null}));let n=[...s.values()].sort((p,u)=>String(p.name).localeCompare(String(u.name)));return JSON.stringify({scope:"project",projectKey:e,count:n.length,statuses:n})}let t=await f("/rest/api/3/status"),i=(Array.isArray(t)?t:[]).map(r=>({id:r.id,name:r.name,category:r.statusCategory?.name||null})).sort((r,o)=>String(r.name).localeCompare(String(o.name)));return JSON.stringify({scope:"global",count:i.length,statuses:i})}case"jira_list_issue_types":{let{projectKey:e}=a||{};if(!e)return JSON.stringify({error:"projectKey is required"});let t=await N(e);return JSON.stringify({projectKey:e,count:t.length,issueTypes:t})}case"jira_search":{let e=a.jql||"",t=a.maxResults||20;e.replace(/\s*ORDER\s+BY\s+.*/i,"").trim()||(e=`created >= -365d ${e}`.trim());let r=`jql=${encodeURIComponent(e)}&maxResults=${t}&fields=summary,status,assignee,priority,updated,issuetype,project`,s=((await f(`/rest/api/3/search/jql?${r}`)).issues||[]).map(n=>({key:n.key,project:n.fields?.project?.key,summary:n.fields?.summary,status:n.fields?.status?.name,assignee:n.fields?.assignee?.displayName||"Unassigned",priority:n.fields?.priority?.name,type:n.fields?.issuetype?.name}));return JSON.stringify({count:s.length,issues:s})}case"jira_get_issue":{let e=a.issueKey;if(!e)return JSON.stringify({error:"issueKey is required"});let t=await f(`/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:i,description:r,priority:o,labels:s,assigneeId:n,moveToSprint:p,moveToActiveSprint:u,sprintId:d,sprintName:y,target:g}=a;if(!e||!t)return JSON.stringify({error:"projectKey and summary are required"});let l={requested:i||null,resolved:null,strategy:"none"},j=[];try{j=await N(e),l=P(i,j)}catch{}let m={project:{key:e},summary:t,issuetype:l?.resolved?.id?{id:l.resolved.id}:{name:i||"Task"}};r&&(m.description={type:"doc",version:1,content:[{type:"paragraph",content:[{type:"text",text:r}]}]}),o&&(m.priority={name:o}),s?.length&&(m.labels=s),n&&(m.assignee={id:n});let _=await f("/rest/api/3/issue",{method:"POST",body:{fields:m}}),h={ok:!0,key:_.key,id:_.id,self:_.self};return l?.resolved&&(h.issueType=l.resolved.name,h.issueTypeResolution=l.strategy,l.strategy!=="exact"&&l.requested&&b(l.requested)!==b(l.resolved.name)&&(h.issueTypeWarning=`Requested "${l.requested}" is not available in ${e}; used "${l.resolved.name}" instead.`)),j.length>0&&(h.availableIssueTypes=j.map(v=>v.name)),(p||u)&&(h.sprintMove=await O({issueKey:_.key,projectKey:e,sprintId:d,sprintName:y,target:g})),JSON.stringify(h)}case"jira_list_sprints":{let{projectKey:e,state:t}=a,i=await A(e,t);return JSON.stringify({count:i.length,sprints:i})}case"jira_move_to_active_sprint":{let{issueKey:e,projectKey:t,sprintId:i,sprintName:r,target:o}=a||{},s=await O({issueKey:e,projectKey:t,sprintId:i,sprintName:r,target:o||"current"});return JSON.stringify(s)}case"jira_move_issue_to_sprint":{let{issueKey:e,projectKey:t,sprintId:i,sprintName:r,target:o}=a||{},s=await O({issueKey:e,projectKey:t,sprintId:i,sprintName:r,target:o});return JSON.stringify(s)}case"jira_get_sprint_issues":{let{sprintName:e,sprintId:t,projectKey:i,status:r,maxResults:o}=a;if(!e&&!t)return JSON.stringify({error:"sprintName or sprintId is required"});let s=o||50,n=t?`sprint = ${t}`:`sprint = "${e}"`,p=i?`project = ${i} AND `:"",u=r?` AND status = "${r}"`:"",d=`${p}${n}${u} ORDER BY status ASC, priority DESC`,y=`jql=${encodeURIComponent(d)}&maxResults=${s}&fields=summary,status,assignee,priority,issuetype,project`,g=await f(`/rest/api/3/search/jql?${y}`),l=(g.issues||[]).map(m=>({key:m.key,project:m.fields?.project?.key,summary:m.fields?.summary,status:m.fields?.status?.name,assignee:m.fields?.assignee?.displayName||"Unassigned",priority:m.fields?.priority?.name,type:m.fields?.issuetype?.name})),j={};for(let m of l)j[m.status]=(j[m.status]||0)+1;return JSON.stringify({count:l.length,total:g.total||l.length,statusCounts:j,issues:l})}case"jira_get_comments":{let{issueKey:e,maxResults:t}=a;if(!e)return JSON.stringify({error:"issueKey is required"});let r=await f(`/rest/api/3/issue/${e}/comment?maxResults=${t||50}&orderBy=-created`),o=(r.comments||[]).map(s=>{let n="";return s.body?.content&&(n=k(s.body.content)),{id:s.id,author:s.author?.displayName||"Unknown",body:n,created:s.created,updated:s.updated}});return JSON.stringify({count:o.length,total:r.total||o.length,comments:o})}case"jira_add_comment":{let{issueKey:e,body:t}=a;return!e||!t?JSON.stringify({error:"issueKey and body are required"}):(await f(`/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}=a;return!e||!t?JSON.stringify({error:"issueKey and fields are required"}):(await f(`/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:i,statusName:r,status:o}=a;if(!e)return JSON.stringify({error:"issueKey is required"});let s=String(i||r||o||"").trim();if(!t&&!s){let d=((await f(`/rest/api/3/issue/${e}/transitions`)).transitions||[]).map(y=>({id:y.id,name:y.name,to:y.to?.name}));return JSON.stringify({ok:!1,error:"transitionId or toStatus is required",issueKey:e,availableTransitions:d})}let n=t;if(!n){let d=(await f(`/rest/api/3/issue/${e}/transitions`)).transitions||[],y=S(s),g=d.find(l=>S(l?.name||"")===y||S(l?.to?.name||"")===y);if(!g){let l=I(s);l.length>=2&&(g=d.find(j=>{let m=I(j?.name||""),_=I(j?.to?.name||""),h=m.length>=2&&(m.includes(l)||l.includes(m)),v=_.length>=2&&(_.includes(l)||l.includes(_));return h||v}))}if(!g){let l=d.map(h=>{let v=w(s,h?.name||""),J=w(s,h?.to?.name||"");return{t:h,score:Math.max(v,J)}}).sort((h,v)=>v.score-h.score),j=l[0],m=l[1];j&&j.score>=.45&&(!m||j.score-m.score>=.12)&&(g=j.t)}if(!g?.id)return JSON.stringify({ok:!1,error:`No transition matches target status: "${s}"`,issueKey:e,availableTransitions:d.map(l=>({id:l.id,name:l.name,to:l.to?.name}))});n=g.id}await f(`/rest/api/3/issue/${e}/transitions`,{method:"POST",body:{transition:{id:n}}});let p=await f(`/rest/api/3/issue/${e}?fields=status`);return JSON.stringify({ok:!0,issueKey:e,transitionId:n,statusAfter:p?.fields?.status?.name||null})}default:return JSON.stringify({error:`Unknown tool: ${c}`})}}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"]}}]};export{M as jiraSkill};
|
package/dist/memory.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createRequire as s}from"module";import{execFileSync as n}from"child_process";import{join as o}from"path";import{existsSync as a}from"fs";
|
|
1
|
+
import{createRequire as s}from"module";import{execFileSync as n}from"child_process";import{join as o}from"path";import{existsSync as a}from"fs";var c=s(import.meta.url);function m(){if(process.env.MCP_MEMORY_PATH)return process.env.MCP_MEMORY_PATH;try{return c.resolve("@zibby/memory/mcp-server")}catch{return null}}var d={id:"memory",serverName:"memory",allowedTools:["mcp__memory__*"],envKeys:[],description:"Zibby Memory MCP Server (test history, selectors, page model)",async middleware(){try{let{createMemoryMiddleware:e}=await import("@zibby/memory");return e()}catch{return null}},promptFragment:`BEFORE executing browser actions:
|
|
2
2
|
- Review any test memory/history above. Prefer selectors proven to work.
|
|
3
3
|
- If a previous run failed, avoid the same approach.
|
|
4
4
|
- After setup/login completes, navigate directly to the target page instead of clicking through menus.
|
|
@@ -10,13 +10,13 @@ DURING execution \u2014 when a selector fails and you switch to a fallback:
|
|
|
10
10
|
AFTER completing the test, you MUST call memory_save_insight at least once:
|
|
11
11
|
- Save any useful finding: reliable selectors, timing quirks, navigation patterns, workarounds.
|
|
12
12
|
- Category: selector_tip | timing | navigation | workaround | flaky | general
|
|
13
|
-
- Be specific \u2014 future runs will read your insights.`,resolve(){
|
|
13
|
+
- Be specific \u2014 future runs will read your insights.`,resolve(){let e=m();if(!e)throw new Error(`\u274C Memory MCP server not found
|
|
14
14
|
|
|
15
15
|
Install @zibby/memory:
|
|
16
|
-
npm install @zibby/memory`);
|
|
16
|
+
npm install @zibby/memory`);let r=o(process.cwd(),".zibby","memory");if(!a(o(r,".dolt")))throw new Error(`\u274C Memory database not initialized
|
|
17
17
|
|
|
18
18
|
Run:
|
|
19
|
-
zibby init --mem`);try{
|
|
19
|
+
zibby init --mem`);try{let t=n("dolt",["sql","-q","SELECT COUNT(*) AS cnt FROM test_runs","-r","json"],{cwd:r,encoding:"utf-8",timeout:5e3}),i=JSON.parse(t.trim()).rows||[];if(!i[0]||i[0].cnt===0)return console.log("[memory] Database empty \u2014 memory tools activate after first completed run"),null}catch(t){throw new Error(`\u274C Dolt not found or memory database error
|
|
20
20
|
|
|
21
21
|
Install Dolt:
|
|
22
22
|
https://docs.dolthub.com/introduction/installation
|
package/dist/package.json
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zibby/skills",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Built-in skill definitions for Zibby test automation framework",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "
|
|
6
|
+
"main": "dist/index.js",
|
|
7
7
|
"exports": {
|
|
8
|
-
".": "./
|
|
9
|
-
"./browser": "./
|
|
10
|
-
"./jira": "./
|
|
11
|
-
"./github": "./
|
|
12
|
-
"./slack": "./
|
|
13
|
-
"./memory": "./
|
|
14
|
-
"./function": "./
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./browser": "./dist/browser.js",
|
|
10
|
+
"./jira": "./dist/jira.js",
|
|
11
|
+
"./github": "./dist/github.js",
|
|
12
|
+
"./slack": "./dist/slack.js",
|
|
13
|
+
"./memory": "./dist/memory.js",
|
|
14
|
+
"./function": "./dist/function-skill.js"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "node ../scripts/build.mjs",
|
|
18
|
+
"prepack": "node scripts/copy-docs.mjs",
|
|
18
19
|
"lint": "eslint .",
|
|
19
20
|
"lint:fix": "eslint --fix ."
|
|
20
21
|
},
|
|
@@ -36,13 +37,17 @@
|
|
|
36
37
|
"url": "https://github.com/ZibbyHQ/zibby-agent/issues"
|
|
37
38
|
},
|
|
38
39
|
"files": [
|
|
39
|
-
"
|
|
40
|
+
"dist/",
|
|
41
|
+
"docs/",
|
|
40
42
|
"README.md",
|
|
41
43
|
"LICENSE"
|
|
42
44
|
],
|
|
43
45
|
"engines": {
|
|
44
46
|
"node": ">=18.0.0"
|
|
45
47
|
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@zibby/workflow": "^0.1.0"
|
|
50
|
+
},
|
|
46
51
|
"peerDependencies": {
|
|
47
52
|
"@zibby/core": ">=0.1.0"
|
|
48
53
|
},
|
package/dist/sentry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{resolveIntegrationToken as
|
|
1
|
+
import{resolveIntegrationToken as u}from"@zibby/core/backend-client.js";async function c(o,n={}){let{token:t,organizationSlug:s}=await u("sentry"),i=`https://sentry.io/api/0/organizations/${s}${o}`,e=await fetch(i,{method:n.method||"GET",headers:{Authorization:`Bearer ${t}`,"Content-Type":"application/json"}});if(!e.ok){let a=await e.text().catch(()=>"");throw new Error(`Sentry API ${e.status}: ${a.slice(0,300)}`)}return e.json()}var l={id:"sentry",description:"Sentry error tracking \u2014 projects, issues, events",envKeys:[],promptFragment:`## Sentry (connected)
|
|
2
2
|
You have access to the user's Sentry. Use these tools:
|
|
3
3
|
- sentry_list_projects: List projects in the organization
|
|
4
4
|
- sentry_list_issues: List errors/issues (supports query, project filter, sort)
|
|
5
|
-
- sentry_get_issue: Get detailed info about a specific issue`,resolve(){return null},async handleToolCall(o,n){try{switch(o){case"sentry_list_projects":{
|
|
5
|
+
- sentry_get_issue: Get detailed info about a specific issue`,resolve(){return null},async handleToolCall(o,n){try{switch(o){case"sentry_list_projects":{let t=await c("/projects/?per_page=50");return JSON.stringify({projects:t.map(s=>({slug:s.slug,name:s.name,platform:s.platform}))})}case"sentry_list_issues":{let t=n.project||"",s=n.query||"is:unresolved",i=n.sort||"date",e=`/issues/?query=${encodeURIComponent(s)}&sort=${i}&per_page=${n.limit||25}`;t&&(e+=`&project=${encodeURIComponent(t)}`);let a=await c(e);return JSON.stringify({issues:a.map(r=>({id:r.id,title:r.title,culprit:r.culprit,count:r.count,firstSeen:r.firstSeen,lastSeen:r.lastSeen,level:r.level,status:r.status}))})}case"sentry_get_issue":{let{issueId:t}=n;if(!t)return JSON.stringify({error:"issueId is required"});let{token:s}=await u("sentry"),i=await fetch(`https://sentry.io/api/0/issues/${t}/`,{headers:{Authorization:`Bearer ${s}`}});if(!i.ok)throw new Error(`Sentry API ${i.status}`);let e=await i.json();return JSON.stringify({id:e.id,title:e.title,culprit:e.culprit,metadata:e.metadata,count:e.count,userCount:e.userCount,firstSeen:e.firstSeen,lastSeen:e.lastSeen,level:e.level,status:e.status,project:{slug:e.project?.slug,name:e.project?.name}})}default:return JSON.stringify({error:`Unknown tool: ${o}`})}}catch(t){return JSON.stringify({error:t.message})}},tools:[{name:"sentry_list_projects",description:"List Sentry projects",input_schema:{type:"object",properties:{}}},{name:"sentry_list_issues",description:"List Sentry issues (errors)",input_schema:{type:"object",properties:{project:{type:"string",description:"Project slug (optional)"},query:{type:"string",description:"Sentry search query (default: is:unresolved)"},sort:{type:"string",description:"Sort order: date, new, priority, freq, user (default: date)"},limit:{type:"number",description:"Max issues to return (default 25)"}}}},{name:"sentry_get_issue",description:"Get details of a specific Sentry issue",input_schema:{type:"object",properties:{issueId:{type:"string",description:"Sentry issue ID"}},required:["issueId"]}}]};export{l as sentrySkill};
|
package/dist/skill-installer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{existsSync as m,readFileSync as b}from"fs";import{homedir as v}from"os";import{join as I}from"path";import{spawn as w}from"child_process";
|
|
1
|
+
import{existsSync as m,readFileSync as b}from"fs";import{homedir as v}from"os";import{join as I}from"path";import{spawn as w}from"child_process";var r={jira:{description:"Jira issue search, details, comments, transitions",integrationProvider:"jira",envKeys:[],setupInstructions:`To connect Jira:
|
|
2
2
|
1. Go to Settings \u2192 Integrations (https://studio.zibby.app/integrations)
|
|
3
3
|
2. Click "Connect Jira" and authorize via Atlassian OAuth
|
|
4
4
|
3. After OAuth completes, ask me to install Jira again`},github:{description:"GitHub issues, PRs, repository management",integrationProvider:"github",envKeys:[],setupInstructions:`To connect GitHub:
|
|
@@ -10,5 +10,5 @@ import{existsSync as m,readFileSync as b}from"fs";import{homedir as v}from"os";i
|
|
|
10
10
|
3. After OAuth completes, ask me to install Slack again`},sentry:{description:"Sentry error tracking \u2014 projects, issues, events",integrationProvider:"sentry",envKeys:[],setupInstructions:`To connect Sentry:
|
|
11
11
|
1. Go to Settings \u2192 Integrations (https://studio.zibby.app/integrations)
|
|
12
12
|
2. Click "Connect Sentry" and authorize
|
|
13
|
-
3. After OAuth completes, ask me to install Sentry again`},runner:{description:"Run zibby test workflows from chat (parallel supported)",envKeys:[],setupInstructions:"Ready to use. Runs zibby test workflows as background processes \u2014 each with its own browser and session."},browser:{description:"Playwright browser automation (navigate, click, fill, screenshot)",envKeys:[],setupInstructions:"Ready to use. Starts a Playwright browser for web automation."},memory:{description:"Test memory database (Dolt) \u2014 history, selectors, insights",envKeys:[],setupInstructions:"Ready to use. Requires Dolt (https://docs.dolthub.com/introduction/installation) and a memory DB via `zibby init --mem`."},"chat-memory":{description:"Persistent chat memory \u2014 remembers facts, decisions, and task history across sessions (Dolt-backed)",envKeys:[],setupInstructions:'Ready to use. Requires Dolt installed. Tables auto-create on first use. Install with: "add chat memory" or "install chat-memory".'},git:{description:"Clone and explore git repositories locally for codebase analysis",envKeys:[],setupInstructions:"Ready to use. Clone repos with git_checkout, explore with git_explore. Auto-authenticates with GitHub/GitLab tokens."}};function S(){
|
|
14
|
-
`)}function _(){if(process.env.ZIBBY_USER_TOKEN)return process.env.ZIBBY_USER_TOKEN;try{
|
|
13
|
+
3. After OAuth completes, ask me to install Sentry again`},runner:{description:"Run zibby test workflows from chat (parallel supported)",envKeys:[],setupInstructions:"Ready to use. Runs zibby test workflows as background processes \u2014 each with its own browser and session."},browser:{description:"Playwright browser automation (navigate, click, fill, screenshot)",envKeys:[],setupInstructions:"Ready to use. Starts a Playwright browser for web automation."},memory:{description:"Test memory database (Dolt) \u2014 history, selectors, insights",envKeys:[],setupInstructions:"Ready to use. Requires Dolt (https://docs.dolthub.com/introduction/installation) and a memory DB via `zibby init --mem`."},"chat-memory":{description:"Persistent chat memory \u2014 remembers facts, decisions, and task history across sessions (Dolt-backed)",envKeys:[],setupInstructions:'Ready to use. Requires Dolt installed. Tables auto-create on first use. Install with: "add chat memory" or "install chat-memory".'},git:{description:"Clone and explore git repositories locally for codebase analysis",envKeys:[],setupInstructions:"Ready to use. Clone repos with git_checkout, explore with git_explore. Auto-authenticates with GitHub/GitLab tokens."}};function S(){let t=["## Available Skills"];for(let[s,n]of Object.entries(r)){let o=n.integrationProvider?`integration: ${n.integrationProvider}`:"ready";t.push(`- ${s}: ${n.description} [${o}]`)}return t.push(""),t.push("Use the install_skill / uninstall_skill / list_available_skills tools to manage skills."),t.push(`Zibby third party Integration settings page: ${p()}`),t.push(""),t.push("## Tool-First Policy (mandatory)"),t.push("CRITICAL RULES \u2014 follow these strictly:"),t.push("1. When user asks to do something and a matching skill is available but not installed, IMMEDIATELY call install_skill. Never ask for credentials or confirmation first."),t.push(`2. If install_skill succeeds, the skill's tools are now available. Use them RIGHT AWAY in the same turn \u2014 don't just say "it's connected", actually call the tools.`),t.push("3. If install_skill reports needsIntegration, tell the user to connect via the integration URL and try again after."),t.push("4. When the relevant skill is already installed, use its tools directly \u2014 don't ask for IDs or keys. Each skill's own instructions explain the workflow."),t.push("5. If a task needs multiple skills (e.g. data from one + execution from another), install all of them, then follow each skill's workflow instructions."),t.join(`
|
|
14
|
+
`)}function _(){if(process.env.ZIBBY_USER_TOKEN)return process.env.ZIBBY_USER_TOKEN;try{let t=I(v(),".zibby","config.json");return m(t)&&JSON.parse(b(t,"utf-8")).sessionToken||null}catch{return null}}function O(){return(process.env.ZIBBY_API_URL||process.env.ZIBBY_PROD_API_URL||"https://api-prod.zibby.app").replace(/\/$/,"")}function p(){return`${(process.env.ZIBBY_FRONTEND_URL||process.env.ZIBBY_PROD_FRONTEND_URL||"https://studio.zibby.app").replace(/\/$/,"")}/integrations`}function P(t){try{let s=process.platform;return w(s==="darwin"?"open":s==="win32"?"cmd":"xdg-open",s==="win32"?["/c","start","",t]:[t],{detached:!0,stdio:"ignore"}).unref(),!0}catch{return!1}}async function R(){let t=_();if(!t)return{checked:!1,statuses:null,reason:"no-session-token"};try{let s=await fetch(`${O()}/integrations/status`,{method:"GET",headers:{Authorization:`Bearer ${t}`}});return s.ok?{checked:!0,statuses:await s.json()||{},reason:null}:{checked:!1,statuses:null,reason:`status-${s.status}`}}catch{return{checked:!1,statuses:null,reason:"network-error"}}}function g(t,s){if(!s||!t)return{connected:null};let n=t[s];return!n||typeof n.connected!="boolean"?{connected:null,details:n||null}:{connected:n.connected,details:n}}var j={id:"skill-installer",description:"Live skill installation for chat sessions",envKeys:[],catalog:r,promptFragment:S,tools:[{name:"install_skill",description:"Install a skill into the current chat session so its tools become available",input_schema:{type:"object",properties:{skillId:{type:"string",description:'Skill identifier to install (e.g. "jira", "github", "browser", "memory")'}},required:["skillId"]}},{name:"uninstall_skill",description:"Remove a skill from the current chat session",input_schema:{type:"object",properties:{skillId:{type:"string",description:"Skill identifier to remove"}},required:["skillId"]}},{name:"list_available_skills",description:"List all skills that can be installed, with their env-var readiness status",input_schema:{type:"object",properties:{}}}],async handleToolCall(t,s,n){let{activeSkills:o}=n,d=await R();if(t==="list_available_skills"){let e=Object.entries(r).map(([i,a])=>{let h=o.includes(i),c=g(d.statuses,a.integrationProvider);return{id:i,description:a.description,installed:h,integrationProvider:a.integrationProvider||void 0,integrationConnected:c.connected,setupInstructions:c.connected===!1?a.setupInstructions:void 0}});return JSON.stringify({skills:e})}if(t==="install_skill"){let{skillId:e}=s;if(!e)return JSON.stringify({ok:!1,error:"skillId is required"});if(o.includes(e)){let l=r[e],{getSkill:u}=await import("@zibby/workflow"),k=(u(e)?.tools||[]).map(y=>y.name);return JSON.stringify({ok:!0,alreadyInstalled:!0,skillId:e,description:l?.description,availableTools:k,integrationUrl:l?.integrationProvider?p():void 0,hint:`${e} is already active. Tools available: ${k.join(", ")}. Use them directly.`})}if(!r[e])return JSON.stringify({ok:!1,error:`Unknown skill "${e}". Available: ${Object.keys(r).join(", ")}`});let i=r[e];if(i.integrationProvider){let l=g(d.statuses,i.integrationProvider),u=p();if(d.checked&&l.connected===!1){let f=P(u);return JSON.stringify({ok:!1,error:`${i.integrationProvider} is not connected for this Zibby account yet`,needsIntegration:!0,integrationUrl:u,openedBrowser:f,setupInstructions:`Please connect ${i.integrationProvider} first at ${u}. After you finish OAuth, ask me to install ${e} again.`})}}o.push(e);let{getSkill:a}=await import("@zibby/workflow"),c=(a(e)?.tools||[]).map(l=>l.name);return JSON.stringify({ok:!0,installed:e,description:i.description,availableTools:c,hint:`${e} is now active. You now have these tools: ${c.join(", ")}. Use them immediately to help the user \u2014 don't just confirm installation.`})}if(t==="uninstall_skill"){let{skillId:e}=s;if(!e)return JSON.stringify({ok:!1,error:"skillId is required"});if(e==="skill-installer")return JSON.stringify({ok:!1,error:"Cannot uninstall the skill installer"});let i=o.indexOf(e);return i===-1?JSON.stringify({ok:!1,error:`${e} is not installed`}):(o.splice(i,1),JSON.stringify({ok:!0,uninstalled:e}))}return JSON.stringify({error:`Unknown tool: ${t}`})},resolve(){return null}};export{j as skillInstallerSkill};
|
package/dist/slack.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{resolveIntegrationToken as p}from"@zibby/core/backend-client.js";async function n(r,e={}){
|
|
1
|
+
import{resolveIntegrationToken as p}from"@zibby/core/backend-client.js";async function n(r,e={}){let{token:s}=await p("slack"),t=["conversations.list","users.list","users.profile.get","conversations.history","conversations.replies"].includes(r),i=`https://slack.com/api/${r}`,c={Authorization:`Bearer ${s}`},o;if(t){let l=new URLSearchParams(e).toString();l&&(i+=`?${l}`)}else c["Content-Type"]="application/json; charset=utf-8",o=JSON.stringify(e);let a=await(await fetch(i,{method:t?"GET":"POST",headers:c,body:o})).json();if(!a.ok)throw new Error(`Slack API error: ${a.error}`);return a}var _={id:"slack",serverName:"slack",allowedTools:["mcp__slack__*"],envKeys:["SLACK_BOT_TOKEN","SLACK_TEAM_ID"],description:"Slack MCP Server",promptFragment:`## Slack (connected)
|
|
2
2
|
You have access to the user's Slack workspace. Use these tools:
|
|
3
3
|
- slack_list_channels, slack_post_message, slack_reply_to_thread
|
|
4
4
|
- slack_add_reaction, slack_get_channel_history, slack_get_thread_replies
|
|
5
|
-
- slack_get_users, slack_get_user_profile`,resolve(){
|
|
5
|
+
- slack_get_users, slack_get_user_profile`,resolve(){let r={};for(let e of this.envKeys)process.env[e]&&(r[e]=process.env[e]);return{command:"npx",args:["-y","@modelcontextprotocol/server-slack@latest"],env:r}},async handleToolCall(r,e){try{switch(r){case"slack_list_channels":{let s=await n("conversations.list",{types:"public_channel",limit:100});return JSON.stringify({channels:(s.channels||[]).map(t=>({id:t.id,name:t.name,topic:t.topic?.value}))})}case"slack_post_message":{if(!e.channel||!e.text)return JSON.stringify({error:"channel and text are required"});let s=await n("chat.postMessage",{channel:e.channel,text:e.text});return JSON.stringify({ok:!0,ts:s.ts,channel:s.channel})}case"slack_reply_to_thread":{if(!e.channel||!e.thread_ts||!e.text)return JSON.stringify({error:"channel, thread_ts, and text are required"});let s=await n("chat.postMessage",{channel:e.channel,thread_ts:e.thread_ts,text:e.text});return JSON.stringify({ok:!0,ts:s.ts})}case"slack_add_reaction":return!e.channel||!e.timestamp||!e.reaction?JSON.stringify({error:"channel, timestamp, and reaction are required"}):(await n("reactions.add",{channel:e.channel,timestamp:e.timestamp,name:e.reaction}),JSON.stringify({ok:!0}));case"slack_get_channel_history":{if(!e.channel)return JSON.stringify({error:"channel is required"});let s=await n("conversations.history",{channel:e.channel,limit:e.limit||20});return JSON.stringify({messages:(s.messages||[]).map(t=>({user:t.user,text:t.text,ts:t.ts}))})}case"slack_get_thread_replies":{if(!e.channel||!e.thread_ts)return JSON.stringify({error:"channel and thread_ts are required"});let s=await n("conversations.replies",{channel:e.channel,ts:e.thread_ts});return JSON.stringify({messages:(s.messages||[]).map(t=>({user:t.user,text:t.text,ts:t.ts}))})}case"slack_get_users":{let s=await n("users.list",{limit:100});return JSON.stringify({users:(s.members||[]).filter(t=>!t.is_bot&&!t.deleted).map(t=>({id:t.id,name:t.real_name||t.name}))})}case"slack_get_user_profile":{if(!e.user_id)return JSON.stringify({error:"user_id is required"});let s=await n("users.profile.get",{user:e.user_id});return JSON.stringify({profile:s.profile})}default:return JSON.stringify({error:`Unknown tool: ${r}`})}}catch(s){return JSON.stringify({error:s.message})}},tools:[{name:"slack_list_channels",description:"List public channels in the workspace",input_schema:{type:"object",properties:{}}},{name:"slack_post_message",description:"Post a message to a Slack channel or DM",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID or name"},text:{type:"string",description:"Message text"}},required:["channel","text"]}},{name:"slack_reply_to_thread",description:"Reply to a specific message thread",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},thread_ts:{type:"string",description:"Thread timestamp"},text:{type:"string",description:"Reply text"}},required:["channel","thread_ts","text"]}},{name:"slack_add_reaction",description:"Add an emoji reaction to a message",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},timestamp:{type:"string",description:"Message timestamp"},reaction:{type:"string",description:"Emoji name without colons"}},required:["channel","timestamp","reaction"]}},{name:"slack_get_channel_history",description:"Get recent messages from a channel",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},limit:{type:"number",description:"Number of messages"}},required:["channel"]}},{name:"slack_get_thread_replies",description:"Get all replies in a message thread",input_schema:{type:"object",properties:{channel:{type:"string",description:"Channel ID"},thread_ts:{type:"string",description:"Thread timestamp"}},required:["channel","thread_ts"]}},{name:"slack_get_users",description:"List workspace users with basic profiles",input_schema:{type:"object",properties:{}}},{name:"slack_get_user_profile",description:"Get detailed profile for a specific user",input_schema:{type:"object",properties:{user_id:{type:"string",description:"Slack user ID"}},required:["user_id"]}}]};export{_ as slackSkill};
|
package/dist/test-runner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{spawn as
|
|
1
|
+
import{spawn as Z}from"child_process";import{writeFileSync as oe,mkdirSync as M,existsSync as I,readdirSync as D,readFileSync as J,unlinkSync as ae,createWriteStream as ce,statSync as le}from"fs";import{resolve as C,join as O}from"path";import{resolveMaxParallelRuns as Q}from"@zibby/core/utils/parallel-config.js";import{zibbyScratchSpecsDir as ue}from"@zibby/core/constants/zibby-scratch.js";var K="sessions",j=".zibby/output",$=process.env.ZIBBY_RUNNER_NODE_PROGRESS==="1",pe=process.env.ZIBBY_RUNNER_STATUS_STREAM==="1",X=process.env.ZIBBY_RUNNER_SPAWN_LOGS==="1",w=new Map,T=[],de=0,L=0,q=3e3;function ee(){return`run_${++de}_${Date.now().toString(36)}`}function G(s){let n=Math.floor(s/1e3);return n<60?`${n}s`:`${Math.floor(n/60)}m ${n%60}s`}function te(s){return s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g,"")}function N(s,n,e){if(!pe)return;let t=`
|
|
2
2
|
${n} [${s}] ${e}
|
|
3
|
-
`;try{process.stderr.write(t)}catch{}}function
|
|
3
|
+
`;try{process.stderr.write(t)}catch{}}function W(){T.length=0;for(let[,s]of w)if(s.status==="queued"&&(s.status="cancelled"),s.status==="running"&&s._child)try{s._child.kill("SIGTERM")}catch{}}process.on("exit",W);process.on("SIGINT",()=>{W(),process.exit(0)});process.on("SIGTERM",()=>{W(),process.exit(0)});var $e={id:"runner",description:"Run zibby test workflows from chat (parallel supported)",envKeys:[],promptFragment:`## Test Runner
|
|
4
4
|
You can run zibby test workflows directly from chat:
|
|
5
5
|
|
|
6
6
|
**CRITICAL: When user asks to test a ticket:**
|
|
@@ -137,17 +137,17 @@ Each run generates:
|
|
|
137
137
|
- events.json: All browser events
|
|
138
138
|
- raw_stream_output.txt: Agent log
|
|
139
139
|
|
|
140
|
-
Use run_artifacts({ runId, type }) and run_diagnose({ runId }) to inspect and explain failures.`,resolve(){return null},async handleToolCall(s,n,e){
|
|
141
|
-
`)){
|
|
140
|
+
Use run_artifacts({ runId, type }) and run_diagnose({ runId }) to inspect and explain failures.`,resolve(){return null},async handleToolCall(s,n,e){let t=e?.options?.workspace||process.cwd();try{switch(s){case"run_generate":return await fe(n,t);case"run_test":return await Se(n,t,e);case"run_status":return _e(n);case"run_cancel":return Ne(n);case"run_artifacts":return Oe(n,t);case"run_diagnose":return xe(n,t);case"list_specs":return Ie(n,t);default:return JSON.stringify({error:`Unknown tool: ${s}`})}}catch(i){return JSON.stringify({error:i.message})}},tools:[{name:"run_generate",description:"Generate specs from codebase. CRITICAL: DO NOT USE if ticket has test steps in comments. Only use when: (1) NO steps in ticket AND (2) local codebase exists (not external URLs). For tickets with steps, use run_test with inline format.",input_schema:{type:"object",properties:{ticket:{type:"string",description:"Jira ticket key (e.g. SCRUM-123). Auto-fetches ticket details."},description:{type:"string",description:"Ticket description text (use if no Jira key available)"},input:{type:"string",description:"Path to a file containing ticket/requirements text"},repo:{type:"string",description:"Path to the codebase (default: current directory)"},agent:{type:"string",description:"Optional agent override (cursor, gemini, claude, codex, assistant). Omit to use configured agent."},output:{type:"string",description:"Output directory for spec files (default: test-specs)"}}}},{name:"run_test",description:"Start a test (async, returns runId). spec = file path, or inline:+steps, or a Jira-shaped issue key (e.g. PROJ-123): when Jira is connected, the runner loads that issue's description+comments into an inline spec. After starting, tell the user and let them ask for progress via run_status.",input_schema:{type:"object",properties:{spec:{type:"string",description:"Workspace file path; or inline:+steps; or Jira issue key (KEY-123) to auto-fetch from Jira when the jira skill is active."},ticketKey:{type:"string",description:"Optional label (e.g. SCRUM-123). If spec is an issue key, this defaults to that key."},agent:{type:"string",description:"Optional agent override (cursor, gemini, claude, codex, assistant). Omit to use configured agent."},headless:{type:"boolean",description:"Run browser headless (default false)"},workflow:{type:"string",description:"Workflow override (e.g. quick-smoke)"}},required:["spec"]}},{name:"run_status",description:'Instant progress check \u2014 returns immediately. Use this whenever user asks about test progress. ALWAYS use runId="all".',input_schema:{type:"object",properties:{runId:{type:"string",description:'Use "all" to see all runs in this session (recommended). Or a specific run ID if known.'}},required:["runId"]}},{name:"run_cancel",description:'Cancel/kill a running test. ONLY use when the USER explicitly asks to cancel or stop a run. NEVER auto-cancel \u2014 tests take 1-5 minutes and "running" is normal. Use runId="all" to cancel all active runs.',input_schema:{type:"object",properties:{runId:{type:"string",description:'Run ID to cancel, or "all" to cancel all active runs'}},required:["runId"]}},{name:"run_artifacts",description:"Read artifacts from a test run session. Can list files, read results/events/logs, or search across all sessions.",input_schema:{type:"object",properties:{runId:{type:"string",description:"Run ID from run_test. Omit to search across all sessions."},type:{type:"string",enum:["list","result","events","log","search"],description:'What to retrieve: "list" = all files in session, "result" = result.json, "events" = events.json, "log" = raw output tail, "search" = search text across sessions'},node:{type:"string",description:'Node name to read from (e.g. "execute_live", "generate_script"). Default: "execute_live"'},query:{type:"string",description:'Search text (only for type="search"). Searches across all session logs/events.'},tail:{type:"number",description:"Number of characters from end of log to return (default: 3000)"}},required:["type"]}},{name:"run_diagnose",description:"Diagnose one or all runs, especially failed ones. Uses run logs + known error patterns and returns likely root cause with suggested next action.",input_schema:{type:"object",properties:{runId:{type:"string",description:'Run ID from run_test, or "all" (default) to diagnose all known runs'},tail:{type:"number",description:"Characters of run log tail to inspect (default: 2000)"}}}},{name:"list_specs",description:"List available test spec files in the project",input_schema:{type:"object",properties:{directory:{type:"string",description:'Directory to scan (default: "test-specs")'}}}}]};function U(){let s=0;for(let[,n]of w)n.status==="running"&&s++;return s}function F(){for(;T.length>0;){let s=Q(T[0]?.context?.options?.config);if(U()>=s)break;let{args:n,cwd:e,context:t}=T.shift();se(n,e,t)}}async function fe(s,n){let{ticket:e,description:t,input:i,repo:o,agent:d,output:u}=s,r=["generate"];e&&r.push("--ticket",e),t&&r.push("--description",t),i&&r.push("--input",i),o&&r.push("--repo",o),u&&r.push("--output",u);let l=["assistant","cursor","claude","codex","gemini"],c=d||process.env.AGENT_TYPE,p=c&&l.includes(c)?c:null;p&&r.push("--agent",p);let g=e||"generate";return N(g,"\u{1F9EA}","Starting test spec generation (real agent with codebase access)..."),new Promise(m=>{X&&console.error(`[zibby:spawn] skill=run_generate parentPid=${process.pid} \u2192 child zibby ${r.map(f=>/\s/.test(f)?JSON.stringify(f):f).join(" ")} cwd=${n}`);let b=Z("zibby",r,{cwd:n,env:{...process.env},stdio:["ignore","pipe","pipe"],detached:!1}),v="",R="";b.stdout.on("data",f=>{let _=f.toString();v+=_;for(let a of _.split(`
|
|
141
|
+
`)){let y=te(a).trim();y.startsWith("\u2705")?N(g,"\u2705",y.slice(2).trim()):y.startsWith("\u2713")&&N(g,"\u2714",y.slice(2).trim())}}),b.stderr.on("data",f=>{R+=f.toString()}),b.on("close",f=>{if(f!==0){N(g,"\u274C",`Generation failed (exit ${f})`),m(JSON.stringify({error:`zibby generate failed with exit code ${f}`,stderr:R.slice(-1e3)}));return}let _=C(n,u||"test-specs"),a=[];try{let y=e?e.toLowerCase().replace(/[^a-z0-9]+/g,"-"):"";a=D(_).filter(x=>x.endsWith(".txt")&&(!y||x.startsWith(y))).map(x=>O(_,x))}catch{}N(g,"\u2705",`Generated ${a.length} test spec files`),m(JSON.stringify({success:!0,ticketKey:e||null,specFiles:a.map(y=>y.replace(`${n}/`,"")),total:a.length,message:`Generated ${a.length} specs. Now call run_test for each file.`}))}),b.on("error",f=>{N(g,"\u274C",`Spawn error: ${f.message}`),m(JSON.stringify({error:f.message}))})})}var Y=1e5,z=/^[A-Z][A-Z0-9]+-\d+$/,ge=new Set(["paragraph","heading","bulletList","orderedList","listItem","blockquote","codeBlock","rule","table","tableRow","tableCell","tableHeader","mediaSingle","panel"]);function he(s,n){if(!n||!n.length)return s;let e=s;for(let t of n)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 E(s,n=0){if(!Array.isArray(s))return"";let e=[];for(let t of s){if(t.type==="text"){e.push(he(t.text||"",t.marks));continue}if(t.type==="hardBreak"){e.push(`
|
|
142
142
|
`);continue}if(t.type==="rule"){e.push(`
|
|
143
143
|
---
|
|
144
|
-
`);continue}
|
|
144
|
+
`);continue}let i=t.content?E(t.content,n+1):"";if(t.type==="listItem")e.push(i);else if(t.type==="bulletList"){let o=(t.content||[]).map(d=>`- ${E(d.content||[],n+1).trim()}`);e.push(`
|
|
145
145
|
${o.join(`
|
|
146
146
|
`)}
|
|
147
|
-
`)}else if(t.type==="orderedList"){
|
|
147
|
+
`)}else if(t.type==="orderedList"){let o=(t.content||[]).map((d,u)=>`${u+1}. ${E(d.content||[],n+1).trim()}`);e.push(`
|
|
148
148
|
${o.join(`
|
|
149
149
|
`)}
|
|
150
|
-
`)}else if(t.type==="heading"){
|
|
150
|
+
`)}else if(t.type==="heading"){let o=t.attrs?.level||2;e.push(`
|
|
151
151
|
|
|
152
152
|
${"#".repeat(o)} ${i.trim()}
|
|
153
153
|
|
|
@@ -156,13 +156,13 @@ ${"#".repeat(o)} ${i.trim()}
|
|
|
156
156
|
${i}
|
|
157
157
|
`):e.push(i)}return e.join("").replace(/\n{3,}/g,`
|
|
158
158
|
|
|
159
|
-
`)}function me(s){return s==null||s===""?"":typeof s=="string"?s.trim():typeof s=="object"&&Array.isArray(s.content)?
|
|
159
|
+
`)}function me(s){return s==null||s===""?"":typeof s=="string"?s.trim():typeof s=="object"&&Array.isArray(s.content)?E(s.content).trim():""}async function ye(s){let{getSkill:n}=await import("@zibby/workflow"),e=n("jira");if(!e||typeof e.handleToolCall!="function")return null;try{let t=await e.handleToolCall("jira_get_issue",{issueKey:s}),i=JSON.parse(t);if(i?.error)return null;let o=await e.handleToolCall("jira_get_comments",{issueKey:s,maxResults:50}),d=JSON.parse(o);if(d?.error)return null;let u=me(i.description),r=[];u&&r.push(u);let l=Array.isArray(d.comments)?d.comments:[];if(l.length>0){let p=l.map(g=>String(g.body||"").trim()).filter(Boolean).join(`
|
|
160
160
|
|
|
161
161
|
`);p&&r.push(p)}let c=r.join(`
|
|
162
162
|
|
|
163
|
-
`).trim();return c?(c.length>
|
|
163
|
+
`).trim();return c?(c.length>Y&&(c=`${c.slice(0,Y)}
|
|
164
164
|
|
|
165
|
-
...[truncated]`),{inlineSpec:`inline:${c}`,issueKey:s}):null}catch{return null}}function ke(s,n){try{
|
|
166
|
-
`);x=A.pop();for(
|
|
167
|
-
Spawn error: ${k.message}`,N(y,"\u274C",`Spawn error: ${k.message}`),b.end(),
|
|
168
|
-
${e||""}`.toLowerCase(),o={runId:s?.runId||null,status:s?.status||null,exitCode:s?.exitCode??null,likelyCause:"Unknown failure",confidence:"low",nextStep:'Call run_artifacts({ runId, type: "log" }) with larger tail and inspect full logs.'};return s?.status==="running"||s?.status==="queued"?{...o,likelyCause:"Run is still active; no terminal failure to diagnose yet.",confidence:"high",nextStep:'Call run_status({ runId: "all" }) to check progress.'}:i.includes("test spec not found")?{...o,likelyCause:"Invalid spec input: run_test received a non-existent spec path.",confidence:"high",nextStep:"Use spec as inline:... or a real file path from list_specs. For ticket keys, fetch steps first via Jira then build inline spec."}:i.includes("unknown command")&&i.includes("'run'")?{...o,likelyCause:"CLI command mismatch (`zibby run` unsupported in current CLI).",confidence:"high",nextStep:"Use `zibby test ...` spawn path (runner should already do this)."}:i.includes("missing openai_api_key")||i.includes("didn't provide an api key")||i.includes("401")?{...o,likelyCause:"Provider authentication/config issue (API key/proxy auth missing or rejected).",confidence:"medium",nextStep:"Verify proxy/token env and auth mode, then retry once configuration is valid."}:i.includes("spawn error")||i.includes("enoent")?{...o,likelyCause:"Failed to spawn CLI process (binary/path/environment issue).",confidence:"medium",nextStep:"Confirm `zibby` is installed and available in PATH for the chat process."}:i.includes("security command failed")||i.includes("security process exited with code: 45")||i.includes("password not found for account")?{...o,likelyCause:"Cursor agent keychain/auth failed during preflight (often transient, more common under parallel starts).",confidence:"high",nextStep:'Retry failed ticket sequentially (not parallel), or run with a different agent via run_test({ ..., agent: "codex" }).'}:o}function Oe(s,n){
|
|
165
|
+
...[truncated]`),{inlineSpec:`inline:${c}`,issueKey:s}):null}catch{return null}}function ke(s,n){try{let e=JSON.parse(s);return JSON.stringify({...e,...n})}catch{return s}}async function Se(s,n,e){let t={...s},i=String(t.spec??"").trim();if(!i)return JSON.stringify({error:"spec is required"});let o=null;if(z.test(i)&&!i.startsWith("inline:")){let c=await ye(i);c&&(i=c.inlineSpec,t.spec=i,String(t.ticketKey||"").trim()||(t.ticketKey=c.issueKey),o=c.issueKey)}let d=String(t.ticketKey||"").trim();if(d){for(let[c,p]of w.entries())if(p?.ticketKey===d&&!(p?.status!=="running"&&p?.status!=="queued"))return JSON.stringify({runId:c,ticketKey:d,status:p.status,reused:!0,message:`A run for ${d} is already ${p.status}. Reusing existing run instead of starting a duplicate.`})}if(!i.startsWith("inline:")){let c=C(n,i);if(!I(c))return z.test(i)?JSON.stringify({error:`Invalid run_test spec: "${i}" is an issue id, not a spec.`,reason:"Jira auto-load was attempted but did not return usable text, or Jira is not configured.",doNext:["Confirm the jira skill is active and authenticated.",'Or call tracker tools yourself, then run_test with spec: "inline:" + steps.'],validExample:{spec:"inline:1. Open https://example.com \u2026 2. Verify \u2026",ticketKey:i},invalidExample:{spec:i,ticketKey:i}}):JSON.stringify({error:`Test spec not found: ${i}`,hint:'If this should be issue steps, load the issue with your tracker tools first, then run_test with spec: "inline:" + steps. Otherwise use a real file path.'})}let u=Q(e?.options?.config);if(U()>=u){let c=ee(),p=t.ticketKey||c,g={runId:c,spec:t.ticketKey?`${t.ticketKey}: ${t.spec}`:t.spec,ticketKey:t.ticketKey||null,status:"queued",startTime:Date.now(),exitCode:null,output:"",error:""};w.set(c,g),T.push({args:{...t,_queuedRunId:c},cwd:n,context:e}),N(p,"\u23F3",`Queued (${U()}/${u} running, ${T.length} queued)`);let m={runId:c,spec:g.spec,ticketKey:g.ticketKey,status:"queued",message:`Queued \u2014 will start when a slot opens (max ${u} concurrent).`};return o&&(m.resolvedFromJiraIssue=o,m.message+=` (spec built from Jira ${o})`),JSON.stringify(m)}let r=Date.now()-L;r<q&&L>0&&await new Promise(c=>setTimeout(c,q-r)),L=Date.now();let l=se(t,n,e);return o?ke(l,{resolvedFromJiraIssue:o,message:`Spec was loaded from Jira issue ${o} (description + comments).`}):l}function se(s,n,e){let{spec:t,ticketKey:i,agent:o,headless:d,workflow:u,_queuedRunId:r}=s,l=r||ee(),c=t,p=!1;if(t.startsWith("inline:")){p=!0;let k=ue(n);M(k,{recursive:!0}),c=O(k,`${l}.txt`),oe(c,t.slice(7).trim(),"utf-8")}let g=C(n,".zibby","output","runs");M(g,{recursive:!0});let m=O(g,`${l}.log`),b=ce(m,{flags:"a"}),R=o&&["assistant","cursor","claude","codex","gemini"].includes(o)?o:null,f=["test",c];R&&f.push("--agent",R),d&&f.push("--headless"),u&&f.push("--workflow",u),X&&console.error(`[zibby:spawn] skill=run_test parentPid=${process.pid} \u2192 child zibby ${f.map(k=>/\s/.test(k)?JSON.stringify(k):k).join(" ")} cwd=${n}`);let _=Z("zibby",f,{cwd:n,env:{...process.env,ZIBBY_WORKFLOW_GRAPH_LOG_MARKERS:"1"},stdio:["ignore","pipe","pipe"],detached:!1}),a={runId:l,spec:i?`${i}: ${t}`:t,ticketKey:i||null,specPath:c,logPath:m,isInline:p,pid:_.pid,status:"running",output:"",error:"",startTime:Date.now(),exitCode:null,currentNode:null,completedNodes:[]},y=i||l,x="";function P(k){let h=te(k).trim();if(!h)return;if(h.startsWith("__WORKFLOW_GRAPH_LOG__")){try{let S=JSON.parse(h.slice(22));S.phase==="node_begin"?a.currentNode=S.node:S.phase==="node_end"&&(S.node&&!a.completedNodes.includes(S.node)&&a.completedNodes.push(S.node),a.currentNode===S.node&&(a.currentNode=null))}catch{}return}let A=h.match(/Session\s+(\S+)/);if(A&&!a.sessionId&&(a.sessionId=A[1],a.sessionPath=C(n,j,K,a.sessionId)),h.startsWith("\u250C ")||h.startsWith("\u250C ")){let S=h.slice(2).trim();a.currentNode=S,$&&N(y,"\u25B6",`${S}`)}else if(h.startsWith("\u2514 ")||h.startsWith("\u2514 ")){let S=h.slice(2).trim();S.startsWith("done")?(a.currentNode&&!a.completedNodes.includes(a.currentNode)&&a.completedNodes.push(a.currentNode),$&&N(y,"\u2714",`${a.currentNode||"node"} done ${S.replace("done","").trim()}`),a.currentNode=null):S.startsWith("failed")&&($&&N(y,"\u2718",`${a.currentNode||"node"} failed ${S.replace("failed","").trim()}`),a.currentNode=null)}else h.includes("Workflow completed")&&(a.currentNode=null,$&&N(y,"\u2714",`Workflow completed (${G(Date.now()-a.startTime)})`))}function re(k){let h=k.toString();a.output+=h,b.write(h),a.output.length>5e4&&(a.output=a.output.slice(-3e4)),x+=h;let A=x.split(`
|
|
166
|
+
`);x=A.pop();for(let S of A)P(S)}return _.stdout.on("data",re),_.stderr.on("data",k=>{let h=k.toString();a.error+=h,b.write(h),a.error.length>2e4&&(a.error=a.error.slice(-1e4))}),_.on("close",k=>{a.status=k===0?"passed":"failed",a.exitCode=k,a.endTime=Date.now(),x&&P(x),b.end();let h=G(Date.now()-a.startTime);if(k===0?N(y,"\u2705",`Passed (${h})`):N(y,"\u274C",`Failed (${h})`),a.isInline)try{ae(a.specPath)}catch{}F()}),_.on("error",k=>{a.status="error",a.error+=`
|
|
167
|
+
Spawn error: ${k.message}`,N(y,"\u274C",`Spawn error: ${k.message}`),b.end(),F()}),a._child=_,w.set(l,a),JSON.stringify({runId:l,spec:a.spec,ticketKey:a.ticketKey,status:"running",pid:_.pid,logFile:m})}function B(s){let n=Math.round(((s.endTime||Date.now())-s.startTime)/1e3),e=s.completedNodes||[],t=s.currentNode||null;if(s.status!=="running")return{elapsed:n,stage:s.status,completedNodes:e,currentNode:null};let i;return t?(i=`Actively executing node "${t}"`,e.length&&(i+=` (completed: ${e.join(", ")})`)):e.length?i=`Between nodes (completed: ${e.join(", ")})`:i="Starting up (initializing workflow)",i+=`. Elapsed: ${n}s. This is normal progress \u2014 do not cancel.`,{elapsed:n,stage:"running",currentNode:t,completedNodes:e,progress:i}}function _e(s){let{runId:n}=s;if(!n)return JSON.stringify({error:"runId is required"});if(n==="all"){let o=[...w.entries()].map(([c,p])=>{let g=B(p),m={runId:c,spec:p.spec,ticketKey:p.ticketKey,status:p.status,elapsed:g.elapsed,exitCode:p.exitCode,sessionId:p.sessionId||null};return p.status==="running"?(m.currentNode=g.currentNode,m.completedNodes=g.completedNodes,m.progress=g.progress):m.outputTail=p.output.slice(-500),m}),d=o.filter(c=>c.status==="running").length,u=o.filter(c=>c.status==="passed").length,r=o.filter(c=>c.status==="failed").length,l={total:o.length,running:d,passed:u,failed:r,runs:o};return d>0&&(l._hint="All running tests are progressing normally through their workflow nodes. Do NOT cancel, diagnose, or interpret as stuck. Just tell the user they are still running."),JSON.stringify(l)}let e=w.get(n);if(!e)return JSON.stringify({error:`Run not found: ${n}`});let t=B(e),i={runId:n,spec:e.spec,ticketKey:e.ticketKey,status:e.status,elapsed:t.elapsed,exitCode:e.exitCode,sessionId:e.sessionId||null};return e.status==="running"?(i.currentNode=t.currentNode,i.completedNodes=t.completedNodes,i.progress=t.progress):(i.outputTail=e.output.slice(-1e3),i.errorTail=e.error.slice(-500)),e.status==="running"&&(i._hint="This run is actively progressing. Do NOT cancel, diagnose, or assume stuck. Just tell the user it is still running."),JSON.stringify(i)}function V(s,n){if(n.status==="queued"){let e=T.findIndex(t=>t.args._queuedRunId===s);return e>=0&&T.splice(e,1),n.status="cancelled",n.endTime=Date.now(),{ok:!0,runId:s,status:"cancelled"}}if(n.status!=="running")return{ok:!1,runId:s,error:`Run is not active (status: ${n.status})`};try{return n._child.kill("SIGTERM"),n.status="cancelled",n.endTime=Date.now(),{ok:!0,runId:s,status:"cancelled"}}catch(e){return{ok:!1,runId:s,error:`Failed to cancel: ${e.message}`}}}function Ne(s){let{runId:n}=s;if(!n)return JSON.stringify({error:"runId is required"});if(n==="all"){let t=[];for(let[i,o]of w.entries())(o.status==="running"||o.status==="queued")&&t.push(V(i,o));return t.length===0?JSON.stringify({ok:!0,message:"No active runs to cancel"}):JSON.stringify({ok:!0,cancelled:t.length,results:t})}let e=w.get(n);return JSON.stringify(e?V(n,e):{error:`Run not found: ${n}`})}function we(s,n){let e=w.get(s);if(e?.sessionPath&&I(e.sessionPath))return e.sessionPath;if(e?.sessionId){let t=C(n,j,K,e.sessionId);if(I(t))return t}return null}function ne(s,n=""){let e=[];if(!I(s))return e;for(let t of D(s,{withFileTypes:!0})){let i=n?`${n}/${t.name}`:t.name;if(t.isDirectory())e.push(...ne(O(s,t.name),i));else{let o=le(O(s,t.name));e.push({path:i,size:o.size})}}return e}function H(s){if(!I(s))return null;try{return JSON.parse(J(s,"utf-8"))}catch{return null}}function ie(s,n=2e3){if(!s||!I(s))return"";try{return J(s,"utf-8").slice(-Math.max(200,Number(n)||2e3))}catch{return""}}function be({run:s,logTail:n,errorTail:e}){let i=`${n||""}
|
|
168
|
+
${e||""}`.toLowerCase(),o={runId:s?.runId||null,status:s?.status||null,exitCode:s?.exitCode??null,likelyCause:"Unknown failure",confidence:"low",nextStep:'Call run_artifacts({ runId, type: "log" }) with larger tail and inspect full logs.'};return s?.status==="running"||s?.status==="queued"?{...o,likelyCause:"Run is still active; no terminal failure to diagnose yet.",confidence:"high",nextStep:'Call run_status({ runId: "all" }) to check progress.'}:i.includes("test spec not found")?{...o,likelyCause:"Invalid spec input: run_test received a non-existent spec path.",confidence:"high",nextStep:"Use spec as inline:... or a real file path from list_specs. For ticket keys, fetch steps first via Jira then build inline spec."}:i.includes("unknown command")&&i.includes("'run'")?{...o,likelyCause:"CLI command mismatch (`zibby run` unsupported in current CLI).",confidence:"high",nextStep:"Use `zibby test ...` spawn path (runner should already do this)."}:i.includes("missing openai_api_key")||i.includes("didn't provide an api key")||i.includes("401")?{...o,likelyCause:"Provider authentication/config issue (API key/proxy auth missing or rejected).",confidence:"medium",nextStep:"Verify proxy/token env and auth mode, then retry once configuration is valid."}:i.includes("spawn error")||i.includes("enoent")?{...o,likelyCause:"Failed to spawn CLI process (binary/path/environment issue).",confidence:"medium",nextStep:"Confirm `zibby` is installed and available in PATH for the chat process."}:i.includes("security command failed")||i.includes("security process exited with code: 45")||i.includes("password not found for account")?{...o,likelyCause:"Cursor agent keychain/auth failed during preflight (often transient, more common under parallel starts).",confidence:"high",nextStep:'Retry failed ticket sequentially (not parallel), or run with a different agent via run_test({ ..., agent: "codex" }).'}:o}function Oe(s,n){let{runId:e,type:t,node:i="execute_live",query:o,tail:d=3e3}=s;if(t==="search"){if(!o)return JSON.stringify({error:'query is required for type="search"'});let r=C(n,j,K);if(!I(r))return JSON.stringify({matches:[],message:"No sessions found"});let l=[],c=o.toLowerCase();for(let p of D(r,{withFileTypes:!0})){if(!p.isDirectory())continue;let g=O(r,p.name),m=[{file:"execute_live/result.json",label:"result"},{file:"execute_live/events.json",label:"events"},{file:"execute_live/raw_stream_output.txt",label:"log"},{file:"generate_script/raw_stream_output.txt",label:"script_log"},{file:"title.txt",label:"title"}];for(let{file:b,label:v}of m){let R=O(g,b);if(I(R))try{let f=J(R,"utf-8");if(f.toLowerCase().includes(c)){let _=f.toLowerCase().indexOf(c),a=Math.max(0,_-100),y=Math.min(f.length,_+o.length+100);l.push({sessionId:p.name,artifact:v,snippet:f.slice(a,y)})}}catch{}}if(l.length>=20)break}return JSON.stringify({query:o,matches:l,total:l.length})}if(!e)return JSON.stringify({error:"runId is required for this type"});if(t==="log"){let r=w.get(e),l=ie(r?.logPath,d);if(l)return JSON.stringify({runId:e,source:"run-log",totalLength:l.length,tail:l})}let u=we(e,n);if(!u)return JSON.stringify({error:`No session found for run ${e}. The run may still be starting.`});switch(t){case"list":{let r=ne(u);return JSON.stringify({sessionId:u.split("/").pop(),files:r,total:r.length})}case"result":{let r=H(O(u,i,"result.json"));return JSON.stringify(r?{sessionId:u.split("/").pop(),node:i,result:r}:{error:`No result.json found in ${i}`})}case"events":{let r=H(O(u,i,"events.json"));if(!r)return JSON.stringify({error:`No events.json found in ${i}`});let l=Array.isArray(r)?r:r.events||[];return JSON.stringify({sessionId:u.split("/").pop(),node:i,totalEvents:l.length,events:l.slice(-50)})}case"log":{let r=O(u,i,"raw_stream_output.txt");if(!I(r))return JSON.stringify({error:`No log found in ${i}`});let l=J(r,"utf-8");return JSON.stringify({sessionId:u.split("/").pop(),node:i,totalLength:l.length,tail:l.slice(-d)})}default:return JSON.stringify({error:`Unknown artifact type: ${t}. Use: list, result, events, log, search`})}}function xe(s,n){let e=String(s?.runId||"all"),t=Number(s?.tail||2e3),i=e==="all"?[...w.keys()]:[e];if(i.length===0)return JSON.stringify({error:"No runs available to diagnose. Call run_test first."});let o=i.map(r=>{let l=w.get(r);if(!l)return{runId:r,error:`Run not found: ${r}`};let c=ie(l.logPath,t),p=String(l.error||"").slice(-Math.max(200,t));return{...be({run:l,logTail:c,errorTail:p}),ticketKey:l.ticketKey||null,spec:l.spec,logTail:c,errorTail:p}}),d=o.filter(r=>r.status==="failed"||r.status==="error"),u=o.filter(r=>r.status==="running"||r.status==="queued");return JSON.stringify({total:o.length,failed:d.length,active:u.length,diagnoses:o})}function Ie(s,n){let e=s?.directory||"test-specs",t=C(n,e);if(!I(t))return JSON.stringify({specs:[],directory:e,message:`Directory not found: ${e}`});try{let o=function(d,u){for(let r of D(d,{withFileTypes:!0})){let l=u?`${u}/${r.name}`:r.name;r.isDirectory()?o(O(d,r.name),l):(r.name.endsWith(".txt")||r.name.endsWith(".md"))&&i.push(l)}},i=[];return o(t,""),JSON.stringify({specs:i.map(d=>`${e}/${d}`),total:i.length,directory:e})}catch(i){return JSON.stringify({error:i.message})}}export{$e as testRunnerSkill};
|