jiranimo 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
1
  {
2
- "builtAt": "2026-04-07T07:17:50.614Z",
2
+ "builtAt": "2026-04-15T14:41:51.694Z",
3
3
  "target": "node24"
4
4
  }
package/dist/index.js CHANGED
@@ -288,17 +288,23 @@ ${l}
288
288
 
289
289
  The repository is at: \`${r}\`
290
290
 
291
- **Step 1 - Create a worktree** for exploration:
291
+ **Step 1 - Fetch latest remote state** (required before branch creation):
292
+ \`\`\`bash
293
+ git -C ${r} fetch origin
292
294
  \`\`\`
295
+
296
+ **Step 2 - Create a worktree** for exploration:
297
+ \`\`\`bash
293
298
  git -C ${r} worktree add ${d} -b ${a}${t.key}-plan origin/${s}
294
299
  cd ${d}
300
+ pwd # must show ${d} \u2014 abort if it does not
295
301
  \`\`\`
296
302
  If \`origin/${s}\` does not exist, detect the actual default branch first:
297
303
  \`git -C ${r} remote show ${c} | grep 'HEAD branch'\`
298
304
 
299
- **Step 2 - Write the plan** to \`${yy(t.key)}\` \u2014 do NOT commit this file or anything else.
305
+ **Step 3 - Write the plan** to \`${yy(t.key)}\` \u2014 do NOT commit this file or anything else.
300
306
 
301
- **Step 3 - Report back using the jiranimo MCP tools** (available as \`jiranimo_*\`):
307
+ **Step 4 - Report back using the jiranimo MCP tools** (available as \`jiranimo_*\`):
302
308
  - \`jiranimo_progress\` \u2014 send progress updates as you work (task_key="${t.key}")
303
309
  - \`jiranimo_complete\` \u2014 when the plan file is written (task_key="${t.key}")
304
310
  - \`jiranimo_fail\` \u2014 if you hit an unrecoverable error (task_key="${t.key}")
@@ -319,19 +325,26 @@ ${f}
319
325
 
320
326
  The repository you should work on is at: \`${r}\`
321
327
 
322
- **Step 1 - Create a worktree** for isolation:
328
+ **Step 1 - Fetch latest remote state** (required before branch creation):
329
+ \`\`\`bash
330
+ git -C ${r} fetch origin
323
331
  \`\`\`
332
+
333
+ **Step 2 - Create a worktree** for isolation:
334
+ \`\`\`bash
324
335
  if [ ! -d "${d}" ]; then
325
336
  git -C ${r} worktree add ${d} -b ${a}${t.key}-<short-slug> origin/${s}
326
337
  fi
327
338
  cd ${d}
339
+ pwd # must show ${d} \u2014 if it does not, STOP and call jiranimo_fail
340
+ git branch --show-current # must NOT be master or main \u2014 if it is, STOP and call jiranimo_fail
328
341
  \`\`\`
329
342
  If \`origin/${s}\` does not exist, detect the actual default branch first:
330
343
  \`git -C ${r} remote show ${c} | grep 'HEAD branch'\`
331
344
 
332
- **Step 2 - Implement** the changes described above. Write or update tests as appropriate and ensure they pass.
345
+ **Step 3 - Implement** the changes described above. Write or update tests as appropriate and ensure they pass.
333
346
 
334
- **Step 3 - Commit and push**:
347
+ **Step 4 - Commit and push**:
335
348
  \`\`\`
336
349
  git add -A
337
350
  git commit -m "<type>(${t.key}): <short description>"
@@ -339,7 +352,7 @@ git push -u ${c} <branch-name>
339
352
  \`\`\`
340
353
  Use conventional commit types: \`feat\` for features, \`fix\` for bugs, \`chore\` for other work.
341
354
 
342
- **Step 4 \u2014 Screenshot (frontend tasks only)**
355
+ **Step 5 \u2014 Screenshot (frontend tasks only)**
343
356
  If your implementation touches any UI files (HTML, CSS, frontend JS, browser extension files), take a screenshot of the real running feature.
344
357
 
345
358
  Think of yourself as a developer who just finished implementing this feature and wants to show it working. How would you demo it to a colleague? Do that \u2014 use the real app, the real dev server, the real test suite.
@@ -359,7 +372,7 @@ Call \`jiranimo_screenshot_failed\` only if you genuinely tried both approaches
359
372
 
360
373
  If your changes are server-only, skip this step.
361
374
 
362
- **Step 5 \u2014 Upload the screenshot and create a PR**:
375
+ **Step 6 \u2014 Upload the screenshot and create a PR**:
363
376
  If a screenshot was taken, call \`jiranimo_upload_screenshot\` with \`file_path="/tmp/jiranimo-${t.key}-screenshot.png"\`. It returns a URL \u2014 use it in the PR body as \`![Screenshot](<url>)\`.
364
377
 
365
378
  \`\`\`bash
@@ -367,10 +380,10 @@ gh pr create ${u?"--draft ":""}--title "#${t.key} ${t.summary}" --body "Implemen
367
380
  # append screenshot line here if upload succeeded: ![Screenshot](<url>)"
368
381
  \`\`\`
369
382
 
370
- **Step 5b \u2014 Verify the screenshot is in the PR body** (required if a screenshot was uploaded):
383
+ **Step 6b \u2014 Verify the screenshot is in the PR body** (required if a screenshot was uploaded):
371
384
  Run \`gh pr view --json body --jq '.body'\` and confirm it contains the screenshot URL. If not, run \`gh pr edit\` to add it.
372
385
 
373
- **Step 6 - Report back using the jiranimo MCP tools** (available as \`jiranimo_*\`):
386
+ **Step 7 - Report back using the jiranimo MCP tools** (available as \`jiranimo_*\`):
374
387
  - \`jiranimo_progress\` \u2014 send progress updates as you work (task_key="${t.key}")
375
388
  - \`jiranimo_report_pr\` \u2014 once the PR is created, report its url, number, and branch name (task_key="${t.key}")
376
389
  - \`jiranimo_complete\` \u2014 when all work is done (task_key="${t.key}")
@@ -434,7 +447,7 @@ data:
434
447
  `;return n&&(o+=`id: ${n}
435
448
  `),o+=`data: ${JSON.stringify(i)}
436
449
 
437
- `,e.enqueue(r.encode(o)),!0}catch(o){return this.onerror?.(o),!1}}handleUnsupportedRequest(){return this.onerror?.(new Error("Method not allowed.")),new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32e3,message:"Method not allowed."},id:null}),{status:405,headers:{Allow:"GET, POST, DELETE","Content-Type":"application/json"}})}async handlePostRequest(e,r){try{let i=e.headers.get("accept");if(!i?.includes("application/json")||!i.includes("text/event-stream"))return this.onerror?.(new Error("Not Acceptable: Client must accept both application/json and text/event-stream")),this.createJsonErrorResponse(406,-32e3,"Not Acceptable: Client must accept both application/json and text/event-stream");let n=e.headers.get("content-type");if(!n||!n.includes("application/json"))return this.onerror?.(new Error("Unsupported Media Type: Content-Type must be application/json")),this.createJsonErrorResponse(415,-32e3,"Unsupported Media Type: Content-Type must be application/json");let o={headers:Object.fromEntries(e.headers.entries()),url:new URL(e.url)},a;if(r?.parsedBody!==void 0)a=r.parsedBody;else try{a=await e.json()}catch{return this.onerror?.(new Error("Parse error: Invalid JSON")),this.createJsonErrorResponse(400,-32700,"Parse error: Invalid JSON")}let s;try{Array.isArray(a)?s=a.map(x=>Cy.parse(x)):s=[Cy.parse(a)]}catch{return this.onerror?.(new Error("Parse error: Invalid JSON-RPC message")),this.createJsonErrorResponse(400,-32700,"Parse error: Invalid JSON-RPC message")}let c=s.some(Ay);if(c){if(this._initialized&&this.sessionId!==void 0)return this.onerror?.(new Error("Invalid Request: Server already initialized")),this.createJsonErrorResponse(400,-32600,"Invalid Request: Server already initialized");if(s.length>1)return this.onerror?.(new Error("Invalid Request: Only one initialization request is allowed")),this.createJsonErrorResponse(400,-32600,"Invalid Request: Only one initialization request is allowed");this.sessionId=this.sessionIdGenerator?.(),this._initialized=!0,this.sessionId&&this._onsessioninitialized&&await Promise.resolve(this._onsessioninitialized(this.sessionId))}if(!c){let x=this.validateSession(e);if(x)return x;let $=this.validateProtocolVersion(e);if($)return $}if(!s.some(zn)){for(let x of s)this.onmessage?.(x,{authInfo:r?.authInfo,requestInfo:o});return new Response(null,{status:202})}let l=crypto.randomUUID(),p=s.find(x=>Ay(x)),d=p?p.params.protocolVersion:e.headers.get("mcp-protocol-version")??hS;if(this._enableJsonResponse)return new Promise(x=>{this._streamMapping.set(l,{resolveJson:x,cleanup:()=>{this._streamMapping.delete(l)}});for(let $ of s)zn($)&&this._requestToStreamMapping.set($.id,l);for(let $ of s)this.onmessage?.($,{authInfo:r?.authInfo,requestInfo:o})});let f=new TextEncoder,m,h=new ReadableStream({start:x=>{m=x},cancel:()=>{this._streamMapping.delete(l)}}),g={"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"};this.sessionId!==void 0&&(g["mcp-session-id"]=this.sessionId);for(let x of s)zn(x)&&(this._streamMapping.set(l,{controller:m,encoder:f,cleanup:()=>{this._streamMapping.delete(l);try{m.close()}catch{}}}),this._requestToStreamMapping.set(x.id,l));await this.writePrimingEvent(m,f,l,d);for(let x of s){let $,w;zn(x)&&this._eventStore&&d>="2025-11-25"&&($=()=>{this.closeSSEStream(x.id)},w=()=>{this.closeStandaloneSSEStream()}),this.onmessage?.(x,{authInfo:r?.authInfo,requestInfo:o,closeSSEStream:$,closeStandaloneSSEStream:w})}return new Response(h,{status:200,headers:g})}catch(i){return this.onerror?.(i),this.createJsonErrorResponse(400,-32700,"Parse error",{data:String(i)})}}async handleDeleteRequest(e){let r=this.validateSession(e);if(r)return r;let i=this.validateProtocolVersion(e);return i||(await Promise.resolve(this._onsessionclosed?.(this.sessionId)),await this.close(),new Response(null,{status:200}))}validateSession(e){if(this.sessionIdGenerator===void 0)return;if(!this._initialized)return this.onerror?.(new Error("Bad Request: Server not initialized")),this.createJsonErrorResponse(400,-32e3,"Bad Request: Server not initialized");let r=e.headers.get("mcp-session-id");if(!r)return this.onerror?.(new Error("Bad Request: Mcp-Session-Id header is required")),this.createJsonErrorResponse(400,-32e3,"Bad Request: Mcp-Session-Id header is required");if(r!==this.sessionId)return this.onerror?.(new Error("Session not found")),this.createJsonErrorResponse(404,-32001,"Session not found")}validateProtocolVersion(e){let r=e.headers.get("mcp-protocol-version");if(r!==null&&!Do.includes(r))return this.onerror?.(new Error(`Bad Request: Unsupported protocol version: ${r} (supported versions: ${Do.join(", ")})`)),this.createJsonErrorResponse(400,-32e3,`Bad Request: Unsupported protocol version: ${r} (supported versions: ${Do.join(", ")})`)}async close(){this._streamMapping.forEach(({cleanup:e})=>{e()}),this._streamMapping.clear(),this._requestResponseMap.clear(),this.onclose?.()}closeSSEStream(e){let r=this._requestToStreamMapping.get(e);if(!r)return;let i=this._streamMapping.get(r);i&&i.cleanup()}closeStandaloneSSEStream(){let e=this._streamMapping.get(this._standaloneSseStreamId);e&&e.cleanup()}async send(e,r){let i=r?.relatedRequestId;if((Vr(e)||Uo(e))&&(i=e.id),i===void 0){if(Vr(e)||Uo(e))throw new Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");let a;this._eventStore&&(a=await this._eventStore.storeEvent(this._standaloneSseStreamId,e));let s=this._streamMapping.get(this._standaloneSseStreamId);if(s===void 0)return;s.controller&&s.encoder&&this.writeSSEEvent(s.controller,s.encoder,e,a);return}let n=this._requestToStreamMapping.get(i);if(!n)throw new Error(`No connection established for request ID: ${String(i)}`);let o=this._streamMapping.get(n);if(!this._enableJsonResponse&&o?.controller&&o?.encoder){let a;this._eventStore&&(a=await this._eventStore.storeEvent(n,e)),this.writeSSEEvent(o.controller,o.encoder,e,a)}if(Vr(e)||Uo(e)){this._requestResponseMap.set(i,e);let a=Array.from(this._requestToStreamMapping.entries()).filter(([c,u])=>u===n).map(([c])=>c);if(a.every(c=>this._requestResponseMap.has(c))){if(!o)throw new Error(`No connection established for request ID: ${String(i)}`);if(this._enableJsonResponse&&o.resolveJson){let c={"Content-Type":"application/json"};this.sessionId!==void 0&&(c["mcp-session-id"]=this.sessionId);let u=a.map(l=>this._requestResponseMap.get(l));u.length===1?o.resolveJson(new Response(JSON.stringify(u[0]),{status:200,headers:c})):o.resolveJson(new Response(JSON.stringify(u),{status:200,headers:c}))}else o.cleanup();for(let c of a)this._requestResponseMap.delete(c),this._requestToStreamMapping.delete(c)}}}};var Kf=class{constructor(e={}){this._requestContext=new WeakMap,this._webStandardTransport=new Wf(e),this._requestListener=e0(async r=>{let i=this._requestContext.get(r);return this._webStandardTransport.handleRequest(r,{authInfo:i?.authInfo,parsedBody:i?.parsedBody})},{overrideGlobalObjects:!1})}get sessionId(){return this._webStandardTransport.sessionId}set onclose(e){this._webStandardTransport.onclose=e}get onclose(){return this._webStandardTransport.onclose}set onerror(e){this._webStandardTransport.onerror=e}get onerror(){return this._webStandardTransport.onerror}set onmessage(e){this._webStandardTransport.onmessage=e}get onmessage(){return this._webStandardTransport.onmessage}async start(){return this._webStandardTransport.start()}async close(){return this._webStandardTransport.close()}async send(e,r){return this._webStandardTransport.send(e,r)}async handleRequest(e,r,i){let n=e.auth;await e0(async a=>this._webStandardTransport.handleRequest(a,{authInfo:n,parsedBody:i}),{overrideGlobalObjects:!1})(e,r)}closeSSEStream(e){this._webStandardTransport.closeSSEStream(e)}closeStandaloneSSEStream(){this._webStandardTransport.closeStandaloneSSEStream()}};import{writeFileSync as pZ,unlinkSync as dZ,readFileSync as fZ}from"node:fs";import{join as mI}from"node:path";var mZ="7922f663cfcfe9b4b0c3119c2b61b7d8";async function hZ(t){let r=fZ(t).toString("base64"),i=new URLSearchParams({key:mZ,image:r}),n=await fetch("https://api.imgbb.com/1/upload",{method:"POST",body:i});if(!n.ok)throw new Error(`imgbb upload failed: ${n.status} ${await n.text()}`);return(await n.json()).data.url}function hI(t){return async(e,r)=>{let i=new Vf({name:"jiranimo",version:"1.7.0"});i.tool("jiranimo_progress",{task_key:G.string(),message:G.string()},async({task_key:o,message:a})=>(t.reportProgress(o,a),{content:[{type:"text",text:"ok"}]})),i.tool("jiranimo_report_pr",{task_key:G.string(),pr_url:G.string(),pr_number:G.number().int(),branch_name:G.string()},async({task_key:o,pr_url:a,pr_number:s,branch_name:c})=>(t.reportPr(o,a,s,c),{content:[{type:"text",text:"PR recorded"}]})),i.tool("jiranimo_complete",{task_key:G.string(),summary:G.string()},async({task_key:o,summary:a})=>(t.completeViaAgent(o,a),{content:[{type:"text",text:"Task marked complete"}]})),i.tool("jiranimo_fail",{task_key:G.string(),error_message:G.string()},async({task_key:o,error_message:a})=>(t.failViaAgent(o,a),{content:[{type:"text",text:"Task marked failed"}]})),i.tool("jiranimo_screenshot_failed",{task_key:G.string(),reason:G.string()},async({task_key:o,reason:a})=>(t.reportScreenshotFailed(o,a),{content:[{type:"text",text:"Screenshot failure recorded"}]})),i.tool("jiranimo_upload_screenshot",{file_path:G.string()},async({file_path:o})=>({content:[{type:"text",text:await hZ(o)}]}));let n=new Kf({sessionIdGenerator:void 0});await i.connect(n),await n.handleRequest(e,r,e.body),r.on("close",()=>{n.close(),i.close()})}}function gI(t,e){let r={mcpServers:{jiranimo:{type:"http",url:`http://127.0.0.1:${e}/mcp`},playwright:{type:"stdio",command:"npx",args:["@playwright/mcp@latest","--headless"]}}};pZ(mI(t,".mcp.json"),JSON.stringify(r,null,2))}function vI(t){try{dZ(mI(t,".mcp.json"))}catch{}}import{appendFileSync as gZ,mkdirSync as vZ}from"node:fs";import{homedir as yZ}from"node:os";import{dirname as xZ,join as bZ,resolve as _Z}from"node:path";var wZ={level:"info",logHttpRequests:!1,logHttpBodies:!1,logClaudeRawOutput:!1},yI={debug:10,info:20,warn:30,error:40},t0=class t{config;filePath;scope;constructor(e,r){this.config=Lc(e);let i=e?.logsDir??_Z(yZ(),".jiranimo","logs");vZ(i,{recursive:!0}),this.filePath=bZ(i,"server.log"),this.scope=r}child(e){let r=this.scope?`${this.scope}:${e}`:e;return new t({logsDir:xZ(this.filePath),logging:this.config},r)}debug(e,r){this.write("debug",e,r)}info(e,r){this.write("info",e,r)}warn(e,r){this.write("warn",e,r)}error(e,r){this.write("error",e,r)}isConsoleLevelEnabled(e){return yI[e]>=yI[this.config.level]}getConfig(){return this.config}write(e,r,i){if(!this.isConsoleLevelEnabled(e))return;let n=kZ(e,this.scope,r,i),o=$Z(n);try{gZ(this.filePath,`${o}
450
+ `,e.enqueue(r.encode(o)),!0}catch(o){return this.onerror?.(o),!1}}handleUnsupportedRequest(){return this.onerror?.(new Error("Method not allowed.")),new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32e3,message:"Method not allowed."},id:null}),{status:405,headers:{Allow:"GET, POST, DELETE","Content-Type":"application/json"}})}async handlePostRequest(e,r){try{let i=e.headers.get("accept");if(!i?.includes("application/json")||!i.includes("text/event-stream"))return this.onerror?.(new Error("Not Acceptable: Client must accept both application/json and text/event-stream")),this.createJsonErrorResponse(406,-32e3,"Not Acceptable: Client must accept both application/json and text/event-stream");let n=e.headers.get("content-type");if(!n||!n.includes("application/json"))return this.onerror?.(new Error("Unsupported Media Type: Content-Type must be application/json")),this.createJsonErrorResponse(415,-32e3,"Unsupported Media Type: Content-Type must be application/json");let o={headers:Object.fromEntries(e.headers.entries()),url:new URL(e.url)},a;if(r?.parsedBody!==void 0)a=r.parsedBody;else try{a=await e.json()}catch{return this.onerror?.(new Error("Parse error: Invalid JSON")),this.createJsonErrorResponse(400,-32700,"Parse error: Invalid JSON")}let s;try{Array.isArray(a)?s=a.map(x=>Cy.parse(x)):s=[Cy.parse(a)]}catch{return this.onerror?.(new Error("Parse error: Invalid JSON-RPC message")),this.createJsonErrorResponse(400,-32700,"Parse error: Invalid JSON-RPC message")}let c=s.some(Ay);if(c){if(this._initialized&&this.sessionId!==void 0)return this.onerror?.(new Error("Invalid Request: Server already initialized")),this.createJsonErrorResponse(400,-32600,"Invalid Request: Server already initialized");if(s.length>1)return this.onerror?.(new Error("Invalid Request: Only one initialization request is allowed")),this.createJsonErrorResponse(400,-32600,"Invalid Request: Only one initialization request is allowed");this.sessionId=this.sessionIdGenerator?.(),this._initialized=!0,this.sessionId&&this._onsessioninitialized&&await Promise.resolve(this._onsessioninitialized(this.sessionId))}if(!c){let x=this.validateSession(e);if(x)return x;let $=this.validateProtocolVersion(e);if($)return $}if(!s.some(zn)){for(let x of s)this.onmessage?.(x,{authInfo:r?.authInfo,requestInfo:o});return new Response(null,{status:202})}let l=crypto.randomUUID(),p=s.find(x=>Ay(x)),d=p?p.params.protocolVersion:e.headers.get("mcp-protocol-version")??hS;if(this._enableJsonResponse)return new Promise(x=>{this._streamMapping.set(l,{resolveJson:x,cleanup:()=>{this._streamMapping.delete(l)}});for(let $ of s)zn($)&&this._requestToStreamMapping.set($.id,l);for(let $ of s)this.onmessage?.($,{authInfo:r?.authInfo,requestInfo:o})});let f=new TextEncoder,m,h=new ReadableStream({start:x=>{m=x},cancel:()=>{this._streamMapping.delete(l)}}),g={"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"};this.sessionId!==void 0&&(g["mcp-session-id"]=this.sessionId);for(let x of s)zn(x)&&(this._streamMapping.set(l,{controller:m,encoder:f,cleanup:()=>{this._streamMapping.delete(l);try{m.close()}catch{}}}),this._requestToStreamMapping.set(x.id,l));await this.writePrimingEvent(m,f,l,d);for(let x of s){let $,w;zn(x)&&this._eventStore&&d>="2025-11-25"&&($=()=>{this.closeSSEStream(x.id)},w=()=>{this.closeStandaloneSSEStream()}),this.onmessage?.(x,{authInfo:r?.authInfo,requestInfo:o,closeSSEStream:$,closeStandaloneSSEStream:w})}return new Response(h,{status:200,headers:g})}catch(i){return this.onerror?.(i),this.createJsonErrorResponse(400,-32700,"Parse error",{data:String(i)})}}async handleDeleteRequest(e){let r=this.validateSession(e);if(r)return r;let i=this.validateProtocolVersion(e);return i||(await Promise.resolve(this._onsessionclosed?.(this.sessionId)),await this.close(),new Response(null,{status:200}))}validateSession(e){if(this.sessionIdGenerator===void 0)return;if(!this._initialized)return this.onerror?.(new Error("Bad Request: Server not initialized")),this.createJsonErrorResponse(400,-32e3,"Bad Request: Server not initialized");let r=e.headers.get("mcp-session-id");if(!r)return this.onerror?.(new Error("Bad Request: Mcp-Session-Id header is required")),this.createJsonErrorResponse(400,-32e3,"Bad Request: Mcp-Session-Id header is required");if(r!==this.sessionId)return this.onerror?.(new Error("Session not found")),this.createJsonErrorResponse(404,-32001,"Session not found")}validateProtocolVersion(e){let r=e.headers.get("mcp-protocol-version");if(r!==null&&!Do.includes(r))return this.onerror?.(new Error(`Bad Request: Unsupported protocol version: ${r} (supported versions: ${Do.join(", ")})`)),this.createJsonErrorResponse(400,-32e3,`Bad Request: Unsupported protocol version: ${r} (supported versions: ${Do.join(", ")})`)}async close(){this._streamMapping.forEach(({cleanup:e})=>{e()}),this._streamMapping.clear(),this._requestResponseMap.clear(),this.onclose?.()}closeSSEStream(e){let r=this._requestToStreamMapping.get(e);if(!r)return;let i=this._streamMapping.get(r);i&&i.cleanup()}closeStandaloneSSEStream(){let e=this._streamMapping.get(this._standaloneSseStreamId);e&&e.cleanup()}async send(e,r){let i=r?.relatedRequestId;if((Vr(e)||Uo(e))&&(i=e.id),i===void 0){if(Vr(e)||Uo(e))throw new Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");let a;this._eventStore&&(a=await this._eventStore.storeEvent(this._standaloneSseStreamId,e));let s=this._streamMapping.get(this._standaloneSseStreamId);if(s===void 0)return;s.controller&&s.encoder&&this.writeSSEEvent(s.controller,s.encoder,e,a);return}let n=this._requestToStreamMapping.get(i);if(!n)throw new Error(`No connection established for request ID: ${String(i)}`);let o=this._streamMapping.get(n);if(!this._enableJsonResponse&&o?.controller&&o?.encoder){let a;this._eventStore&&(a=await this._eventStore.storeEvent(n,e)),this.writeSSEEvent(o.controller,o.encoder,e,a)}if(Vr(e)||Uo(e)){this._requestResponseMap.set(i,e);let a=Array.from(this._requestToStreamMapping.entries()).filter(([c,u])=>u===n).map(([c])=>c);if(a.every(c=>this._requestResponseMap.has(c))){if(!o)throw new Error(`No connection established for request ID: ${String(i)}`);if(this._enableJsonResponse&&o.resolveJson){let c={"Content-Type":"application/json"};this.sessionId!==void 0&&(c["mcp-session-id"]=this.sessionId);let u=a.map(l=>this._requestResponseMap.get(l));u.length===1?o.resolveJson(new Response(JSON.stringify(u[0]),{status:200,headers:c})):o.resolveJson(new Response(JSON.stringify(u),{status:200,headers:c}))}else o.cleanup();for(let c of a)this._requestResponseMap.delete(c),this._requestToStreamMapping.delete(c)}}}};var Kf=class{constructor(e={}){this._requestContext=new WeakMap,this._webStandardTransport=new Wf(e),this._requestListener=e0(async r=>{let i=this._requestContext.get(r);return this._webStandardTransport.handleRequest(r,{authInfo:i?.authInfo,parsedBody:i?.parsedBody})},{overrideGlobalObjects:!1})}get sessionId(){return this._webStandardTransport.sessionId}set onclose(e){this._webStandardTransport.onclose=e}get onclose(){return this._webStandardTransport.onclose}set onerror(e){this._webStandardTransport.onerror=e}get onerror(){return this._webStandardTransport.onerror}set onmessage(e){this._webStandardTransport.onmessage=e}get onmessage(){return this._webStandardTransport.onmessage}async start(){return this._webStandardTransport.start()}async close(){return this._webStandardTransport.close()}async send(e,r){return this._webStandardTransport.send(e,r)}async handleRequest(e,r,i){let n=e.auth;await e0(async a=>this._webStandardTransport.handleRequest(a,{authInfo:n,parsedBody:i}),{overrideGlobalObjects:!1})(e,r)}closeSSEStream(e){this._webStandardTransport.closeSSEStream(e)}closeStandaloneSSEStream(){this._webStandardTransport.closeStandaloneSSEStream()}};import{writeFileSync as pZ,unlinkSync as dZ,readFileSync as fZ}from"node:fs";import{join as mI}from"node:path";var mZ="7922f663cfcfe9b4b0c3119c2b61b7d8";async function hZ(t){let r=fZ(t).toString("base64"),i=new URLSearchParams({key:mZ,image:r}),n=await fetch("https://api.imgbb.com/1/upload",{method:"POST",body:i});if(!n.ok)throw new Error(`imgbb upload failed: ${n.status} ${await n.text()}`);return(await n.json()).data.url}function hI(t){return async(e,r)=>{let i=new Vf({name:"jiranimo",version:"1.9.0"});i.tool("jiranimo_progress",{task_key:G.string(),message:G.string()},async({task_key:o,message:a})=>(t.reportProgress(o,a),{content:[{type:"text",text:"ok"}]})),i.tool("jiranimo_report_pr",{task_key:G.string(),pr_url:G.string(),pr_number:G.number().int(),branch_name:G.string()},async({task_key:o,pr_url:a,pr_number:s,branch_name:c})=>(t.reportPr(o,a,s,c),{content:[{type:"text",text:"PR recorded"}]})),i.tool("jiranimo_complete",{task_key:G.string(),summary:G.string()},async({task_key:o,summary:a})=>(t.completeViaAgent(o,a),{content:[{type:"text",text:"Task marked complete"}]})),i.tool("jiranimo_fail",{task_key:G.string(),error_message:G.string()},async({task_key:o,error_message:a})=>(t.failViaAgent(o,a),{content:[{type:"text",text:"Task marked failed"}]})),i.tool("jiranimo_screenshot_failed",{task_key:G.string(),reason:G.string()},async({task_key:o,reason:a})=>(t.reportScreenshotFailed(o,a),{content:[{type:"text",text:"Screenshot failure recorded"}]})),i.tool("jiranimo_upload_screenshot",{file_path:G.string()},async({file_path:o})=>({content:[{type:"text",text:await hZ(o)}]}));let n=new Kf({sessionIdGenerator:void 0});await i.connect(n),await n.handleRequest(e,r,e.body),r.on("close",()=>{n.close(),i.close()})}}function gI(t,e){let r={mcpServers:{jiranimo:{type:"http",url:`http://127.0.0.1:${e}/mcp`},playwright:{type:"stdio",command:"npx",args:["@playwright/mcp@latest","--headless"]}}};pZ(mI(t,".mcp.json"),JSON.stringify(r,null,2))}function vI(t){try{dZ(mI(t,".mcp.json"))}catch{}}import{appendFileSync as gZ,mkdirSync as vZ}from"node:fs";import{homedir as yZ}from"node:os";import{dirname as xZ,join as bZ,resolve as _Z}from"node:path";var wZ={level:"info",logHttpRequests:!1,logHttpBodies:!1,logClaudeRawOutput:!1},yI={debug:10,info:20,warn:30,error:40},t0=class t{config;filePath;scope;constructor(e,r){this.config=Lc(e);let i=e?.logsDir??_Z(yZ(),".jiranimo","logs");vZ(i,{recursive:!0}),this.filePath=bZ(i,"server.log"),this.scope=r}child(e){let r=this.scope?`${this.scope}:${e}`:e;return new t({logsDir:xZ(this.filePath),logging:this.config},r)}debug(e,r){this.write("debug",e,r)}info(e,r){this.write("info",e,r)}warn(e,r){this.write("warn",e,r)}error(e,r){this.write("error",e,r)}isConsoleLevelEnabled(e){return yI[e]>=yI[this.config.level]}getConfig(){return this.config}write(e,r,i){if(!this.isConsoleLevelEnabled(e))return;let n=kZ(e,this.scope,r,i),o=$Z(n);try{gZ(this.filePath,`${o}
438
451
  `,"utf-8")}catch{}if(e==="error"){console.error(n);return}if(e==="warn"){console.warn(n);return}console.log(n)}};function $Z(t){return t.replace(/\u001B\[[0-9;]*m/g,"")}function Ln(t,e){return new t0(t,e)}function Lc(t){return{...wZ,...t?.logging??{}}}function xI(t){return[/chrome\/updater\//i,/GoogleUpdater/i,/VERBOSE\d?:chrome\//i].some(e=>e.test(t))}function kZ(t,e,r,i){let n=[new Date().toISOString(),t.toUpperCase()];e&&n.push(`[${e}]`),n.push(r);let o=SZ(i);return o&&n.push(o),n.join(" ")}function SZ(t){if(!t)return"";let e=Object.entries(t).filter(([,r])=>r!==void 0);return e.length===0?"":JSON.stringify(Object.fromEntries(e))}import{execFile as EZ}from"node:child_process";var TZ=10*1024*1024;function IZ(t){let e;try{e=new URL(t)}catch{throw new Error(`Invalid PR URL: ${t}`)}let r=e.pathname.match(/^\/([^/]+)\/([^/]+)\/pull\/(\d+)(?:\/|$)/);if(!r)throw new Error(`Unsupported PR URL: ${t}`);return{owner:r[1],repo:r[2],prNumber:Number(r[3])}}function _I(t,e){if(typeof e.id!="number")return;let r=e.updated_at??e.created_at;return r?`${t}:${e.id}:${r}`:void 0}function PZ(t){if(typeof t.id!="number"||typeof t.body!="string"||t.body.trim().length===0)return;let e=_I("review",t);if(e)return{id:t.id,fingerprint:e,kind:"review",author:t.user?.login||"unknown",body:t.body,path:typeof t.path=="string"?t.path:void 0,line:typeof t.line=="number"?t.line:typeof t.original_line=="number"?t.original_line:void 0,url:typeof t.html_url=="string"?t.html_url:void 0,created:typeof t.created_at=="string"?t.created_at:void 0,updated:typeof t.updated_at=="string"?t.updated_at:void 0}}function zZ(t){if(typeof t.id!="number"||typeof t.body!="string"||t.body.trim().length===0)return;let e=_I("conversation",t);if(e)return{id:t.id,fingerprint:e,kind:"conversation",author:t.user?.login||"unknown",body:t.body,url:typeof t.html_url=="string"?t.html_url:void 0,created:typeof t.created_at=="string"?t.created_at:void 0,updated:typeof t.updated_at=="string"?t.updated_at:void 0}}async function bI(t){let e=await new Promise((n,o)=>{EZ("gh",["api",t,"--paginate","--slurp"],{maxBuffer:TZ},(a,s)=>{if(a){o(a);return}n(s)})}),r=JSON.parse(e);return(Array.isArray(r)?r:[]).flatMap(n=>Array.isArray(n)?n:[])}async function r0(t,e=[]){let{owner:r,repo:i,prNumber:n}=IZ(t),o=new Set(e),[a,s]=await Promise.all([bI(`repos/${r}/${i}/pulls/${n}/comments`),bI(`repos/${r}/${i}/issues/${n}/comments`)]);return[...a.map(PZ),...s.map(zZ)].filter(c=>!!c&&!o.has(c.fingerprint)).sort((c,u)=>{let l=c.updated??c.created??"",p=u.updated??u.created??"";return l.localeCompare(p)})}var n0=2e4;async function i0(t,e,r,i){let n=jZ(t);if(!n.trim())return"No conversation content found in log.";let o=["You are summarizing an AI agent execution log for a developer following task progress.",`Task: "${e}"`,"","Write a concise but sufficiently detailed summary that covers:","1. The main steps the agent took","2. Important findings, decisions, or deviations","3. Any problems or failures encountered","4. The final outcome","","Rules:","- Use 5-12 bullet points.","- Focus on task-specific implementation details, not filler.","- Prefer what changed in the repo, what was discovered, and how the task was solved.","- Do not spend bullets on routine workflow boilerplate such as worktree setup/cleanup, generic git add/commit/push, PR creation, or Jira status/reporting unless something unusual happened there.","- Mention concrete files, commands, tools, or errors when they materially explain the implementation.","- Do not mention token usage, compaction, or transcript mechanics.","- Do not invent details that are not in the log.","","Execution log:",OZ(n)].join(`
439
452
  `),a=await qr({prompt:o,cwd:i,config:r});if(!a.resultText.trim())throw new Error("No compact log generated");return a.resultText.trim()}function jZ(t){let e=t.split(`
440
453
  `).filter(i=>i.trim()),r=[];for(let i of e){let n;try{n=JSON.parse(i)}catch{continue}switch(n.type){case"assistant":{let o=n.message?.content??[];for(let a of o)if(a.type==="text"&&a.text)r.push(`[Claude] ${a.text}`);else if(a.type==="tool_use"){let s=a.name??"unknown";if(CZ(s,a.input))continue;let c=RZ(a.input);r.push(`[Tool] ${s}${c?`(${c})`:""}`)}break}case"result":{let o=typeof n.cost_usd=="number"?` (cost: $${n.cost_usd.toFixed(4)})`:"",a=n.result?`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jiranimo",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "type": "module",
5
5
  "description": "Local automation server for the Jiranimo Jira extension",
6
6
  "main": "./dist/index.js",