epiq 1.16.1 → 1.17.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.
- package/dist/{Index-HOECJ74Y.js → Index-3QU43YBE.js} +5 -5
- package/dist/{args-5WLJ3JSM.js → args-D7OXPSAH.js} +1 -1
- package/dist/chunk-2KWSZBVJ.js +2 -0
- package/dist/chunk-7KCKIQRO.js +116 -0
- package/dist/chunk-ITVJCUOL.js +10 -0
- package/dist/chunk-SID42K4V.js +4 -0
- package/dist/gui/main.js +126 -126
- package/dist/index.js +1 -1
- package/dist/mcp.js +1 -1
- package/dist/server-JQTYRNQ7.js +2 -0
- package/dist/{virtual-nodes-W5WJQXFR.js → virtual-nodes-XI6JN2RD.js} +1 -1
- package/package.json +6 -6
- package/readme.md +13 -48
- package/dist/chunk-5E42KQM3.js +0 -10
- package/dist/chunk-GOROXQFX.js +0 -116
- package/dist/chunk-SCSGIFUR.js +0 -2
- package/dist/server-JWNOKRFL.js +0 -2
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
process.env.NODE_ENV||="production";await import("./Index-
|
|
2
|
+
import{a as o}from"./chunk-SID42K4V.js";o();process.env.NODE_ENV||="production";await import("./Index-3QU43YBE.js");
|
package/dist/mcp.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
process.env.EPIQ_MCP="true";process.env.NODE_ENV||="production";console.log=console.error;console.info=console.error;console.debug=console.error;console.warn=console.error;var{applyActorNameArgument:
|
|
2
|
+
import{a as r}from"./chunk-SID42K4V.js";r();process.env.EPIQ_MCP="true";process.env.NODE_ENV||="production";console.log=console.error;console.info=console.error;console.debug=console.error;console.warn=console.error;var{applyActorNameArgument:s}=await import("./actor-env-F6R6NBSD.js"),{isFail:t}=await import("./result-types-RM3ASLV3.js"),{parseMcpArgs:n}=await import("./args-D7OXPSAH.js"),e=n(process.argv.slice(2));e.kind==="error"&&(console.error(e.message),process.exit(1));e.kind==="print"&&(process.stdout.write(e.text),process.exit(0));if(e.kind==="name"){let o=s(e.name,"The name argument");t(o)&&(console.error(o.message),process.exit(1))}var{setVirtualNodesEnabled:i}=await import("./virtual-nodes-XI6JN2RD.js");i(!1);var{startMcpServer:a}=await import("./server-JQTYRNQ7.js");await a();
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{$ as Te,B as ne,C as se,D as re,E as ae,F as le,G as ce,H as pe,J as de,K as me,L as ue,M as ge,N as he,O as fe,P as we,Q as be,R as _e,S as ye,U as Re,X as Ie,Y as ke,Z as qe,_ as Se,aa as ve,b as A,ba as Ee,ca as je,da as xe,ea as Ae,fa as Ne,ga as Ce,ha as Le,i as F,ia as Pe,ja as Me,k as z,ka as Oe,l as X,la as Ge,m as W,ma as Ue,n as Y,o as J,oa as De,p as Q,pa as $e,q as V,qa as He,r as Z,s as ee,sa as Ke,t as te,u as oe,x as ie}from"./chunk-ITVJCUOL.js";import{Sa as I,b as M,ca as $,ec as K,h as O,ja as H,l as G,m as U,vc as B,wb as k,x as D}from"./chunk-7KCKIQRO.js";import{a as P}from"./chunk-WUCQWBQE.js";import{b as p,c,d as l}from"./chunk-5RIQL7P4.js";import{McpServer as gt}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as ht}from"@modelcontextprotocol/sdk/server/stdio.js";import{ListToolsRequestSchema as ft}from"@modelcontextprotocol/sdk/types.js";import{z as e}from"zod";var ct=t=>{let o=new Map,n=[t.nodes,t.contributors,t.tags,t.goals,t.comments,t.attachments];for(let s of n)for(let r of Object.keys(s??{})){let a=I(r),d=o.get(a);d?d.includes(r)||d.push(r):o.set(a,[r])}return{byRef:o,goals:new Set(Object.keys(t.goals??{}))}},Be=()=>{let t=B();return l(t)?null:ct(t.value)},Fe="goal",ze=t=>/(Id|Ids)$/.test(t)||t==="sibling"||t==="idOrRef"||t===Fe,S=t=>typeof t=="string"&&t.length===7,T=t=>Array.isArray(t)?t.some(T):!t||typeof t!="object"?!1:Object.entries(t).some(([o,n])=>ze(o)?S(n)||Array.isArray(n)&&n.some(S):T(n)),Xe=(t,o,n=!1)=>{let s=(t.byRef.get(o.toUpperCase())??[]).filter(r=>!n||t.goals.has(r));return s.length>1?p(`Ref ${o} is ambiguous: use one of ${s.join(", ")}`):c("Resolved ref",s[0]??o)},We=(t,o)=>{let n=[];for(let s of t){let r=o(s);if(l(r))return r;n.push(r.value)}return c("Expanded",n)},Ye=(t,o)=>S(o)?Xe(t,o):Array.isArray(o)?We(o,n=>Ye(t,n)):c("Kept",o),N=(t,o)=>{if(Array.isArray(o))return We(o,s=>N(t,s));if(!o||typeof o!="object")return c("Kept",o);let n={};for(let[s,r]of Object.entries(o)){let a=s===Fe&&S(r)?Xe(t,r,!0):ze(s)?Ye(t,r):N(t,r);if(l(a))return a;n[s]=a.value}return c("Expanded",n)},Je=t=>{let o=Be();return o?N(o,t):c("No state to resolve against",t)},q=(t,o)=>{if(typeof o=="string"){let s=t.byRef.get(I(o));return s?.length===1&&s[0]===o?I(o):o}if(Array.isArray(o))return o.map(s=>q(t,s));if(!o||typeof o!="object")return o;let n={};for(let[s,r]of Object.entries(o))n[q(t,s)]=q(t,r);return typeof n.id=="string"&&n.id===n.ref&&delete n.ref,n},Qe=t=>{let o=Be();return o?q(o,t):t};import{ulid as pt}from"ulid";var Ve=[{name:"userName",question:"the name they want to appear under on the board"},{name:"preferredEditor",question:'the command that opens a file for editing, e.g. "vim", "nano" or "code --wait"'},{name:"autoSync",question:"whether the TUI and GUI should sync with the git remote on their own (true or false)"}],Ze=t=>t.trim().toLowerCase(),et=async(t={})=>{let o=G();if(l(o))return p(o.message);let n=o.value,s=t.userName?.trim(),r=(process.env[P]??"").trim();if(s&&r&&Ze(s)===Ze(r))return p(`userName "${s}" is this agent's own identity. Pass the user's name, the one they want the TUI and GUI to write as.`);let a={userName:s||void 0,preferredEditor:t.preferredEditor?.trim()||void 0,autoSync:t.autoSync},d={userName:n.userName?.trim()||void 0,preferredEditor:n.preferredEditor?.trim()||void 0,autoSync:typeof n.autoSync=="boolean"?n.autoSync:void 0};for(let m of Ve){let g=a[m.name],b=d[m.name];if(!(g===void 0||b===void 0||g===b))return p(`This machine is already set up with ${m.name} ${JSON.stringify(b)}; epiq_project_init does not change it. Call again without ${m.name}, or let the user change it in the TUI with :config.`)}let u={};if(a.userName&&!d.userName&&(u.userName=a.userName),a.preferredEditor&&!d.preferredEditor&&(u.preferredEditor=a.preferredEditor),a.autoSync!==void 0&&d.autoSync===void 0&&(u.autoSync=a.autoSync),(u.userName||d.userName)&&!n.userId&&(u.userId=pt()),Object.keys(u).length>0){let m=U(u);if(l(m))return p(m.message)}let E={...n,...u},j=Ve.filter(m=>{let g=E[m.name];return typeof g=="boolean"?!1:!g?.trim()});if(j.length>0){let m=j.map(R=>`${R.name} (${R.question})`).join("; "),g=j.some(R=>R.name==="userName")?await F(t.repoRoot??process.cwd()):null,b=g?` This repository's git user.name is ${JSON.stringify(g)} \u2014 offer it as the default for userName, and use it only if the user agrees.`:"";return p(`Setup incomplete. Ask the user for: ${m}.${b} Then call epiq_project_init again with the answers; what was given so far is saved.`)}let _={userId:E.userId??"",userName:E.userName??""};if(!_.userId||!_.userName)return p("Missing Epiq user id");let x=await K({cwd:t.repoRoot??process.cwd(),user:_});if(l(x))return x;let{defaultEvents:It,...y}=x.value,L=$({root:y.repoRoot});l(L)&&M.info(L.message);let at=y.warnings.length===0?"Project initialized":`Project initialized, with warnings: ${y.warnings.join(" ")}`;return c(at,{...y,user:_})};import v from"node:fs";import tt from"node:path";var h=".claude/skills/epiq/SKILL.md",C="---\nname: epiq\ndescription: Workflow for working an epiq issue board through the epiq MCP \u2014 take an identity, sync on demand, keep tickets small and their status, tags and comments current.\n---\n\n# Epiq board workflow\n\n- **Use the `epiq_*` MCP tools** for every board read and write \u2014 never the CLI, hand edits, or the state branch directly.\n- **Never touch the state branch** \u2014 no checkout, no edits to its files by hand or by script. The event log is the system of record; a manual edit can corrupt history for good.\n- Move tickets along the board with correct status\n- **The MCP never syncs on its own:** call `epiq_sync` to pull or publish.\n- **Take an identity before your first write** with `epiq_actor_assume`, e.g. `claude/peter`. Reuse one from `epiq_contributor_list` \u2014 names are permanent. If other agents may be running, ask which name is free, rather than just picking one.\n- **One MCP server per session** \u2014 the name binds to the process; a restart forgets it. Every session needs a server process of its own, so agents and the human stay apart.\n- **Keep tickets reasonably scoped**; put a small follow-up on its existing ticket instead of a new one.\n- **Read a ticket's comments before starting.**\n- **Board and lane descriptions describe team conventions** (`boardDescription`, `laneDescription` in responses): follow them unless they conflict with your user's instructions.\n- **Picking up a ticket:** assign yourself and move it to the in-progress lane. Ask before taking one assigned to someone else.\n- **Keep status, tags and description current**; reuse existing tags.\n- **Goals group tickets that serve one outcome, on any board.** Put a ticket in one with `epiq_issue_goal_set` (`goal`: its name, created if new, or null to take it out). A ticket has at most one goal; `epiq_goal_list` shows every goal and its progress. A ticket counts as done for its goal once closed, or in a lane named Done or marked done (`epiq_swimlane_rule_set`). A goal closes once all its tickets are closed, and reopens with any of them.\n- **Start bigger work with a goal.** Before a feature or larger task, create a goal (`epiq_goal_create`), describe its intent and expectations in a few lines, not its implementation (`epiq_goal_description_edit`), then break the work into tickets in that goal.\n- **Comment** on deviations, blockers, and the outcome when done.\n- **Prompt user for closing tickets and big decisions**\n- **Park decisions that are the user's to make.** A design fork, a change with blast radius beyond its ticket, anything you'd want a second opinion on \u2014 file a ticket tagged `human-input-needed` naming the options and the trade-offs, then carry on with whatever doesn't depend on the answer.\n- **Link commits** with an `Epiq-Ref: <ref>` line in the commit body. The MCP names every ticket by its 7-character ref; use that. Several tickets go on one line, comma-separated. A subject starting with the ref also links, but puts the ref in everyone's history.\n- *If you decide to take a new approach*, add a `fork` tag, and document the fork in a comment, also, update the description to reflect the new plan.\n- **Attachments are permanent** in every clone (max 500 KB): use with moderation, make sure images are cropped and compressed. An oversized image is permanent, for everybody.\n- **Be concise, use clear language.** Titles, descriptions, and comments should be scannable, easy to understand, not essays.\n- **When a ticket is done, Document the outcome in a comment starting with \"Solution:\"**. Use that exact prefix as the comment's opening line.\n- **The git user is the sole author of every commit you write.** Commit under the repo's configured user.name / user.email and nothing else: no Co-Authored-By: line, no Claude-Session: line, no \u{1F916} Generated with footer, and never an --author override naming Claude or a tool.\n- On rebase or merge, make sure Epiq refs survive. Dropping them from commit messages breaks commit linking.\n";var ot=async(t={})=>{let o=await O(t.repoRoot??process.cwd());if(l(o))return p(o.message);let n=tt.join(o.value,h);if(v.existsSync(n)){if(v.readFileSync(n,"utf8")===C)return c("Skill already installed and up to date",{path:n,written:!1});if(!t.force)return p(`${h} already exists and differs from the bundled skill. Pass force:true to overwrite it.`)}try{v.mkdirSync(tt.dirname(n),{recursive:!0}),v.writeFileSync(n,C,"utf8")}catch(s){return p(s instanceof Error?`Failed to write ${h}: ${s.message}`:`Failed to write ${h}`)}return c(`Installed skill at ${h}`,{path:n,written:!0})};var w=t=>({isError:l(t),content:[{type:"text",text:JSON.stringify(l(t)?{message:t.message}:{message:t.message,value:Qe(t.value)})}]}),wt=async t=>{if(!T(t))return c("No refs",t);let o=await z(t.repoRoot);return l(o)?c("Unbooted",t):Je(t)},i=t=>o=>A(async()=>{let n=await wt(o);return w(l(n)?n:await t(n.value))}),bt="A board's or lane's description (boardDescription, laneDescription) holds the team's conventions for it; follow them unless they conflict with your user's instructions. Replies name nodes by their 7-character ref, and every id input takes one.",_t=t=>({...t,tools:t.tools.map(({inputSchema:{$schema:o,...n},...s})=>({...s,inputSchema:n}))}),yt=t=>{let o=t.server,n=o.setRequestHandler.bind(o);o.setRequestHandler=((s,r)=>n(s,s===ft?async(a,d)=>_t(await r(a,d)):r))},Rt=()=>{let t=new gt({name:"epiq",version:"0.2.30"},{instructions:bt});return yt(t),t.registerTool("epiq_state_get",{description:"Get the full current Epiq state, including nodes and event log",inputSchema:e.object({repoRoot:e.string().optional()})},i(ie)),t.registerTool("epiq_issue_list",{description:"List Epiq issues: id, title, swimlane title and id, goal, tag and assignee names. Narrow with boardId, swimlaneId, tag, assignee or query (matched against title and description). brief: false adds descriptions and the rest; read one ticket with epiq_issue_get instead.",inputSchema:e.object({repoRoot:e.string().optional(),includeClosed:e.boolean().optional(),boardId:e.string().optional(),swimlaneId:e.string().optional(),tag:e.string().optional(),assignee:e.string().optional(),query:e.string().optional(),brief:e.boolean().optional()})},i(o=>re({...o,brief:o.brief??!0}))),t.registerTool("epiq_issue_get",{description:"Get one Epiq issue by ref or id, with its comments in log order: the latest three whole, earlier ones by their first three lines (olderComments says so); allComments returns every one whole. When you know which issue you want, prefer it to listing the board or epiq_state_get.",inputSchema:e.object({idOrRef:e.string().min(1),allComments:e.boolean().optional(),repoRoot:e.string().optional()})},i(se)),t.registerTool("epiq_issue_stats",{description:"Code stats for one issue, from the commits whose subject carries its ref: how big the change is, how it is spread, and how much rewrote its own work. Reads git only, so it is safe while other work is in flight. No such commits reads as zero size, which is not the same as no work: a commit only links if its subject opens with the ref.",inputSchema:e.object({idOrRef:e.string().min(1),repoRoot:e.string().optional()})},async o=>w(await Re(o))),t.registerTool("epiq_board_list",{description:"List Epiq boards",inputSchema:e.object({repoRoot:e.string().optional()})},i(ue)),t.registerTool("epiq_board_create",{description:"Create an Epiq board. It comes out without swimlanes; add them with epiq_swimlane_create. Refused while time travelling.",inputSchema:e.object({repoRoot:e.string().optional(),title:e.string().min(1).max(300)})},i(ge)),t.registerTool("epiq_swimlane_list",{description:"List Epiq swimlanes",inputSchema:e.object({repoRoot:e.string().optional(),boardId:e.string().optional()})},i(fe)),t.registerTool("epiq_swimlane_create",{description:"Create an Epiq swimlane on a board",inputSchema:e.object({repoRoot:e.string().optional(),boardId:e.string().min(1),title:e.string().min(1).max(300)})},i(we)),t.registerTool("epiq_swimlane_title_edit",{description:"Edit an Epiq swimlane title",inputSchema:e.object({repoRoot:e.string().optional(),swimlaneId:e.string().min(1),title:e.string().min(1).max(300)})},i(be)),t.registerTool("epiq_board_title_edit",{description:"Edit an Epiq board title. Refused while time travelling.",inputSchema:e.object({repoRoot:e.string().optional(),boardId:e.string().min(1),title:e.string().min(1).max(300)})},i(he)),t.registerTool("epiq_swimlane_description_edit",{description:"Edit a swimlane's markdown description: the conventions for work in that lane. Empty clears it.",inputSchema:e.object({repoRoot:e.string().optional(),swimlaneId:e.string().min(1),description:e.string().max(k("swimlane"))})},i(W)),t.registerTool("epiq_swimlane_rule_set",{description:"Turn a swimlane rule on or off. The one rule is `done`: an open ticket in a lane with it counts as done for its goal. A lane named Done always counts.",inputSchema:e.object({repoRoot:e.string().optional(),swimlaneId:e.string().min(1),rule:e.enum(H),on:e.boolean()})},i(te)),t.registerTool("epiq_board_description_edit",{description:"Edit a board's markdown description: the conventions for work on that board. Empty clears it. Refused while time travelling.",inputSchema:e.object({repoRoot:e.string().optional(),boardId:e.string().min(1),description:e.string().max(k("board"))})},i(X)),t.registerTool("epiq_swimlane_move",{description:"Move/reorder an Epiq swimlane, optionally to another board",inputSchema:e.object({repoRoot:e.string().optional(),swimlaneId:e.string().min(1),boardId:e.string().min(1),position:e.discriminatedUnion("at",[e.object({at:e.literal("start")}),e.object({at:e.literal("end")}),e.object({at:e.literal("before"),sibling:e.string().min(1)}),e.object({at:e.literal("after"),sibling:e.string().min(1)})]).optional()})},i(_e)),t.registerTool("epiq_swimlane_delete",{description:"Delete (archive) an Epiq swimlane. This also archives every issue contained in it.",inputSchema:e.object({repoRoot:e.string().optional(),swimlaneId:e.string().min(1)})},i(ye)),t.registerTool("epiq_issue_create",{description:"Create an Epiq issue in a swimlane: parentId is the swimlane id (epiq_swimlane_list), never a board id. Optionally set description, tags, and assignees atomically in the same call instead of separate follow-up edits.",inputSchema:e.object({title:e.string().min(1).max(300),parentId:e.string().min(1),repoRoot:e.string().optional(),description:e.string().max(2e4).optional(),tagNames:e.array(e.string().max(60)).max(20).optional(),assigneeNames:e.array(e.string().max(80)).max(20).optional()})},i(ae)),t.registerTool("epiq_issue_description_edit",{description:"Edit the markdown description of an Epiq issue",inputSchema:e.object({issueId:e.string().min(1),description:e.string().max(2e4),repoRoot:e.string().optional()})},i(de)),t.registerTool("epiq_issue_title_edit",{description:"Edit the title of an Epiq issue",inputSchema:e.object({issueId:e.string().min(1),title:e.string().min(1).max(300),repoRoot:e.string().optional()})},i(me)),t.registerTool("epiq_issue_tag_add",{description:"Add a tag to an Epiq issue, or to many at once with issueIds, creating the tag if it does not exist",inputSchema:e.object({issueId:e.string().min(1).optional(),issueIds:e.array(e.string().min(1)).min(1).max(200).optional(),tagName:e.string().min(1).max(60),repoRoot:e.string().optional()})},i(ke)),t.registerTool("epiq_issue_goal_set",{description:"Put an Epiq issue in a goal (a group of tickets serving one outcome, on any board), by the goal's name or id, creating the goal if there is none of that name; null takes it out. One goal per ticket. epiq_goal_list lists every goal with its progress.",inputSchema:e.object({issueId:e.string().min(1),goal:e.string().min(1).nullable(),repoRoot:e.string().optional()})},i(Q)),t.registerTool("epiq_goal_list",{description:"List the goals in the project: each a group of tickets serving one outcome, with done/total (done is closed, or in a done lane: see epiq_swimlane_rule_set), closed once all its tickets are, and the boards its tickets are on. Open goals first. Put a ticket in one with epiq_issue_goal_set; epiq_goal_stats gives its code stats.",inputSchema:e.object({repoRoot:e.string().optional()})},i(J)),t.registerTool("epiq_goal_stats",{description:"Code stats for a goal, by its name or id: epiq_issue_stats over every commit of every ticket in it, each commit counted once. Its `ref` field holds the goal id, not a ticket ref.",inputSchema:e.object({goal:e.string().min(1),repoRoot:e.string().optional()})},i(Ie)),t.registerTool("epiq_goal_create",{description:"Create a goal with no tickets yet. To create one and put a ticket in it at once, use epiq_issue_goal_set with the new name.",inputSchema:e.object({name:e.string().min(1),repoRoot:e.string().optional()})},i(V)),t.registerTool("epiq_goal_rename",{description:"Rename a goal; its colour and tickets stay. Ids are in epiq_goal_list.",inputSchema:e.object({goalId:e.string().min(1),name:e.string().min(1),repoRoot:e.string().optional()})},i(Z)),t.registerTool("epiq_goal_description_edit",{description:"Edit a goal's markdown description: what it is for and when it is done. Empty clears it. epiq_goal_list returns it.",inputSchema:e.object({goal:e.string().min(1),description:e.string().max(k("goal")),repoRoot:e.string().optional()})},i(Y)),t.registerTool("epiq_goal_remove",{description:"Remove a goal; its tickets stay where they are, in no goal. Ids are in epiq_goal_list.",inputSchema:e.object({goalId:e.string().min(1),repoRoot:e.string().optional()})},i(ee)),t.registerTool("epiq_issue_tag_remove",{description:"Remove a tag from an Epiq issue",inputSchema:e.object({issueId:e.string().min(1),tagId:e.string().min(1),repoRoot:e.string().optional()})},i(Te)),t.registerTool("epiq_contributor_list",{description:"List people who can be assigned: everyone who has authored an event (optionally scoped to one board) unioned with the contributor registry. Use these ids with epiq_issue_assignee_add rather than assigning by name.",inputSchema:e.object({boardId:e.string().min(1).optional(),repoRoot:e.string().optional()})},i(Ae)),t.registerTool("epiq_contributor_remove",{description:"Remove an external contributor from the assignee suggestion lists. Their id and every reference to it survive, so existing assignments stay intact and the event log is untouched. Refused for anyone who has authored events, since their name is in the log regardless.",inputSchema:e.object({contributorId:e.string().min(1),repoRoot:e.string().optional()})},i(je)),t.registerTool("epiq_tag_remove",{description:"Delete a tag from the whole workspace. It disappears from every ticket, picker and filter, but its id and history stay in the event log, and the name is free for a new tag.",inputSchema:e.object({tagId:e.string().min(1),repoRoot:e.string().optional()})},i(qe)),t.registerTool("epiq_tag_restore",{description:"Put back a tag deleted with epiq_tag_remove, on every ticket that still carried it.",inputSchema:e.object({tagId:e.string().min(1),repoRoot:e.string().optional()})},i(Se)),t.registerTool("epiq_contributor_restore",{description:"Put a contributor removed with epiq_contributor_remove back into the suggestion lists, under the name they were created with.",inputSchema:e.object({contributorId:e.string().min(1),repoRoot:e.string().optional()})},i(xe)),t.registerTool("epiq_issue_assignee_add",{description:"Assign a contributor to an Epiq issue: self: true for yourself, else preferably assigneeId (epiq_contributor_list; fails if unknown). assigneeName matches an existing contributor by name; with createUnlinked it adds someone new as an external assignee.",inputSchema:e.object({issueId:e.string().min(1),assigneeId:e.string().min(1).optional(),self:e.boolean().optional(),assigneeName:e.string().min(1).max(80).optional(),createUnlinked:e.boolean().optional(),repoRoot:e.string().optional()})},i(Ee)),t.registerTool("epiq_issue_assignee_remove",{description:"Remove an assignee from an Epiq issue",inputSchema:e.object({issueId:e.string().min(1),assigneeId:e.string().min(1),repoRoot:e.string().optional()})},i(Ne)),t.registerTool("epiq_issue_comment_add",{description:"Add a comment to an Epiq issue",inputSchema:e.object({issueId:e.string().min(1),body:e.string().min(1).max(4e3),repoRoot:e.string().optional()})},i(Ce)),t.registerTool("epiq_issue_attachment_add",{description:"Attach an image file to an Epiq issue. Returns `markdown` \u2014 paste it into a comment or description body to render the image inline.",inputSchema:e.object({issueId:e.string().min(1),filePath:e.string().min(1),name:e.string().min(1).max(200).optional(),repoRoot:e.string().optional()})},i(oe)),t.registerTool("epiq_issue_comment_delete",{description:"Delete a comment from an Epiq issue",inputSchema:e.object({commentId:e.string().min(1),repoRoot:e.string().optional()})},i(Le)),t.registerTool("epiq_issue_comment_edit",{description:"Replace the body of one of your own comments on an Epiq issue",inputSchema:e.object({commentId:e.string().min(1),body:e.string().min(1).max(4e3),repoRoot:e.string().optional()})},i(Pe)),t.registerTool("epiq_issue_reopen",{description:"Reopen a closed Epiq issue, restoring it to its previous swimlane",inputSchema:e.object({issueId:e.string().min(1),repoRoot:e.string().optional()})},i(ce)),t.registerTool("epiq_issue_close",{description:"Close an Epiq issue, or many at once with issueIds \u2014 one call, an outcome per ticket",inputSchema:e.object({issueId:e.string().min(1).optional(),issueIds:e.array(e.string().min(1)).min(1).max(200).optional(),repoRoot:e.string().optional()})},i(le)),t.registerTool("epiq_issue_move",{description:"Move an Epiq issue to another swimlane (parentId is the swimlane id), or many at once with issueIds \u2014 one call, an outcome per ticket",inputSchema:e.object({issueId:e.string().min(1).optional(),issueIds:e.array(e.string().min(1)).min(1).max(200).optional(),parentId:e.string().min(1),position:e.discriminatedUnion("at",[e.object({at:e.literal("start")}),e.object({at:e.literal("end")}),e.object({at:e.literal("before"),sibling:e.string().min(1)}),e.object({at:e.literal("after"),sibling:e.string().min(1)})]).optional(),repoRoot:e.string().optional()})},i(pe)),t.registerTool("epiq_actor_assume",{description:'Take a board identity for this session, so your writes are attributed to you, not the user: the name you announced: the model provider, a slash, then a short human first name, e.g. "claude/peter". Takes effect on the next write. A name the server was launched with wins; re-assuming is harmless. Reuse an existing agent identity (epiq_contributor_list) before inventing one: a name is permanent once it has authored anything.',inputSchema:e.object({name:e.string().min(1).max(80),repoRoot:e.string().optional()})},i(ve)),t.registerTool("epiq_contributor_email_link",{description:"Bind a git author address to a contributor, so their commits and board activity read as one person. Defaults to you; pass contributorId for a colleague who does not use epiq. The repository's configured git address links itself on your first write, so this is for older addresses and other people. An address two contributors claim resolves to neither.",inputSchema:e.object({email:e.string().min(1).max(254),contributorId:e.string().min(1).optional(),repoRoot:e.string().optional()})},i(Me)),t.registerTool("epiq_contributor_email_unlink",{description:"Retract a link between a git address and a contributor; only its writer or the contributor it names may. The address stops resolving; the event stays in the log.",inputSchema:e.object({email:e.string().min(1).max(254),contributorId:e.string().min(1).optional(),repoRoot:e.string().optional()})},i(Oe)),t.registerTool("epiq_contributor_email_suggest",{description:"Unclaimed addresses in this repository's history that look like yours; until linked, their commits show a raw git name. Show the list and link only what the user confirms: each link is permanent and replicates to every clone.",inputSchema:e.object({repoRoot:e.string().optional()})},i(Ue)),t.registerTool("epiq_contributor_email_list",{description:"Every git address bound to a contributor, and who holds it. One with several claimants is flagged contested and resolves to none.",inputSchema:e.object({repoRoot:e.string().optional()})},i(Ge)),t.registerTool("epiq_project_init",{description:'Set up a new Epiq board in the git repository at repoRoot (default: the current directory), which must have no uncommitted changes. The first time on a machine, ask the user for userName (how they appear on the board: theirs, never your agent identity), preferredEditor (e.g. "vim" or "code --wait") and autoSync (whether the TUI and GUI sync on their own); without them it fails naming what is missing, keeping what was given. Answers the machine already has stay. Commits the state branch and .epiq/project.json and tries to push both; a failed push is a warning.',inputSchema:e.object({repoRoot:e.string().optional(),userName:e.string().min(1).max(80).optional(),preferredEditor:e.string().min(1).max(200).optional(),autoSync:e.boolean().optional()})},i(et)),t.registerTool("epiq_skill_install",{description:"Write the Epiq workflow skill to .claude/skills/epiq/SKILL.md in the repository, so agents there follow the board rules. In a new project, run it after epiq_project_init, which refuses the uncommitted file; on an existing board, any time. A differing file is overwritten only with force: true.",inputSchema:e.object({repoRoot:e.string().optional(),force:e.boolean().optional()})},i(ot)),t.registerTool("epiq_sync",{description:"Pull and push Epiq state with the git remote, then fetch the repos connected with epiq_repo_connect. Other tools never sync: call this to see or publish remote changes.",inputSchema:e.object({repoRoot:e.string().optional()})},async o=>w(await Ke(await A(()=>ne(o)),o))),t.registerTool("epiq_repo_list",{description:"List the repos connected to the board, whose commits count as its own: each one's clone status, size on disk, last fetch error and whether it is past the board's limit (and so not fetched), plus their total on this machine.",inputSchema:e.object({repoRoot:e.string().optional()})},async o=>w(await De(o))),t.registerTool("epiq_repo_connect",{description:"Connect another repo to the board by URL (https://, ssh://, git:// or user@host:path; never a local path), so its commits prefixed with a ticket's ref link to it; or change a connected repo's options, which applies at once. Board-wide: every contributor's epiq downloads it on their next sync. The first call clones to staging and returns a preview (size, host, default branch, latest commit, commits naming this board's tickets \u2014 none suggests the wrong repo), writing nothing; show it to the user, and only once they agree call again with confirm: true and the same options. branches: default or all. since: how much history to clone, board (default), all, or YYYY-MM-DD. label: its name on the board, the URL's basename unless set; empty resets it. Uses your git credentials: ssh-agent or a credential helper must already work.",inputSchema:e.object({repoRoot:e.string().optional(),url:e.string().min(1).max(D),branches:e.enum(["default","all"]).optional(),since:e.string().max(20).optional(),label:e.string().max(24).optional(),confirm:e.boolean().optional()})},async o=>w(await $e(o))),t.registerTool("epiq_repo_disconnect",{description:"Disconnect a repo by its label (when no other repo shares it), id or URL, and delete this machine's clone of it.",inputSchema:e.object({repoRoot:e.string().optional(),repo:e.string().min(1).max(2e3)})},async o=>w(await He(o))),t},mo=async()=>{let t=Rt(),o=new ht;await t.connect(o)};export{bt as MCP_INSTRUCTIONS,Rt as createMcpServer,w as resultJson,mo as startMcpServer};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{
|
|
2
|
+
import{Gc as a,Hc as b,Ic as c,Jc as d,Kc as e,Lc as f}from"./chunk-7KCKIQRO.js";import"./chunk-WUCQWBQE.js";import"./chunk-5RIQL7P4.js";export{e as areVirtualNodesEnabled,a as createOrUpdateVirtualField,b as createOrUpdateVirtualFieldList,c as materializeTicketVirtualNodes,f as materializeVirtualNodes,d as setVirtualNodesEnabled};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "epiq",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "EPIQ - ergonomic, distributed CLI-first issue tracker TUI ready for the agentic era",
|
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
"epiq-mcp": "dist/mcp.js"
|
|
24
24
|
},
|
|
25
25
|
"engines": {
|
|
26
|
-
"node": ">=
|
|
26
|
+
"node": ">=22"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsc --noEmit",
|
|
30
30
|
"build:gui:types": "tsc --noEmit -p source/gui/client/tsconfig.json",
|
|
31
31
|
"build:gui": "npm run build:gui:types && node source/scripts/build-gui.mjs",
|
|
32
32
|
"build:gui:dev": "EPIQ_GUI_DEV_BUILD=1 npm run build:gui",
|
|
33
|
-
"build:npm": "node source/scripts/write-version.mjs && node source/scripts/write-skill.mjs && rm -rf ./dist && npm run build:gui && esbuild index=source/cli.ts mcp=source/mcp/index.ts --bundle --splitting --packages=external --platform=node --format=esm --target=
|
|
33
|
+
"build:npm": "node source/scripts/write-version.mjs && node source/scripts/write-skill.mjs && rm -rf ./dist && npm run build:gui && esbuild index=source/cli.ts mcp=source/mcp/index.ts --bundle --splitting --packages=external --platform=node --format=esm --target=node22 --minify --outdir=dist --banner:js='#!/usr/bin/env node'",
|
|
34
34
|
"build:sea:bundle": "node source/scripts/write-version.mjs && node source/scripts/write-skill.mjs && node source/scripts/build-sea.mjs",
|
|
35
35
|
"build:sea": "node source/scripts/build-sea.mjs",
|
|
36
36
|
"build:publish": "npm run build:npm",
|
|
@@ -73,10 +73,10 @@
|
|
|
73
73
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
74
74
|
"@pierre/diffs": "^1.3.6",
|
|
75
75
|
"@xterm/headless": "^6.0.0",
|
|
76
|
-
"ink": "^
|
|
76
|
+
"ink": "^8.0.0",
|
|
77
77
|
"meow": "^14.1.0",
|
|
78
|
-
"react": "^19.
|
|
79
|
-
"react-dom": "^19.
|
|
78
|
+
"react": "^19.3.1",
|
|
79
|
+
"react-dom": "^19.3.1",
|
|
80
80
|
"react-markdown": "^10.1.0",
|
|
81
81
|
"react-router-dom": "^7.18.4",
|
|
82
82
|
"remark-breaks": "^4.0.0",
|
package/readme.md
CHANGED
|
@@ -1,79 +1,45 @@
|
|
|
1
|
-
# Epiq
|
|
1
|
+
# Epiq - issue tracking as code
|
|
2
2
|
|
|
3
|
-
Epiq is a self-hosted issue tracker
|
|
3
|
+
Epiq is a self-hosted issue tracker persisting state as an immutable event log in your repository, versioned and synchronized via Git.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Epiq offers issue tracking as an integrated part of the development environment with workflow replay, inline code-diffs, filtering, and a tight code integration.
|
|
6
6
|
|
|
7
|
-
**[
|
|
8
|
-
|
|
9
|
-
> All state is kept local, Git-backed, and versioned.
|
|
7
|
+
**[Docs, blog, etc. →](https://ljtn.github.io/epiq/)**
|
|
10
8
|
|
|
11
9
|

|
|
12
10
|
|
|
13
11
|
## Audit the workflow
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
Because state is an event log, you can replay the board to find out what happened when, who made the changes, and what what lead up to it.
|
|
16
14
|
|
|
17
15
|
## Code, linked to tickets
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
Commits are linked to a ticket with an `Epiq-Ref:` line in its body, naming the ticket's ref:
|
|
20
18
|
|
|
21
19
|
```
|
|
22
20
|
git commit -m "fix the parser" -m "Epiq-Ref: 1YRTG8T"
|
|
23
21
|
```
|
|
24
|
-
|
|
25
|
-
One line can name several tickets (`Epiq-Ref: 1YRTG8T, 4KQ2M7D`), it can sit anywhere in the body, and its case does not matter. Prefixing the subject with the ref works too:
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
git commit -m "1YRTG8T fix the parser"
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Linking makes a commit show up in the ticket's Code tab, with its message. You can comment on selected lines, and also file a new ticket straight from the selection. The MCP exposes the ref as a `ref` property agents can refer to.
|
|
32
|
-
|
|
33
22
|

|
|
34
23
|
|
|
35
|
-
How you merge decides whether the link survives:
|
|
36
|
-
|
|
37
|
-
- **Epiq-Ref lines** survive a squash merge that keeps the squashed messages in its body, as git's and GitHub's default messages do, so every ticket they name stays linked. A rebase or merge commit keeps them as they are.
|
|
38
|
-
- **Subject prefixes** need the commits to land as they are: rebase-merge them. Squashing _within_ one ticket's commits is fine as long as the result keeps the prefix; do not squash commits carrying different prefixes into one.
|
|
39
|
-
|
|
40
24
|
### Commits from other repos
|
|
41
25
|
|
|
42
|
-
A board can read commits from other repositories too
|
|
43
|
-
|
|
44
|
-
Connecting is a board-wide action: every contributor's epiq downloads the repo on their next sync. So it takes two steps. The first clones the repo into a staging area and shows what it is and what it costs — its size, host, default branch and latest commit, and how many of its commits name tickets on this board (none suggests the wrong repo) — and writes nothing. Only a confirm connects it: the TUI takes the same line again with `confirm`, the MCP a second call with `confirm: true`. Options of a connected repo change at once. `branches` is `default` (the default) or `all`; `since` is how much history to clone: `board` (since the board was created, the default), `all`, or a date; `label` is what the repo is called on the board — its URL's basename unless set, which is how a fork and its upstream are told apart.
|
|
45
|
-
|
|
46
|
-
The list is part of the board: connecting, changing and disconnecting are board events, so they sync like any other change and never conflict — nothing to commit. Epiq keeps its own read-only bare clone of each repo under `~/.epiq-global/repos/`, fetched on sync with your usual git credentials (ssh-agent or a credential helper; a passphrase prompt cannot be answered) — never on startup. A commit shows once it is pushed and the board has synced.
|
|
47
|
-
|
|
48
|
-
A board reads at most 20 repos; one connected past that is listed but not fetched until one ahead of it is disconnected. Each clone may take at most 2 GB of disk on your machine; `:config repoCloneMaxMb <MB>` changes that, in your own config. A fetch that would pass it is stopped and undone, and the repo says so. The Repos section shows what each clone takes on this machine, and the total. Autosync fetches each at most once a minute; a sync you ask for always fetches. Widening `since` fetches the older history, narrowing it keeps what is already cloned. Disconnecting a repo deletes its clone, on every machine as it syncs; worktrees of one board share a clone. A repo is something every contributor can fetch, so a local path is refused. A connected repo that is itself an epiq board lends its commits, never its board's event log.
|
|
49
|
-
|
|
50
|
-
Since anyone on the board can connect a repo, URLs are read with care: only `https`, `http`, `ssh`, `git` and `user@host:path` URLs are accepted, never one carrying a password or a user before an http host, and git runs in the clones with none of your hooks, attribute drivers or command-running transports.
|
|
51
|
-
|
|
52
|
-
## Goals
|
|
53
|
-
|
|
54
|
-
A goal groups the tickets that serve one outcome, on any board. A ticket can be in one goal. Goals are not tickets: a board shows the goals its tickets are in as a strip above the lanes, each pill underlined as far as the goal's tickets are done (closed, or in a done lane), on every board. A lane named Done counts, and so does any lane ticked "Tickets here count as done" in its panel.
|
|
55
|
-
|
|
56
|
-
- **GUI:** pick a goal in a ticket's Goal dropdown, or type a new name there to create one; from the keyboard, **Put in a goal** and **Take out of its goal** in the palette (⌘K, then `goal`). Click a pill to show only that goal's tickets and open its panel: a description of what it's for, progress and every ticket in it on any board, a Stats tab measuring the code of all of them together, and a Code tab listing every commit on them. **Manage goals** (the strip, the dropdown or the palette) adds, renames and removes them.
|
|
57
|
-
- **TUI:** `:goal <name>` puts the selected ticket in that goal, creating it if needed; `:ungoal` takes it out; `:filter goal <name>` shows only its tickets. A ticket in a goal carries `◆` in the goal's colour. `:rule done` and `:unrule done` mark the selected lane done or not; a done lane shows `✓` after its count. Renaming and removing a goal is done in the GUI or the MCP, as with tags.
|
|
58
|
-
- **MCP:** `epiq_issue_goal_set` with a goal's name (created if needed) or `null`; `epiq_goal_list` lists every goal with its progress and boards; `epiq_goal_stats` gives a goal's code stats; `epiq_goal_create`, `epiq_goal_rename`, `epiq_goal_description_edit`, `epiq_goal_remove`; `epiq_goal_list` carries each goal's description. `epiq_board_list` lists the goals each board shows. `epiq_swimlane_rule_set` marks a lane done; `epiq_swimlane_list` says which lanes count.
|
|
59
|
-
|
|
60
|
-
A board shows a goal once one of its tickets is on it. Removing a goal leaves its tickets where they are, in no goal.
|
|
26
|
+
A board can read commits from other repositories too. Connecting is a board-wide action: every contributor's epiq downloads the repo on their next sync.
|
|
61
27
|
|
|
62
28
|
## Terminal + Browser
|
|
63
29
|
|
|
64
|
-
Epiq originated from the command line and offers a first-class terminal experience, but also features a browser interface
|
|
30
|
+
Epiq originated from the command line and offers a first-class terminal experience, but also features a browser interface.
|
|
65
31
|
|
|
66
32
|

|
|
67
33
|
|
|
68
|
-
##
|
|
34
|
+
## What Epiq brings
|
|
69
35
|
|
|
70
36
|
Fundamental design choices gives a system with:
|
|
71
37
|
|
|
72
|
-
- **
|
|
38
|
+
- **Repo-native** — issues live where the code lives, simplifying sandboxed setups
|
|
39
|
+
- **Workflow replay**, - inspect what happened while you were away
|
|
73
40
|
- **Simple setup** — no accounts, SaaS, or external services required
|
|
74
|
-
- **Repo-native** — your issues can live where your code lives
|
|
75
41
|
- **Offline-friendly** — works anywhere, with eventual consistency
|
|
76
|
-
- **Fast** — local first
|
|
42
|
+
- **Fast** — local first edits are instant
|
|
77
43
|
- **Portable** — runs on your local machine, on a remote Linux server or your grandma’s connected toaster
|
|
78
44
|
- **Command driven** — scriptable and automation-friendly, ready for the agentic era
|
|
79
45
|
|
|
@@ -265,6 +231,5 @@ Epiq follows a **local-first** model:
|
|
|
265
231
|
|
|
266
232
|
---
|
|
267
233
|
|
|
268
|
-
[](https://github.com/sponsors/ljtn)
|
|
269
234
|
|
|
270
|
-
|
|
235
|
+
[](https://github.com/sponsors/ljtn)
|
package/dist/chunk-5E42KQM3.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import{$a as w,A as qt,Aa as vt,B as Vt,Ba as vo,C as Yt,Ca as ht,Cc as _,D as ye,Dc as Jo,E as O,Ea as ho,Ec as Qo,F as Jt,Fa as bt,G as Qt,Ga as Ue,Gb as me,H as te,Ha as bo,Hb as we,I as Zt,Ia as Ro,J as D,Ja as Se,Jb as pe,K as We,Ka as ze,La as yo,M as Ae,Ma as Ao,Mb as $o,N as eo,Na as Rt,Nb as Wo,O as to,Oa as yt,Ob as jo,P as mt,Pa as y,Q as oo,Qa as To,R as so,Ra as A,Rb as Ho,S as no,Sa as So,Sb as Uo,T as F,Tb as zo,Ua as At,V as U,Va as ue,W as $,Wa as wo,Xa as ce,Ya as oe,Za as Eo,_ as C,_a as xo,b as Fe,c as le,ca as ro,d as _e,e as Ge,eb as Co,ec as Ko,f as jt,fa as k,fb as Bo,g as Q,ga as io,ha as ao,hb as Lo,i as Ht,ib as Ke,jb as No,jc as Xo,k as $e,ka as lo,kb as ko,kc as Tt,la as uo,lb as Po,lc as qo,ma as je,mb as Oo,n as ee,na as He,nb as Mo,o as Ut,oa as Te,ob as Do,pa as pt,pb as Fo,pc as qe,q as ct,qa as ft,qc as Vo,ra as gt,rb as _o,s as zt,sa as de,ta as co,tc as Yo,ua as It,uc as Ve,va as mo,vb as Xe,vc as St,wa as po,wb as Go,xa as fo,xb as M,y as Kt,ya as go,yc as wt,z as Xt,za as Io,zc as Ye}from"./chunk-GOROXQFX.js";import{g as Wt}from"./chunk-WUCQWBQE.js";import{b as d,c as m,d as l}from"./chunk-5RIQL7P4.js";var an=e=>e.payload??{},Et=e=>{let t=new Map,o={},s=new Set,n=new Set,a=new Set;for(let r of e){let{id:i,name:u}=an(r);if(i)switch(r.action){case"create.contributor":case"create.tag":t.set(i,u??""),s.add(i),n.delete(i);break;case"rename.contributor":u!==void 0&&s.has(i)&&!n.has(i)&&t.set(i,u);break;case"tombstone.contributor":s.has(i)&&(t.set(i,We),n.add(i));break;case"restore.contributor":case"restore.tag":u!==void 0&&s.has(i)&&(t.set(i,u),n.delete(i));break;case"add.board":u!==void 0&&(t.set(i,u),a.add(i));break;case"add.swimlane":u!==void 0&&(t.set(i,u),a.add(i),o[i]=u);break;case"edit.title":u!==void 0&&a.has(i)&&(t.set(i,u),i in o&&(o[i]=u));break;default:break}}return{byId:t,lanes:o}};var ln=e=>{let t=new Map,o=new Map;for(let s of e){let n=s.payload;!n?.id||!n.parent||(s.action==="move.node"&&t.set(s.id,o.get(n.id)),o.set(n.id,n.parent))}return t},es=e=>{let t=e.payload;return e.action==="tombstone.tag"||e.action==="restore.tag"?t?.id:t?.tag},dn=e=>{let t=new Set,o=new Map;for(let s of e){let n=s.payload,a=n?.id;a&&(s.action==="add.issue"&&t.add(a),n.parent&&o.set(a,n.parent))}return{issueIds:t,parentById:o}},un=(e,{issueIds:t,parentById:o})=>{let s=e.payload;if(s?.issue)return s.issue;let n=new Set,a=s?.id;for(;a!==void 0&&!n.has(a);){if(t.has(a))return a;n.add(a),a=o.get(a)}return null},cn=new Set(["add.issue.comment","edit.issue.comment","delete.issue.comment"]),mn=e=>cn.has(e.action)?e.payload?.id??null:null,pn=new Set(["add.issue","move.node","close.issue","reopen.issue"]),fn=(e,t)=>{let o=new Map;return e.map((s,n)=>{let a=t[n]??null;if(a===null)return{lane:null,laneBefore:null};let r=s.payload,i=null;if(pn.has(s.action)&&r?.id===a&&r.parent){let u=o.get(a);u!==void 0&&u!==r.parent&&(i=u),o.set(a,r.parent)}return{lane:o.get(a)??null,laneBefore:i}})},gn=e=>{let t=new Map,o=new Map;return e.map(s=>{let{id:n,name:a}=s.payload??{};if(!n)return null;s.action==="add.board"&&t.set(n,"board"),s.action==="add.swimlane"&&t.set(n,"swimlane");let r=t.get(n);if(!r)return null;let i={kind:r,name:o.get(n)??a};return(s.action==="edit.title"||s.action.startsWith("add."))&&a!==void 0&&o.set(n,a),i})},Zo=90,In=e=>{let t=e.trim().split(`
|
|
3
|
-
`)[0]?.trim()??"";return t.length>Zo?`${t.slice(0,Zo).trimEnd()}\u2026`:t},ts=({kind:e,name:t})=>t===void 0?e:`${e} "${t}"`,vn=(e,t,o,s)=>{let{name:n,parent:a}=e.payload??{},r=ts(t);switch(e.action){case"move.node":{if(t.kind==="board"||!a)return`Moved ${r}`;let i=o.get(a);return i?s.get(e.id)===a?`Moved ${r} within ${i}`:`Moved ${r} to ${i}`:`Moved ${r}`}case"edit.title":return n===void 0?`Renamed ${r}`:`Renamed ${r} to "${n}"`;case"delete.node":return`Deleted ${r}`;case"lock.node":return`Locked ${r}`;default:return null}},hn=(e,t,o,s,n)=>{let a=e.payload,r=es(e),i=e.action?Jo(e.action):"";if(a?.md!==void 0){let f=In(a.md),g=e.action==="edit.description"&&!s&&n?`${i} of ${ts(n)}`:i;return f?`${g}: ${f}`:g}if(n&&!s){let f=vn(e,n,t,o);if(f)return f}if(e.action==="move.node"&&a?.parent){let f=t.get(a.parent),g=s?`${A(s)} moved`:"Moved";return f?o.get(e.id)===a.parent?`${g} within ${f}`:`${g} to ${f}`:s?g:i}if((e.action==="close.issue"||e.action==="reopen.issue")&&s)return`${A(s)} ${i.toLowerCase()}`;if(Vt(e.action))return[i,Yt(e)].filter(Boolean).join(" ");let u=r!==void 0?t.get(r)??"":a?.assignee!==void 0?t.get(a.assignee)??"":a?.email!==void 0?a.email:a?.name!==void 0?`"${a.name}"`:"";return[i,u].filter(Boolean).join(" ")},xt=(e,t)=>e?D(e,t.get(e)):null,bn=(e,t)=>{let o=e.payload;return{actor:xt(e.userId,t),tag:xt(es(e),t),assignee:xt(o?.assignee,t)}},os=e=>{let t=new Map,o=new Set,s=n=>{let a=new Set,r=n;for(;r&&!a.has(r);){if(o.has(r))return r;a.add(r),r=t.get(r)}return null};return e.map(n=>{let a=n.payload,r=a?.id;if(!r)return null;n.action==="add.board"&&o.add(r);let i=n.action==="close.issue"?s(r):null,u=a.parent??a.issue;return u&&t.set(r,u),i!==null?i:o.has(r)?r:s(r)})},ss=e=>Rn.has(e),Rn=new Set(["create.contributor","rename.contributor","tombstone.contributor","restore.contributor","link.contributor.user","link.contributor.email","unlink.contributor.email","connect.repo","edit.repo","disconnect.repo","create.tag","tombstone.tag","restore.tag"]),ns=(e,t)=>{let o=os(e);return e.filter((s,n)=>o[n]===t)},yn=(e,t=new Map)=>{let o=new Map([...t,...Et(e).byId]),s=ln(e),n=dn(e),a=e.map(c=>un(c,n)),r=gn(e),i=fn(e,a),u=os(e),f=Jt(e.map(c=>te(c)));return e.flatMap((c,I)=>{let v=f[I]??null;return v===null?[]:[{id:c.id,t:v,action:c.action,label:hn(c,o,s,a[I]??null,r[I]??null),issue:a[I]??null,target:mn(c),...i[I],board:u[I]??null,...bn(c,o)}]}).sort((c,I)=>c.t-I.t)},An=e=>{let t=new Map;for(let s of e){if(s.issue===null||s.lane===null)continue;let n=t.get(s.issue)??[],a=n[n.length-1];(a===void 0||a.lane!==s.lane)&&(n.push({t:s.t,lane:s.lane,board:s.board}),t.set(s.issue,n))}let o=new Map;for(let[s,n]of t){let a=n[n.length-1];o.set(s,{changes:n,from:n[0].t,closedFrom:a.lane===k?a.t:null})}return o},Tn=(e,t)=>{let o=0,s=e.length;for(;o<s;){let n=o+s>>1;e[n].t<t?o=n+1:s=n}return e[o-1]},rs=(e,t,o)=>{let s={};for(let[n,a]of e){if(a.from>=t||a.closedFrom!==null&&a.closedFrom<t)continue;let r=Tn(a.changes,t);r===void 0||r.lane===k||o&&r.board!==o||(s[n]=r.lane)}return s},Sn=e=>Et(e).lanes,Ee=null,is=e=>{let t=ze(e);if(Ee&&Ee.root===e&&Ee.signature===t)return m("Timeline entries, cached",Ee.index);let o=oe(e);if(l(o))return d(o.message);let s=yn(o.value,ce(e)),n={entries:s,lanes:An(s),laneNames:Sn(o.value)};return Ee={root:e,signature:t,index:n},m("Timeline entries, built",n)};var as=1e5,wn=2e4,Je=null,ls=Promise.resolve(),us=e=>{let t=ls.then(e,e);return ls=t.then(()=>{},()=>{}),t},Qe=e=>{let t=Se(e);return l(t)?t:Q({repoRoot:t.value})},z=()=>{if(!Ye())return{mode:"live",asOfTime:null};let{timeMode:e}=Yo();return e==="live"?{mode:"live",asOfTime:null}:{mode:"scrub",asOfTime:Je}},ds=(e,t)=>{let o=0,s=e.length;for(;o<s;){let n=o+s>>1;e[n].t<t?o=n+1:s=n}return o},ra=async(e={})=>{let t=Qe(e.repoRoot);if(l(t))return d(t.message);let o=is(t.value);if(l(o))return d(o.message);let s=o.value.entries,n=Date.now(),a=e.end??n,r=e.start??s[0]?.t??a;if(a<=r)return m("Empty time window",{bucketMs:0,buckets:[],capped:!1,events:[],lanesAtStart:{},laneNames:o.value.laneNames,closedLane:k,earliest:r,latest:a});let i=ds(s,r),u=ds(s,a),f=s.slice(i,u).filter(p=>!e.boardId||p.board===e.boardId||ss(p.action)),g=f.map(p=>p.t),c=Math.max(1,Math.ceil((a-r)/as)),I=new Map;for(let p of g){let b=Math.min(as-1,Math.floor((p-r)/c)),T=r+b*c;I.set(T,(I.get(T)??0)+1)}let v=[...I.entries()].sort(([p],[b])=>p-b).map(([p,b])=>({t:p,count:b})),h=f.length>wn;return m("Computed event timeline",{bucketMs:c,buckets:v,capped:h,events:h?[]:f,lanesAtStart:rs(o.value.lanes,r,e.boardId),laneNames:o.value.laneNames,closedLane:k,earliest:r,latest:a})},cs=e=>{let t=oe(e);if(l(t))return d(t.message);let o=wt();if(l(o))return d(o.message);Je=null;let s=At(t.value),{fatal:n,skipped:a}=Rt(s);return n.length>0?d(n.map(r=>r.message).join(", ")):(yt(a),St({readOnly:!1,readOnlyReason:void 0,timeMode:"live",unappliedEvents:[],replay:null}),m("Restored live state",!0))},En=(e,t)=>{Je=null;let o=cs(e);return l(o)?d(`${t} (recovery to live also failed: ${o.message})`):d(t)},xn=e=>us(async()=>{let t=Qe(e.repoRoot);if(l(t))return d(t.message);let o=Eo(t.value,e.targetTime);if(l(o))return d(o.message);let{appliedEvents:s,unappliedEvents:n}=o.value,a=wt();if(l(a))return a;let r=At(s),{fatal:i,skipped:u}=Rt(r);return i.length>0?En(t.value,i.map(f=>f.message).join(", ")):(yt(u),St({readOnly:!0,timeMode:"peek",unappliedEvents:n,replay:null}),Je=e.targetTime,m("Checked out historical state",{asOfTime:e.targetTime}))}),ia=async e=>{let t=Qe(e.repoRoot);if(l(t))return d(t.message);let o=xo(t.value);if(l(o))return d(o.message);let s=o.value.get(e.eventId)??null;return s===null?d("Event not found in the log"):xn({repoRoot:e.repoRoot,targetTime:s+1})},aa=(e={})=>us(async()=>{let t=Qe(e.repoRoot);if(l(t))return d(t.message);let o=cs(t.value);return l(o)?d(o.message):m("Returned to live state",!0)});import Bn from"node:fs";var ms=new Map,Cn=3e4,ps=async e=>{let t=ms.get(e);if(t&&Date.now()-t.readAt<Cn)return{name:t.name,email:t.email};let o=await Ge({args:["config","--get-regexp","^user\\.(name|email)$"],cwd:e}),s=null,n=null;if(o.exitCode===0)for(let a of(o.stdout??"").split(`
|
|
4
|
-
`)){let r=a.indexOf(" ");if(r===-1)continue;let i=a.slice(0,r),u=a.slice(r+1);i==="user.name"&&(s=u.trim()||null),i==="user.email"&&(n=Ae(u)||null)}return ms.set(e,{name:s,email:n,readAt:Date.now()}),{name:s,email:n}},fs=async e=>(await ps(e)).email,fe=async e=>(await ps(e)).name;var se=e=>{let t=$e(e??process.cwd());return l(t)?d(t.message):m("Resolved Epiq repo root",t.value)},Ze=null,Ln=async(e,t)=>{let o=se(e);if(l(o))return o;let s=Q({repoRoot:o.value});if(l(s))return d(s.message);let n=le(o.value);if(l(n))return d(n.message);if(!(Ze!==null&&Ze.repoRoot===o.value&&Ze.stateBranchRoot===s.value&&Bn.existsSync(Ht(s.value)))){let c=await Ho({repoRoot:o.value,stateBranchName:n.value});if(l(c))return d(c.message);let I=await Uo({repoRoot:o.value,stateBranchRoot:s.value,stateBranchName:n.value});if(l(I))return d(I.message);Ze={repoRoot:o.value,stateBranchRoot:s.value}}if(t?.pull??!1){let c=await _e({cwd:s.value,args:["pull","--ff-only"]});l(c)&&Fe.info(3,c.message)}let r={repoRoot:o.value,stateBranchRoot:s.value},i=ee();l(i)||ct(i.value),ct({gitEmail:await fs(o.value),gitName:await fe(o.value)});let u=ze(s.value);if(yo(s.value)===u&&Ye()&&z().mode==="live")return m("Booted Epiq state, unchanged",r);let f=ue(s.value);if(l(f))return d(f.message);let g=pe(f.value.events,f.value.unreadable);return l(g)?d(g.message):(Ao(s.value,u),m("Booted Epiq state",r))},W=()=>{let e=ee();return l(e)?d(e.message):e.value.userId?e.value.userName?m("Resolved actor",{userId:e.value.userId,userName:e.value.userName}):d("Unable to retrieve user name"):d("Unable to retrieve user id")},B=()=>{let e=Ve();return l(e)?d(e.message):e},L=e=>Ln(e,{pull:!1}),R=async e=>{let t=await L(e);if(l(t))return t;let o=W();if(l(o))return o;let s=B();return l(s)?s:m("Booted",{boot:t.value,actor:o.value,state:s.value})};import{ulid as Nn}from"ulid";var N=e=>{let t=B();if(l(t))return t;let o=t.value.nodes[e];return!o||o.isDeleted?d("Issue not found"):F(o)?o.readonly?d("Issue is readonly"):m("Found issue",o):d("Target must be an issue")},j=e=>{let t=B();if(l(t))return t;let o=t.value.nodes[e];return!o||o.isDeleted?d("Swimlane not found"):U(o)?o.readonly?d("Swimlane is readonly"):m("Found swimlane",o):d("Target must be a swimlane")},xe=e=>{let t=B();if(l(t))return t;let o=t.value.nodes[e];return!o||o.isDeleted?d("Board not found"):$(o)?o.readonly?d("Board is readonly"):m("Found board",o):d("Target must be a board")};var Ct=e=>{if(!e)return;let t=B();if(l(t))return;let o=t.value.nodes[e];if(!o||!($(o)||U(o)))return;let s=o.props?.description;return s?.trim()?s:void 0},et=e=>{let t=Ct(e);return t?{laneDescription:t}:{}},Ce=e=>{let t=Ct(e);return t?{boardDescription:t}:{}},ge=e=>{let t=Ct(e);return t?{description:t}:{}},Be=({node:e,label:t,max:o,description:s,actor:n,stateBranchRoot:a,action:r="edit.description"})=>{let i=Go(s),u=M(t,i,o);if(u)return d(u);if(z().mode!=="live")return d(`Cannot edit the ${t.toLowerCase()} while time travelling`);let f={id:e.id,ref:A(e.id),description:i};if((e.props?.description??"")===i)return m("No changes made",f);let g={id:Nn(),...y(n),action:r,payload:{id:e.id,md:i}},c=w([g],a);return l(c)?d(c.message):m(`Edited ${t.toLowerCase()}`,f)},kn=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=xe(e.boardId);return l(o)?o:Be({node:o.value,label:"Board description",max:Xe("board"),description:e.description,actor:t.value.actor,stateBranchRoot:t.value.boot.stateBranchRoot})},Pn=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=j(e.swimlaneId);return l(o)?o:Be({node:o.value,label:"Swimlane description",max:Xe("swimlane"),description:e.description,actor:t.value.actor,stateBranchRoot:t.value.boot.stateBranchRoot})},On=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=de(e.goal);return o?Be({node:{id:o.id,props:{description:o.description}},label:"Goal description",action:"edit.goal.description",max:Xe("goal"),description:e.description,actor:t.value.actor,stateBranchRoot:t.value.boot.stateBranchRoot}):d(`No goal called or with the id "${e.goal}"`)};import{ulid as Le}from"ulid";var Bt=e=>({id:e.id,name:C(e.name),color:mo(e)}),Ne=e=>{let t=co(e);return t?{goal:Bt(t)}:{}},gs=e=>({...Bt(e),...vt(e.id),...e.description&&{description:e.description}}),Is=()=>ft().map(gs),Mn=async(e={})=>{let t=await L(e.repoRoot);return l(t)?t:m("Listed goals",ft().map(o=>({...gs(o),boards:fo(o.id).map(s=>({id:s,ref:A(s)}))})))},tt=e=>Io(e).map(t=>({...Bt(t),...vt(t.id),members:go(t.id,e).map(o=>({id:o.id,done:vo(o)}))})),ot=(e,t,o)=>{let s=w(t,e);return l(s)?d(s.message):m("Saved",o)},Dn=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=N(e.issueId);if(l(o))return o;let s=o.value,n={id:s.id,ref:A(s.id)},a;if(e.goal!==null){let u=de(e.goal);if(!u){let f=await vs({repoRoot:e.repoRoot,name:e.goal,issueId:s.id});return l(f)?f:m(`Created goal "${f.value.name}"`,n)}a=u.id}let r=It(s,a);if(r.length===0)return m("No changes made",n);let i=y(t.value.actor);return ot(t.value.boot.stateBranchRoot,r.map(u=>({id:Le(),...i,...u})),n)},vs=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=gt(e.name),s=ht(o);if(s)return d(s);let n=y(t.value.actor),a=Le(),r=[{id:a,...n,action:"create.goal",payload:{id:a,name:o}}];if(e.issueId){let i=N(e.issueId);if(l(i))return i;let u=i.value;for(let f of It(u,a))r.push({id:Le(),...n,...f})}return ot(t.value.boot.stateBranchRoot,r,{id:a,name:o})},Fn=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=pt(e.goalId);if(!o)return d("Goal not found");let s=gt(e.name);if(s===o.name)return m("No changes made",{id:o.id,name:s});let n=ht(s,o.id);return n?d(n):ot(t.value.boot.stateBranchRoot,[{id:Le(),...y(t.value.actor),action:"rename.goal",payload:{id:o.id,name:s}}],{id:o.id,name:s})},_n=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=pt(e.goalId);return o?ot(t.value.boot.stateBranchRoot,[{id:Le(),...y(t.value.actor),action:"tombstone.goal",payload:{id:o.id}}],{id:o.id,name:o.name}):d("Goal not found")};import{ulid as Gn}from"ulid";var st=e=>{let t=lo(e);return t.length>0?{rules:t}:{}},$n=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=j(e.swimlaneId);if(l(o))return o;let s=o.value,n=uo(s,e.rule,e.on);if(l(n))return n;let a={id:s.id,ref:A(s.id),rule:e.rule};if(!n.value)return m(n.message,a);let r=w([{id:Gn(),...y(t.value.actor),...n.value}],t.value.boot.stateBranchRoot);return l(r)?d(r.message):m(e.on?"Added lane rule":"Removed lane rule",a)};import Wn from"node:fs";import jn from"node:path";import{ulid as Lt}from"ulid";var Hn=e=>{if(e.filePath)try{return m("Read attachment file",{data:Wn.readFileSync(e.filePath),name:e.name??jn.basename(e.filePath)})}catch(t){return d(`Unable to read ${e.filePath}: ${t instanceof Error?t.message:String(t)}`)}return e.dataBase64?m("Decoded attachment data",{data:Buffer.from(e.dataBase64,"base64"),name:e.name??"image"}):d("Provide either filePath or dataBase64")},nt=()=>{let e=ee();return l(e)?bt:e.value.attachmentMaxKb??bt},Un=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=N(e.issueId);if(l(o))return o;let s=Hn(e);if(l(s))return s;let n=bo(t.value.boot.stateBranchRoot,s.value.data,nt());if(l(n))return n;let a=C(s.value.name).trim()||"image",r=Lt(),i={id:Lt(),...y(t.value.actor),action:"add.issue.attachment",payload:{id:r,issue:e.issueId,author:t.value.actor.userId,hash:n.value.hash,ext:n.value.ext,name:a,bytes:n.value.bytes}},u=w([i],t.value.boot.stateBranchRoot);if(l(u))return d(u.message);let f=Ue(n.value.hash,n.value.ext);return m("Added issue attachment",{id:r,issueId:e.issueId,fileName:f,bytes:n.value.bytes,markdown:ho(a,f)})},zn=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=t.value.state.eventLog.find(i=>i.action==="add.issue.attachment"&&i.payload.id===e.attachmentId);if(!o)return d("Unable to resolve attachment");if(o.payload.author!==t.value.actor.userId)return d("You can only delete your own attachments");let s=N(o.payload.issue);if(l(s))return s;if(t.value.state.eventLog.some(i=>i.action==="delete.issue.attachment"&&i.payload.id===e.attachmentId))return m("Attachment already deleted",{id:e.attachmentId,issueId:o.payload.issue});let a={id:Lt(),...y(t.value.actor),action:"delete.issue.attachment",payload:{id:e.attachmentId,issue:o.payload.issue}},r=w([a],t.value.boot.stateBranchRoot);return l(r)?d(r.message):m("Deleted issue attachment",{id:e.attachmentId,issueId:o.payload.issue})},Kn=async e=>{let t=await L(e.repoRoot);return l(t)?t:Ro(t.value.stateBranchRoot,e.fileName,nt())};var Xn=e=>{switch(e.action){case"add.issue":case"move.node":case"close.issue":case"reopen.issue":return e.payload.parent;default:return null}},Nt=(e,t)=>{let o=[];for(let s of e){let n=Xn(s);if(n===null||n===o.at(-1)?.laneId)continue;let a=te(s);o.push({laneId:n,enteredAt:a===null?t:ye(a)})}return o},hs=(e,t)=>e.reduce((o,s)=>{let n=te(s);return n===null?o:Math.max(o,ye(n))},t),ke=(e,t)=>Nt(e,t).at(-1)?.enteredAt??t;var ne=e=>io(e).map(t=>D(t.id,t.name)),qn=e=>_.getCommentsByIssue(e).sort((t,o)=>t.id<o.id?-1:t.id>o.id?1:0).map(t=>({id:t.id,author:Zt(t.authorId,_.getContributor(t.authorId)?.name),createdAt:O(t.id),body:t.md})),re=e=>ao(e).map(({id:t,name:o})=>D(t,o)),Vn=3,Yn=3,Jn=400,Qn=200,Zn=" \u2026 [cut: call epiq_issue_get with allComments: true to read the rest]",er=e=>{let t=e.split(`
|
|
5
|
-
`).slice(0,Yn).join(`
|
|
6
|
-
`).slice(0,Jn);return e.length-t.length<Qn?e:`${t}${Zn}`},bs=(e,t=!1)=>{let o=qn(e),s=o.length-Vn;return t||s<=0?{comments:o}:{comments:o.map((n,a)=>a<s?{...n,body:er(n.body)}:n),olderComments:`The first ${s} show their first lines only: pass allComments to read them whole`}};var tr=async(e={})=>{let t=await L(e.repoRoot);if(l(t))return t;let o=B();return l(o)?o:m("Retrieved Epiq state",{root:t.value.repoRoot,stateBranchRoot:t.value.stateBranchRoot,nodes:o.value.nodes,rootNodeId:o.value.rootNodeId,contextNode:o.value.contextNode,selectedIndex:o.value.selectedIndex,eventLog:o.value.eventLog})},or=365*24*60*60*1e3,sr=e=>{let t=B();if(l(t))return t;let o=t.value.nodes[e];return m("Issue description",o&&F(o)&&!o.isDeleted?o.props.description??"":"")},kt=()=>{let e=B();if(l(e))return e;let t=z(),o=t.mode!=="live",s=Object.values(e.value.nodes),n=s.filter(p=>$(p)&&!p.isDeleted),a=Date.now()-or,r=new Map,i=new Map;for(let p of s)if(!p.isDeleted){if(U(p)&&p.parentNodeId){let b=r.get(p.parentNodeId)??[];b.push(p),r.set(p.parentNodeId,b)}if(F(p)&&p.parentNodeId){if(p.parentNodeId===k&&O(p.id)<a)continue;let b=i.get(p.parentNodeId)??[];b.push(p),i.set(p.parentNodeId,b)}}let u=new Set;for(let p of i.values())for(let b of p)u.add(b.id);let f=ee();if(l(f))return f;let g={},c=_.getCommentsGroupedByIssue();for(let p of s.filter(F))p.isDeleted||!u.has(p.id)||(g[p.id]=(c.get(p.id)??[]).map(b=>{let T=_.getContributor(b.authorId);return{id:b.id,issueId:b.issue,body:b.md,author:D(b.authorId,T?.name),createdAt:O(b.id)}}));let I=new Map;for(let p of e.value.eventLog)p.action==="add.issue.attachment"&&I.set(p.payload.id,p.payload.author);let v={},h=_.getAttachmentsGroupedByIssue();for(let p of s.filter(F))p.isDeleted||!u.has(p.id)||(v[p.id]=(h.get(p.id)??[]).map(b=>({id:b.id,issueId:b.issue,name:b.name,fileName:Ue(b.hash,b.ext),bytes:b.bytes,createdAt:O(b.id),canDelete:I.get(b.id)===f.value.userId})));return m("Retrieved Epiq GUI state",{boards:n.sort((p,b)=>p.rank.localeCompare(b.rank)).map(p=>({id:p.id,ref:A(p.id),title:p.title,readonly:!!p.readonly||o,...ge(p.id),goals:tt(p.id),swimlanes:(r.get(p.id)??[]).sort((b,T)=>b.rank.localeCompare(T.rank)).map(b=>({id:b.id,title:b.title,readonly:!!b.readonly||o,issues:(i.get(b.id)??[]).sort((T,Y)=>T.rank.localeCompare(Y.rank)).map(T=>({id:T.id,ref:A(T.id),title:C(T.title),description:T.props.description??"",createdAt:O(T.id),enteredLaneAt:ke(T.log??[],O(T.id)),lastEventAt:hs(T.log??[],O(T.id)),readonly:!!T.readonly||o,tags:ne(T),assignees:re(T),...Ne(T),parentNodeId:T.parentNodeId,isClosed:T.parentNodeId===k,boardIds:Te(T)})),parentNodeId:b.parentNodeId,...ge(b.id),...st(b),countsDone:He(b.id),doneByName:je(b)}))})),tags:_.getTags().map(p=>({...p,...D(p.id,p.name)})),goals:Is(),contributors:Object.values(e.value.contributors).map(p=>({...p,...D(p.id,p.name)})),user:{id:f.value.userId??"",name:f.value.userName??"",color:zt(f.value.userName??"")},commentsByIssueId:g,attachmentsByIssueId:v,attachmentMaxKb:nt(),timeTravel:t})},Rs=null,nr=e=>{if(e===Rs)return;let t=ro({root:e});if(l(t)){Fe.info(t.message);return}Rs=e},rr=async(e={})=>{if(z().mode!=="live")return kt();let t=await L(e.repoRoot);return l(t)?t:(nr(t.value.repoRoot),kt())},ir=async(e={})=>{$o();let t=se(e.repoRoot);if(l(t))return d("Sync failed");let o=W();if(l(o))return o;let s=await zo({cwd:t.value,ownEventFileName:To(o.value)});return l(s)?(jo(s.message),s):(Wo(),m("Synced",s.value))};import{ulid as K}from"ulid";var Pe=e=>{let t=[...new Set([...e.issueIds??[],...e.issueId?[e.issueId]:[]])];return t.length?m("Resolved targets",t):d("Provide issueId or issueIds")},Ie=(e,t)=>{let o=t.done.length,s=`${e} ${o} issue${o===1?"":"s"}`+(t.failed.length?`, ${t.failed.length} failed`:"");return o?m(s,t):d(`${s}: ${t.failed.map(n=>`${n.ref} ${n.reason}`).join("; ")}`)},Oe=(e,t)=>{let o={done:[],failed:[]};for(let s of e){let n=t(s);l(n)?o.failed.push({id:s,ref:A(s),reason:n.message}):o.done.push({id:s,ref:A(s)})}return o};var ar=async e=>{let t=await L(e.repoRoot);if(l(t))return t;let o=B();if(l(o))return o;let s=e.idOrRef.trim();if(!s)return d("Provide an issue id or ref");let n=Object.values(o.value.nodes).filter(F).filter(u=>!u.isDeleted),a=n.find(u=>u.id===s),r=a?[a]:n.filter(u=>So(u.id,s));if(r.length===0)return d(`No issue matches "${s}"`);if(r.length>1)return d(`"${s}" matches ${r.length} issues: ${r.map(u=>`${A(u.id)} (${C(u.title)})`).join(", ")}. Use a full ref or id.`);let i=r[0];return m("Found issue",{id:i.id,ref:A(i.id),title:C(i.title),description:i.props.description??"",createdAt:O(i.id),enteredLaneAt:ke(i.log??[],O(i.id)),parentNodeId:i.parentNodeId,isClosed:i.parentNodeId===k,boardIds:Te(i),readonly:!!i.readonly,tags:ne(i),assignees:re(i),...Ne(i),...bs(i.id,e.allComments),...et(i.parentNodeId)})},Ts=(e,t)=>e.trim().toLowerCase()===t.trim().toLowerCase();async function lr(e){let t=await L(e.repoRoot);if(l(t))return t;let o=B();if(l(o))return o;let s=o.value.nodes,n=e.query?.trim().toLowerCase(),a=Object.values(s).filter(F).filter(i=>!i.isDeleted).filter(i=>e.includeClosed||i.parentNodeId!==k).filter(i=>e.boardId?(i.parentNodeId?s[i.parentNodeId]:void 0)?.parentNodeId===e.boardId:!0).filter(i=>!e.swimlaneId||i.parentNodeId===e.swimlaneId).filter(i=>!e.tag||ne(i).some(u=>Ts(u.name,e.tag))).filter(i=>!e.assignee||re(i).some(u=>Ts(u.name,e.assignee))).filter(i=>!n||i.title.toLowerCase().includes(n)||(i.props.description??"").toLowerCase().includes(n)),r=i=>C((i.parentNodeId?s[i.parentNodeId]?.title:void 0)??"");return e.brief?m("Listed issues",a.map(i=>({id:i.id,ref:A(i.id),title:C(i.title),swimlane:r(i),tags:ne(i).map(u=>u.name),assignees:re(i).map(u=>u.name)}))):m("Listed issues",a.map(i=>({id:i.id,ref:A(i.id),title:C(i.title),description:i.props.description??"",createdAt:O(i.id),enteredLaneAt:ke(i.log??[],O(i.id)),parentNodeId:i.parentNodeId,isClosed:i.parentNodeId===k,boardIds:Te(i),readonly:!!i.readonly,tags:ne(i),assignees:re(i),...Ne(i)})))}var dr=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=C(e.title);if(!o)return d("Issue title cannot be empty");let s=e.description??"",n=M("Issue title",o,300)??M("Issue description",s,2e4);if(n)return d(n);if((e.tagNames?.length??0)>20)return d(`Cannot set more than ${20} tags at once`);if((e.assigneeNames?.length??0)>20)return d(`Cannot set more than ${20} assignees at once`);let a=j(e.parentId);if(l(a))return a;let r=we(e.parentId,t.value.actor,t.value.boot.stateBranchRoot);if(l(r))return r;let i=Ko({name:o,parent:e.parentId,user:t.value.actor,rank:r.value});if(l(i))return i;let u=[...i.value],f=u.find(v=>v.action==="add.issue")?.payload.id;if(!f)return d("Unable to determine created issue id");s&&u.push({id:K(),...y(t.value.actor),action:"edit.description",payload:{id:f,md:s}});let g=[];for(let v of e.tagNames??[]){let h=C(v).trim();if(!h||g.some(Y=>Y.name===h))continue;let p=M("Tag name",h,60);if(p)return d(p);let b=_.findTagByName(h),T=b?.id??K();b||u.push({id:K(),...y(t.value.actor),action:"create.tag",payload:{id:T,name:h}}),u.push({id:K(),...y(t.value.actor),action:"add.issue.tag",payload:{id:f,tag:T}}),g.push({id:T,name:h})}let c=[];for(let v of e.assigneeNames??[]){let h=C(v).trim();if(!h||c.some(Y=>Y.name===h))continue;let p=M("Assignee name",h,80);if(p)return d(p);let b=Object.values(t.value.state.contributors).find(Y=>Y.name===h),T=b?.id??K();b||u.push({id:K(),...y(t.value.actor),action:"create.contributor",payload:{id:T,name:h}}),u.push({id:K(),...y(t.value.actor),action:"add.issue.assignee",payload:{id:f,assignee:T}}),c.push({id:T,name:h})}let I=w(u,t.value.boot.stateBranchRoot);return l(I)?d(I.message):m("Created issue",{id:f,ref:A(f),title:o,parentId:e.parentId,description:s,tags:g,assignees:c,...et(e.parentId),...Ce(t.value.state.nodes[e.parentId]?.parentNodeId)})},ur=(e,t,o)=>{let s=N(e);if(l(s))return s;let n=me(k,e,{at:"end"},t,o);if(l(n))return n;let a={id:K(),...y(t),action:"close.issue",payload:{id:e,parent:k,rank:n.value}},r=w([a],o);return l(r)?d(r.message):m("Closed issue",{id:e,ref:A(e)})};async function cr(e){let t=await L(e.repoRoot);if(l(t))return t;let o=W();if(l(o))return o;let s=Pe(e);if(l(s))return s;let n=a=>ur(a,o.value,t.value.stateBranchRoot);return e.issueIds?Ie("Closed",Oe(s.value,n)):n(s.value[0])}var mr=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=t.value.state.nodes[e.issueId];if(!o||o.isDeleted)return d("Issue not found");if(!F(o))return d("Target must be an issue");if(o.parentNodeId!==k)return d("Issue is not closed");let s=Xo(o);if(!s)return d("Unable to resolve previous parent from issue history");if(s===k)return d("Previous parent resolves to closed swimlane");let n=j(s);if(l(n))return n;let a=n.value,r=me(a.id,o.id,{at:"end"},t.value.actor,t.value.boot.stateBranchRoot);if(l(r))return r;let i={id:K(),...y(t.value.actor),action:"reopen.issue",payload:{id:o.id,parent:a.id,rank:r.value}},u=w([i],t.value.boot.stateBranchRoot);return l(u)?d(u.message):m("Reopened issue",{id:o.id,ref:A(o.id),parentId:a.id})},pr=(e,t,o,s)=>{let n=N(e);if(l(n))return n;let a=j(t.parentId);if(l(a))return a;let r=me(t.parentId,e,t.position??{at:"end"},o,s);if(l(r))return r;let i={id:K(),...y(o),action:"move.node",payload:{id:e,parent:t.parentId,rank:r.value}},u=w([i],s);return l(u)?d(u.message):m("Moved issue",{id:e,ref:A(e),parentId:t.parentId})};async function fr(e){let t=se(e.repoRoot);if(l(t))return t;let o=W();if(l(o))return o;let s=Q({repoRoot:t.value});if(l(s))return s;let n=ue(s.value);if(l(n))return n;let a=pe(n.value.events,n.value.unreadable);if(l(a))return a;let r=Pe(e);if(l(r))return r;let i=(()=>{let g=B();if(l(g))return()=>({});let{nodes:c}=g.value,I=p=>p?c[p]?.parentNodeId:void 0,v=I(e.parentId),h=r.value.some(p=>I(c[p]?.parentNodeId)!==v);return()=>({...et(e.parentId),...h?Ce(v):{}})})(),u=g=>pr(g,e,o.value,s.value);if(!e.issueIds){let g=u(r.value[0]);return l(g)?g:m(g.message,{...g.value,...i()})}let f=Ie("Moved",Oe(r.value,u));return l(f)?f:m(f.message,{...f.value,...i()})}var gr=e=>{let t=B();if(l(t))return t;let o=t.value.nodes[e];return o?m("Read issue history",(o.log??[]).map(s=>({id:s.id,t:O(s.id),action:s.action,label:Qo(s),...s.action==="move.node"?{parentId:s.payload.parent}:{},actor:D(s.userId,t.value.contributors[s.userId]?.name)}))):d("Issue not found")},Ir=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=N(e.issueId);return l(o)?o:Be({node:o.value,label:"Issue description",max:2e4,description:e.description,actor:t.value.actor,stateBranchRoot:t.value.boot.stateBranchRoot})},vr=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=N(e.issueId);if(l(o))return o;let s=o.value,n=C(e.title);if(!n.trim())return d("Issue title cannot be empty");let a=M("Issue title",n,300);if(a)return d(a);if(s.title===n)return m("No changes made",{id:e.issueId,ref:A(e.issueId),title:n});let r={id:K(),...y(t.value.actor),action:"edit.title",payload:{id:e.issueId,name:n}},i=w([r],t.value.boot.stateBranchRoot);return l(i)?d(i.message):m("Edited issue title",{id:e.issueId,ref:A(e.issueId),title:n})};import{ulid as Z}from"ulid";var hr=async(e={})=>{let t=await L(e.repoRoot);if(l(t))return t;let o=B();if(l(o))return o;let s=Object.values(o.value.nodes).filter(n=>n.context==="BOARD"&&!n.isDeleted).map(n=>({id:n.id,ref:A(n.id),title:n.title,parentId:n.parentNodeId,readonly:!!n.readonly,...ge(n.id),goals:tt(n.id)}));return m("Listed boards",s)},br=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=t.value.state.nodes[t.value.state.rootNodeId];if(!o)return d("Workspace not found");if(z().mode!=="live")return d("Cannot add a board while time travelling");let s=C(e.title);if(!s.trim())return d("Board title cannot be empty");let n=M("Board title",s,300);if(n)return d(n);let a=we(o.id,t.value.actor,t.value.boot.stateBranchRoot);if(l(a))return a;let r=Z(),i={id:Z(),...y(t.value.actor),action:"add.board",payload:{id:r,name:s,parent:o.id,rank:a.value}},u=w([i],t.value.boot.stateBranchRoot);return l(u)?d(u.message):m("Created board",{id:r,ref:A(r),title:s})},Rr=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=t.value.state.nodes[e.boardId];if(!o||o.isDeleted)return d("Board not found");if(!$(o))return d("Edit target must be a board");if(o.readonly)return d("Cannot edit readonly board");if(z().mode!=="live")return d("Cannot rename a board while time travelling");let s=C(e.title);if(!s.trim())return d("Board title cannot be empty");let n=M("Board title",s,300);if(n)return d(n);if(o.title===s)return m("No changes made",{id:e.boardId,ref:A(e.boardId),title:s});let a={id:Z(),...y(t.value.actor),action:"edit.title",payload:{id:e.boardId,name:s}},r=w([a],t.value.boot.stateBranchRoot);return l(r)?d(r.message):m("Edited board title",{id:e.boardId,ref:A(e.boardId),title:s})},yr=async(e={})=>{let t=await L(e.repoRoot);if(l(t))return t;let o=B();if(l(o))return o;let s=Object.values(o.value.nodes).filter(n=>n.context==="SWIMLANE"&&!n.isDeleted).filter(n=>!e.boardId||n.parentNodeId===e.boardId).map(n=>({id:n.id,title:n.title,boardId:n.parentNodeId,isClosed:n.id===k,readonly:!!n.readonly,...ge(n.id),...U(n)?st(n):{},countsDone:He(n.id),doneByName:U(n)&&je(n)}));return m("Listed swimlanes",s)},Ar=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=xe(e.boardId);if(l(o))return o;if(z().mode!=="live")return d("Cannot add a swimlane while time travelling");let s=C(e.title);if(!s.trim())return d("Swimlane title cannot be empty");let n=M("Swimlane title",s,300);if(n)return d(n);let a=we(e.boardId,t.value.actor,t.value.boot.stateBranchRoot);if(l(a))return a;let r=Z(),i={id:Z(),...y(t.value.actor),action:"add.swimlane",payload:{id:r,name:s,parent:e.boardId,rank:a.value}},u=w([i],t.value.boot.stateBranchRoot);return l(u)?d(u.message):m("Created swimlane",{id:r,ref:A(r),title:s,boardId:e.boardId})},Tr=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=j(e.swimlaneId);if(l(o))return o;let s=o.value,n=C(e.title);if(!n.trim())return d("Swimlane title cannot be empty");let a=M("Swimlane title",n,300);if(a)return d(a);if(s.title===n)return m("No changes made",{id:e.swimlaneId,ref:A(e.swimlaneId),title:n});let r={id:Z(),...y(t.value.actor),action:"edit.title",payload:{id:e.swimlaneId,name:n}},i=w([r],t.value.boot.stateBranchRoot);return l(i)?d(i.message):m("Edited swimlane title",{id:e.swimlaneId,ref:A(e.swimlaneId),title:n})},Sr=async e=>{let t=se(e.repoRoot);if(l(t))return t;let o=W();if(l(o))return o;let s=Q({repoRoot:t.value});if(l(s))return s;let n=ue(s.value);if(l(n))return n;let a=pe(n.value.events,n.value.unreadable);if(l(a))return a;let r=j(e.swimlaneId);if(l(r))return r;let i=xe(e.boardId);if(l(i))return i;let u=me(e.boardId,e.swimlaneId,e.position??{at:"end"},o.value,s.value);if(l(u))return u;let f={id:Z(),...y(o.value),action:"move.node",payload:{id:e.swimlaneId,parent:e.boardId,rank:u.value}},g=w([f],s.value);return l(g)?d(g.message):m("Moved swimlane",{id:e.swimlaneId,ref:A(e.swimlaneId),boardId:e.boardId})},wr=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=j(e.swimlaneId);if(l(o))return o;let s={id:Z(),...y(t.value.actor),action:"delete.node",payload:{id:e.swimlaneId}},n=w([s],t.value.boot.stateBranchRoot);return l(n)?d(n.message):m("Deleted swimlane",{id:e.swimlaneId,ref:A(e.swimlaneId)})};var V=e=>({path:e.path,sha:e.shas[e.shas.length-1]??""});var Er=e=>{let t=e.lastIndexOf("/");return t===-1?".":e.slice(0,t)},ws=({commits:e,patch:t})=>{let o=e.map(r=>r.time),n=t.files.map(r=>({...V(r),changed:r.addedCount+r.removed})).reduce((r,i)=>r===null||i.changed>r.changed?i:r,null),a=t.insertions+t.deletions;return{commits:e.length,authors:[...new Set(e.map(r=>r.author))].sort(),firstCommitAt:o.length?Math.min(...o):null,lastCommitAt:o.length?Math.max(...o):null,files:t.files.length,filesAdded:t.files.filter(r=>r.status==="added").length,filesModified:t.files.filter(r=>r.status==="modified").length,filesDeleted:t.files.filter(r=>r.status==="deleted").length,binaryFiles:t.files.filter(r=>r.binary).length,directories:new Set(t.files.map(r=>Er(r.path))).size,insertions:t.insertions,deletions:t.deletions,net:t.insertions-t.deletions,largestFile:n,concentration:n&&a>0?n.changed/a:0,selfChurn:t.selfChurn,truncated:t.truncated}};var P={line:["//"],block:[["/*","*/"]]},H={line:["#"],block:[]},Mt={line:["--"],block:[]},ve={line:[],block:[["<!--","-->"]]},xr={line:[],block:[]},S=(e,t)=>({name:e,comment:t}),rt=S("TypeScript",P),it=S("JavaScript",P),Es={ts:rt,tsx:rt,mts:rt,cts:rt,js:it,jsx:it,mjs:it,cjs:it,py:S("Python",{line:["#"],block:[['"""','"""']]}),rb:S("Ruby",{line:["#"],block:[["=begin","=end"]]}),go:S("Go",P),rs:S("Rust",P),java:S("Java",P),kt:S("Kotlin",P),swift:S("Swift",P),c:S("C",P),h:S("C",P),cc:S("C++",P),cpp:S("C++",P),hpp:S("C++",P),cs:S("C#",P),php:S("PHP",P),scala:S("Scala",P),dart:S("Dart",P),lua:S("Lua",Mt),hs:S("Haskell",Mt),sql:S("SQL",Mt),ex:S("Elixir",H),exs:S("Elixir",H),pl:S("Perl",H),r:S("R",H),jl:S("Julia",H),sh:S("Shell",H),bash:S("Shell",H),zsh:S("Shell",H),vue:S("Vue",ve),svelte:S("Svelte",ve),html:S("HTML",ve),xml:S("XML",ve),md:S("Markdown",ve),mdx:S("Markdown",ve),css:S("CSS",{line:[],block:[["/*","*/"]]}),scss:S("CSS",P),less:S("CSS",P),yml:S("YAML",H),yaml:S("YAML",H),toml:S("TOML",H),ini:S("INI",H),json:S("JSON",xr)},Cr="Other",xs=e=>{let t=e.slice(e.lastIndexOf("/")+1),o=t.lastIndexOf(".");return o<=0?"":t.slice(o+1).toLowerCase()},ae=e=>Es[xs(e)]?.name??Cr,Me=e=>Es[xs(e)]?.comment??null,Br=[/\.(test|spec)\.[a-z0-9]+$/i,/(^|\/)test_[^/]+\.py$/,/_test\.[a-z0-9]+$/i,/(^|\/)conftest\.py$/,/Tests?\.(java|kt|cs|scala)$/],Lr=/(^|\/)(tests?|__tests__|spec|specs|e2e|src\/test|features)\//i,he=e=>Lr.test(e)||Br.some(t=>t.test(e)),Nr=/(^|\/)(package-lock\.json|npm-shrinkwrap\.json|yarn\.lock|pnpm-lock\.yaml|Cargo\.lock|go\.sum|poetry\.lock|Gemfile\.lock|composer\.lock)$/,kr=/(^|\/)(node_modules|dist|build|out|target|vendor|coverage|\.next|__snapshots__)\//,Pr=/(\.min\.(js|css)|\.map|\.snap|\.pb\.go|_pb2\.py)$/,X=e=>Nr.test(e)||kr.test(e)||Pr.test(e),Cs=e=>/(^|\/)(package\.json|go\.mod|Cargo\.toml|requirements[^/]*\.txt|pyproject\.toml|Gemfile|composer\.json|pom\.xml|build\.gradle(\.kts)?)$/.test(e),Bs=e=>/(^|\/)\.github\/workflows\//.test(e)||/(^|\/)(Dockerfile|docker-compose[^/]*\.ya?ml|Makefile|Jenkinsfile|\.gitlab-ci\.yml)$/.test(e)||/\.tf$/.test(e),Ls=e=>/\.(md|mdx|rst|txt)$/i.test(e)||/(^|\/)docs?\//i.test(e),Or=/\b(it|test|describe|context|suite)\.skip\b|\bx(it|describe|test)\s*\(|@pytest\.mark\.skip|@unittest\.skip|\bt\.Skip\s*\(|@(Ignore|Disabled)\b/,Mr=/\b(it|test|describe|context|suite)\.only\b|\bf(it|describe|test)\s*\(/,Ns=e=>Or.test(e),ks=e=>Mr.test(e);var Ps=/\b(TODO|FIXME|HACK|XXX)\b/,Dr=/[;{}]\s*$|^\s*(const|let|var|function|def|class|if|for|while|return|import|export|await|print)\b.*[=(]/,Dt=(e,t)=>{let o=e.trim();return o===""?"blank":t&&(t.line.some(s=>o.startsWith(s))||t.block.some(([s])=>o.startsWith(s))||t.block.some(([s])=>s==="/*")&&(o.startsWith("*")||o.startsWith("*/")))?"comment":"code"},Fr=(e,t)=>{let o=e.trim();for(let s of[...t.line,...t.block.flat(),"*"])if(o.startsWith(s)){o=o.slice(s.length).trim();break}return o},Os=({patch:e,repoShareByLanguage:t})=>{let o=new Map,s=0,n=0,a=0,r=0,i=0,u=0;for(let g of e.files){if(g.binary||X(g.path))continue;let c=Me(g.path);if(!c||c.line.length===0&&c.block.length===0)continue;let I=ae(g.path),v=o.get(I)??{comment:0,code:0};for(let{text:h}of g.added){let p=Dt(h,c);if(Ps.test(h)&&r++,p==="blank"){a++;continue}if(p==="comment"){s++,v.comment++,c&&Dr.test(Fr(h,c))&&u++;continue}n++,v.code++}for(let{text:h}of g.removedLines)Ps.test(h)&&i++;o.set(I,v)}let f=s+n;return{byLanguage:[...o.entries()].map(([g,c])=>({name:g,commentLines:c.comment,codeLines:c.code,share:c.comment+c.code===0?0:c.comment/(c.comment+c.code),repoShare:t?.get(g)??null})).sort((g,c)=>c.commentLines+c.codeLines-(g.commentLines+g.codeLines)),commentLines:s,codeLines:n,blankLines:a,share:f===0?0:s/f,todoLinesAdded:r,todoLinesRemoved:i,commentedOutCodeLines:u}};var _r=/\bconsole\.(log|debug|dir)\s*\(|\bdebugger\b|^\s*print\s*\(|\bfmt\.Print(ln|f)?\s*\(|\bSystem\.out\.print|\bdbg!\s*\(/,at=6,Ft=10,Gr=e=>{let t=0;for(let o of e){if(o.trim()===""||o.startsWith(" "))continue;let s=o.length-o.trimStart().length;s>0&&(t===0||s<t)&&(t=s)}return Math.min(Math.max(t,2),8)},$r=(e,t)=>{let o=e.length-e.trimStart().length;if(o===0)return 0;let s=e.slice(0,o).split(" ").length-1;return s>0?s:Math.floor(o/t)},Wr=e=>{if(e.length<at*2)return 0;let t=new Map,o=new Array(e.length).fill(!1);for(let s=0;s+at<=e.length;s++){let n=e.slice(s,s+at);if(n.some(i=>i.text.length<3))continue;let a=n.map(i=>i.text).join(`
|
|
7
|
-
`),r=t.get(a);if(r===void 0){t.set(a,{...n[0],at:s});continue}if(r.file!==n[0]?.file)for(let i=0;i<at;i++)o[r.at+i]=!0,o[s+i]=!0}return o.filter(Boolean).length},Ms=({patch:e})=>{let t=[],o=[],s=[],n=!1,a=0,r=0,i=null,u=[];for(let[f,g]of e.files.entries()){if(X(g.path)&&t.push(V(g)),Cs(g.path)&&o.push(V(g)),Bs(g.path)&&s.push(V(g)),Ls(g.path)&&(n=!0),g.binary||X(g.path))continue;let c=g.added.map(h=>h.text),I=Gr(c),v=g.status!=="deleted";for(let h of c){_r.test(h)&&a++;let p=$r(h,I);p>r&&(r=p,i=V(g)),v&&u.push({text:h.trim(),file:f})}u.push({text:" ",file:f})}return{generatedPaths:t.slice(0,Ft),dependencyManifests:o.slice(0,Ft),buildOrCiPaths:s.slice(0,Ft),docsTouched:n,debugPrintLinesAdded:a,maxIndentLevels:r,deepestFile:i,duplicatedLines:Wr(u)}};var Ds=({patch:e,languagesBefore:t})=>{let o=new Map,s=0;for(let r of e.files){if(r.binary)continue;let i=r.addedCount+r.removed;if(X(r.path)){s+=i;continue}let u=ae(r.path),f=o.get(u)??{name:u,added:0,removed:0,addedInTests:0,share:0};f.added+=r.addedCount,f.removed+=r.removed,he(r.path)&&(f.addedInTests+=r.addedCount),o.set(u,f)}let n=[...o.values()].reduce((r,i)=>r+i.added+i.removed,0),a=[...o.values()].map(r=>({...r,share:n===0?0:(r.added+r.removed)/n})).sort((r,i)=>i.added+i.removed-(r.added+r.removed));return{languages:a,introduced:t===null?[]:a.filter(r=>!t.has(r.name)).map(r=>r.name),generatedLines:s}};var jr=200,_t=2e5,Hr=5e4,Ur=25,Fs=64*1024*1024,Gt="",zr=/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/,Kr=/^diff --git a\/(.+) b\/\1$/,_s=(e,t)=>e==="/dev/null"?null:e.startsWith(t)?e.slice(2):e,Gs=(e,t)=>e===null?"added":t===null?"deleted":"modified",Xr=()=>({byPath:new Map,insertions:0,deletions:0,selfChurn:0,retainedLines:0}),qr=(e,t)=>{let o=e.byPath,s="",n=null,a=null,r=null,i=!1,u=0,f=(c,I)=>I==="modified"&&c==="added"?"added":I,g=(c,I)=>{let v=o.get(c);if(v)return v.entry.status=f(v.entry.status,I),v.entry.shas.includes(s)||v.entry.shas.push(s),v;let p={entry:{path:c,status:I,binary:!1,added:[],addedCount:0,removed:0,removedLines:[],shas:[s]},pendingByText:new Map};return o.set(c,p),p};for(let c of t.split(`
|
|
8
|
-
`)){if(c.startsWith(Gt)){s=c.slice(Gt.length).trim(),n=null,a=null,r=null,i=!1;continue}if(c.startsWith("diff --git ")){n=null,r=null,i=!0,a=Kr.exec(c)?.[1]??null;continue}if(i&&c.startsWith("Binary files ")&&a){let[,v=""]=/^Binary files (.*) differ$/.exec(c)??[],[h="",p=""]=v.split(" and ");n=g(a,Gs(h==="/dev/null"?null:h,p==="/dev/null"?null:p)),n.entry.binary=!0;continue}if(i&&c.startsWith("--- ")){r=_s(c.slice(4),"a/");continue}if(i&&c.startsWith("+++ ")){let v=_s(c.slice(4),"b/"),h=v??r??a;h&&(n=g(h,Gs(r,v)));continue}let I=zr.exec(c);if(I){u=Number(I[1]),i=!1;continue}if(n){if(c.startsWith("+")){let v=c.slice(1);e.insertions++,n.entry.addedCount++,e.retainedLines<_t&&(n.entry.added.push({sha:s,line:u,text:v}),e.retainedLines++),n.pendingByText.size<Hr&&n.pendingByText.set(v,(n.pendingByText.get(v)??0)+1),u++;continue}if(c.startsWith("-")){let v=c.slice(1);e.deletions++,n.entry.removed++,e.retainedLines<_t&&(n.entry.removedLines.push({sha:s,line:0,text:v}),e.retainedLines++);let h=n.pendingByText.get(v)??0;h>0&&v.trim().length>2&&(e.selfChurn++,n.pendingByText.set(v,h-1))}}}},Vr=e=>({files:[...e.byPath.values()].map(t=>t.entry),insertions:e.insertions,deletions:e.deletions,selfChurn:e.selfChurn,retainedLines:e.retainedLines});var $s=async({repoRoot:e,shas:t,cwdOf:o=()=>e})=>{if(t.length===0)return m("No commits to scan",{files:[],insertions:0,deletions:0,selfChurn:0,scannedCommits:0,truncated:!1});let s=t.slice(0,jr).reverse(),n=Xr(),a=0,r=0,i=!1,u=[];for(let g of s){let c=o(g),I=u[u.length-1];I&&I.cwd===c&&I.shas.length<Ur?I.shas.push(g):u.push({cwd:c,shas:[g]})}for(let{cwd:g,shas:c}of u){let I=await _e({cwd:g,maxOutput:Math.max(1,Fs-a),keepPartial:!0,args:["-c","core.quotePath=false","show","--unified=0","--no-renames","--no-color",`--format=${Gt}%H`,...c]});if(l(I)){if(g!==e)continue;return d(I.message)}if(qr(n,I.value.stdout),a+=I.value.stdout.length,r+=c.length,I.value.truncated||a>Fs){i=!0;break}}let f=Vr(n);return m("Scanned ticket patch",{files:f.files,insertions:f.insertions,deletions:f.deletions,selfChurn:f.selfChurn,scannedCommits:r,truncated:i||r<t.length||f.retainedLines>=_t})};var Yr=40,Jr=/^\d{6} blob ([0-9a-f]{40})\t(.+)$/,Ws=async({repoRoot:e,sha:t})=>{let o=await Ge({cwd:e,args:["-c","core.quotePath=false","ls-tree","-r",`${t}~1`]});if(o.exitCode!==0)return null;let s=new Set,n=new Map;for(let g of o.stdout.split(`
|
|
9
|
-
`)){let c=Jr.exec(g);if(!c)continue;let[,I="",v=""]=c;if(X(v))continue;let h=ae(v);if(s.add(h),he(v)||!Me(v))continue;let p=n.get(h)??[];p.length<Yr&&(p.push({blob:I,path:v}),n.set(h,p))}let a=[...n.values()].flat(),r=await jt(a.map(g=>g.blob),e);if(l(r))return{languages:s,commentShareByLanguage:new Map};let i=r.value,u=new Map;for(let{blob:g,path:c}of a){let I=i.get(g);if(I===void 0)continue;let v=Me(c),h=ae(c),p=u.get(h)??{comment:0,code:0};for(let b of I.split(`
|
|
10
|
-
`)){let T=Dt(b,v);T==="comment"&&p.comment++,T==="code"&&p.code++}u.set(h,p)}let f=new Map;for(let[g,c]of u){let I=c.comment+c.code;I>0&&f.set(g,c.comment/I)}return{languages:s,commentShareByLanguage:f}};var js=({patch:e})=>{let t=0,o=0,s=[],n=[],a=0,r=0;for(let i of e.files)if(!(i.binary||X(i.path))&&he(i.path)){t+=i.addedCount,o+=i.removed,i.status==="added"&&s.push(V(i)),i.status==="deleted"&&n.push(V(i));for(let{text:u}of i.added)Ns(u)&&a++,ks(u)&&r++}return{testLinesAdded:t,testLinesRemoved:o,addedTestFiles:s,deletedTestFiles:n,skippedTestLinesAdded:a,focusedTestLinesAdded:r}};var Qr=e=>e.reduce((t,o)=>t===null||o.time<t.time?o:t,null),Hs=async({repoRoot:e,ref:t,commits:o})=>{let s=await Co(e);if(l(s))return d(s.message);let n=Bo(s.value),a=new Map(o.map(c=>[c.sha,c.repo])),r=c=>n(a.get(c))??e,i=await $s({repoRoot:e,shas:o.map(c=>c.sha),cwdOf:r});if(l(i))return d(i.message);let u=i.value,f=Qr(o),g=f?await Ws({repoRoot:r(f.sha),sha:f.sha}):null;return m("Derived issue stats",{ref:t,shape:ws({commits:o,patch:u}),languages:Ds({patch:u,languagesBefore:g?.languages??null}),tests:js({patch:u}),comments:Os({patch:u,repoShareByLanguage:g?.commentShareByLanguage??null}),flags:Ms({patch:u})})};var lt=e=>{if(l(e))return e;let t=Ve(),o=l(t)?{}:t.value.emailLinks,s=l(t)?{}:t.value.contributors,n=so(o),a=e.value.map(r=>({...r,authorIdentity:qo({authorName:r.author,authorEmail:r.authorEmail,owners:n,registry:s})}));return m(e.message,a)};var ei=32,De=new Map;var ti=(e,t)=>{if(De.set(e,t),De.size>ei){let o=De.keys().next();o.done||De.delete(o.value)}return t},Us=e=>{let t=lt(e);return l(t)?d(t.message):m("Matched commits by ref",t.value.map(o=>({sha:o.sha,...o.repo?{repo:o.repo}:{},time:o.time,author:o.authorIdentity?.resolved?o.authorIdentity.name:o.author,subject:o.subject})))},zs=e=>{let t=$e(e??process.cwd());return l(t)?d(t.message):t},Ks=async(e,t,o)=>{let s=[e,t,o.map(r=>`${r.sha}:${r.author}`).join(",")].join(" "),n=De.get(s);if(n)return m("Derived issue stats",n);let a=await Hs({repoRoot:e,ref:t,commits:o});return l(a)?d(a.message):m("Derived issue stats",ti(s,a.value))},Lc=async(e={idOrRef:""})=>{let t=e.idOrRef.trim();if(!t)return d("idOrRef must not be empty");let o=t.length>7?A(t):t.toUpperCase(),s=zs(e.repoRoot);if(l(s))return s;let n=Us(await Lo({repoRoot:s.value,ref:o,end:e.end}));return l(n)?n:Ks(s.value,o,n.value)},Xs=async e=>{let t=zs(e.repoRoot);if(l(t))return t;let o=Us(await Ke({repoRoot:t.value,refs:e.refs,end:e.end}));return l(o)?o:Ks(t.value,e.label,o.value)};var qs=e=>{let t=B();if(l(t))return t;let o=de(e);return o?m("Found goal",{id:o.id,refs:po(o.id).map(s=>A(s.id))}):d(`No goal called or with the id "${e}"`)},Vs=async e=>{let t=qs(e.goal);return l(t)?t:Xs({repoRoot:e.repoRoot,label:t.value.id,refs:t.value.refs,end:e.end})},oi=async e=>{let t=qs(e.goal);return l(t)?t:_o(e.repoRoot,lt(await Ke({repoRoot:e.repoRoot,refs:t.value.refs,end:e.end})))},si=async e=>{let t=await L(e.repoRoot);return l(t)?t:Vs({repoRoot:t.value.repoRoot,goal:e.goal})};import{ulid as be}from"ulid";async function ni(e){let t=await L(e.repoRoot);if(l(t))return t;let o=W();if(l(o))return o;let s=Pe(e);if(l(s))return s;let n=C(e.tagName).trim();if(!n)return d("Tag name cannot be empty");let a=M("Tag name",n,60);if(a)return d(a);let r=Oe(s.value,N);if(!e.issueIds&&r.failed[0])return d(r.failed[0].reason);if(r.done.length===0)return d(Ie("Tagged",r).message);let i=_.findTagByName(n),u=i?.id??be(),f=[...i?[]:[{id:be(),...y(o.value),action:"create.tag",payload:{id:u,name:n}}],...r.done.map(({id:v})=>({id:be(),...y(o.value),action:"add.issue.tag",payload:{id:v,tag:u}}))],g=w(f,t.value.stateBranchRoot);if(l(g))return d(g.message);let c={id:u,name:n};if(!e.issueIds)return m("Added issue tag",{...r.done[0],tag:c});let I=Ie("Tagged",r);return l(I)?I:m(I.message,{...I.value,tag:c})}var ri=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=t.value.state.tags[e.tagId];if(!o)return d("Tag not found");if(o.tombstoned)return d("Tag is already deleted");let s=[{id:be(),...y(t.value.actor),action:"tombstone.tag",payload:{id:e.tagId}}],n=w(s,t.value.boot.stateBranchRoot);return l(n)?d(n.message):m("Deleted tag",{id:o.id,name:o.name})},ii=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=t.value.state.tags[e.tagId];if(!o)return d("Tag not found");if(!o.tombstoned)return d("Tag is not deleted");if(_.findTagByName(o.name))return d(`Cannot restore: another tag is already named "${o.name}"`);let s=[{id:be(),...y(t.value.actor),action:"restore.tag",payload:{id:e.tagId,name:o.name}}],n=w(s,t.value.boot.stateBranchRoot);return l(n)?d(n.message):m("Restored tag",{id:o.id,name:o.name})},ai=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=N(e.issueId);if(l(o))return o;if(!t.value.state.tags[e.tagId])return d("Tag not found");let s={id:be(),...y(t.value.actor),action:"remove.issue.tag",payload:{id:e.issueId,tag:e.tagId}},n=w([s],t.value.boot.stateBranchRoot);return l(n)?d(n.message):m("Removed issue tag",{id:e.issueId,ref:A(e.issueId),tagId:e.tagId})};import{ulid as J}from"ulid";var li=async e=>{let t=Wt(e.name,"name");if(l(t))return d(t.message);let o=await R(e.repoRoot);if(l(o))return o;let s=o.value.actor,n=!!o.value.state.contributors[s.userId];if(!n){let r=w([{id:J(),...y(s),action:"create.contributor",payload:{id:s.userId,name:s.userName}}],o.value.boot.stateBranchRoot);if(l(r))return d(r.message)}let a=Object.values(o.value.state.nodes).filter(r=>$(r)&&!r.isDeleted).flatMap(r=>{let{boardDescription:i}=Ce(r.id);return i?[{id:r.id,title:C(r.title),boardDescription:i}]:[]});return m(`Assumed ${s.userName}`,{...s,registered:!n,...a.length?{boards:a}:{}})},Ys=async(e,t)=>{let o=ce(e).get(t);return o===void 0?void 0:{id:t,name:o}},di=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=N(e.issueId);if(l(o))return o;let s=e.self?t.value.actor.userId:e.assigneeId;if(s){let v=t.value.state.contributors[s],h=v?void 0:await Ys(t.value.boot.stateBranchRoot,s);if(!v&&!h)return d("Unknown assignee id");let p=v??h,b=[...v?[]:[{id:J(),...y(t.value.actor),action:"create.contributor",payload:{id:p.id,name:p.name}}],{id:J(),...y(t.value.actor),action:"add.issue.assignee",payload:{id:e.issueId,assignee:p.id}}],T=w(b,t.value.boot.stateBranchRoot);return l(T)?d(T.message):m("Added issue assignee",{id:e.issueId,ref:A(e.issueId),assignee:{id:p.id,name:p.name}})}let n=C(e.assigneeName??"").trim();if(!n)return d("Provide assigneeId, self or assigneeName");let a=M("Assignee name",n,80);if(a)return d(a);let r=Tt({events:t.value.state.eventLog??[],registry:t.value.state.contributors,logFileNames:ce(t.value.boot.stateBranchRoot)}).filter(v=>v.name===n);if(r.length>1)return d(`"${n}" matches ${r.length} contributors (${r.map(({id:v})=>v).join(", ")}). Assign by assigneeId to choose.`);let i=r[0];if(!i&&!e.createUnlinked)return d(`No contributor named "${n}". Assign by id, or pass createUnlinked to add them as an external assignee.`);let u=i?.id??J(),f=i?.name??n,c=[...!!t.value.state.contributors[u]?[]:[{id:J(),...y(t.value.actor),action:"create.contributor",payload:{id:u,name:f}}],{id:J(),...y(t.value.actor),action:"add.issue.assignee",payload:{id:e.issueId,assignee:u}}],I=w(c,t.value.boot.stateBranchRoot);return l(I)?d(I.message):m("Added issue assignee",{id:e.issueId,ref:A(e.issueId),assignee:{id:u,name:f}})},ui=async e=>{let t=await R(e.repoRoot);if(l(t))return t;if(!t.value.state.contributors[e.contributorId])return d("Contributor not found");if(await Ys(t.value.boot.stateBranchRoot,e.contributorId))return d("Cannot remove a contributor who has authored events \u2014 their name appears throughout the log");let n=[{id:J(),...y(t.value.actor),action:"tombstone.contributor",payload:{id:e.contributorId}}],a=w(n,t.value.boot.stateBranchRoot);return l(a)?d(a.message):m("Tombstoned contributor",{id:e.contributorId,name:We})},ci=async(e,t)=>{let o=oe(e);if(l(o))return;let s;for(let n of o.value){if(n.action!=="create.contributor")continue;let a=n.payload;a.id===t&&a.name&&(s=a.name)}return s},mi=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=t.value.state.contributors[e.contributorId];if(!o)return d("Contributor not found");if(!o.tombstoned)return d("Contributor is not tombstoned");let s=await ci(t.value.boot.stateBranchRoot,e.contributorId);if(!s)return d("Cannot restore this contributor \u2014 no original name found in the event log");let n=[{id:J(),...y(t.value.actor),action:"restore.contributor",payload:{id:e.contributorId,name:s}}],a=w(n,t.value.boot.stateBranchRoot);return l(a)?d(a.message):m("Restored contributor",{id:e.contributorId,name:s})},pi=async(e={})=>{let t=await R(e.repoRoot);if(l(t))return t;let o=oe(t.value.boot.stateBranchRoot);if(l(o))return d(o.message);let s=e.boardId?ns(o.value,e.boardId):o.value,n=new Set,a=wo(t.value.boot.stateBranchRoot);if(l(a))return d(a.message);for(let u of a.value)u.userId&&n.add(u.userId);let r=t.value.state.contributors,i=Tt({events:s,registry:r,logFileNames:ce(t.value.boot.stateBranchRoot)}).map(({id:u,name:f,isExternal:g})=>({...D(u,f),isSelf:u===t.value.actor.userId,isExternal:g,isRemoved:r[u]?.tombstoned===!0,hasAuthoredAnywhere:n.has(u)}));return m("Listed board contributors",i)},fi=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=N(e.issueId);if(l(o))return o;if(!t.value.state.contributors[e.assigneeId])return d("Assignee not found");let s={id:J(),...y(t.value.actor),action:"remove.issue.assignee",payload:{id:e.issueId,assignee:e.assigneeId}},n=w([s],t.value.boot.stateBranchRoot);return l(n)?d(n.message):m("Removed issue assignee",{id:e.issueId,ref:A(e.issueId),assigneeId:e.assigneeId})};import{ulid as ut}from"ulid";var gi=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=N(e.issueId);if(l(o))return o;let s=e.body.trim();if(!s)return d("Comment cannot be empty");if(s.length>4e3)return d(`Comment cannot exceed ${4e3} characters (got ${s.length})`);let n=ut(),a={id:ut(),...y(t.value.actor),action:"add.issue.comment",payload:{id:n,issue:e.issueId,md:s,author:t.value.actor.userId}},r=w([a],t.value.boot.stateBranchRoot);return l(r)?d(r.message):m("Added issue comment",{id:n,issueId:e.issueId,body:s})},Ii=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=t.value.state.eventLog.find(i=>i.action==="add.issue.comment"&&i.payload.id===e.commentId);if(!o)return d("Unable to resolve comment");if(o.payload.author!==t.value.actor.userId)return d("You can only delete your own comments");let s=N(o.payload.issue);if(l(s))return s;if(t.value.state.eventLog.some(i=>i.action==="delete.issue.comment"&&i.payload.id===e.commentId))return m("Comment already deleted",{id:e.commentId,issueId:o.payload.issue});let a={id:ut(),...y(t.value.actor),action:"delete.issue.comment",payload:{id:e.commentId,issue:o.payload.issue}},r=w([a],t.value.boot.stateBranchRoot);return l(r)?d(r.message):m("Deleted issue comment",{id:e.commentId,issueId:o.payload.issue})},vi=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=t.value.state.eventLog.find(u=>u.action==="add.issue.comment"&&u.payload.id===e.commentId);if(!o)return d("Unable to resolve comment");if(o.payload.author!==t.value.actor.userId)return d("You can only edit your own comments");let s=N(o.payload.issue);if(l(s))return s;if(t.value.state.eventLog.some(u=>u.action==="delete.issue.comment"&&u.payload.id===e.commentId))return d("Comment was deleted");let a=e.body.trim();if(!a)return d("Comment cannot be empty");if(a.length>4e3)return d(`Comment cannot exceed ${4e3} characters (got ${a.length})`);let r={id:ut(),...y(t.value.actor),action:"edit.issue.comment",payload:{id:e.commentId,issue:o.payload.issue,md:a}},i=w([r],t.value.boot.stateBranchRoot);return l(i)?d(i.message):m("Edited issue comment",{id:e.commentId,issueId:o.payload.issue,body:a})};import{ulid as Js}from"ulid";var hi=async e=>{let t=await R(e.repoRoot);if(l(t))return t;if(!eo(e.email))return d(`Not an email address: ${e.email}`);let o=Ae(e.email),s=e.contributorId??t.value.actor.userId;if(!t.value.state.contributors[s])return d(`Unknown contributor: ${s}`);let n=mt(t.value.state.emailLinks,o);if(n.includes(s))return m(`${o} is already yours`,{contributor:s,email:o,contested:n.filter(r=>r!==s)});let a=w([{id:Js(),action:"link.contributor.email",payload:{contributor:s,email:o},userId:t.value.actor.userId}],t.value.boot.stateBranchRoot);return l(a)?d(a.message):m(n.length>0?`Claimed ${o}, but somebody else claims it too, so it now resolves to neither of you`:`Claimed ${o}`,{contributor:s,email:o,contested:n})},bi=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let o=Ae(e.email),s=e.contributorId??t.value.actor.userId,n=t.value.state.emailLinks[to(o,s)];if(!n||n.tombstoned)return d(`No such link: ${o} to ${s}`);if(!no(t.value.actor.userId,n))return d("Only whoever made a claim, or the contributor it names, may undo it");let a=w([{id:Js(),action:"unlink.contributor.email",payload:{contributor:s,email:o},userId:t.value.actor.userId}],t.value.boot.stateBranchRoot);return l(a)?d(a.message):m(`Unclaimed ${o}`,{contributor:s,email:o})},Ri=async(e={})=>{let t=await L(e.repoRoot);if(l(t))return t;let o=B();if(l(o))return o;let{emailLinks:s,contributors:n}=o.value,a=new Map;for(let c of Object.values(s)){if(c.tombstoned)continue;let I=a.get(c.email)??new Set;I.add(c.contributor),a.set(c.email,I)}let r=[...a.entries()].map(([c,I])=>({email:c,contested:I.size>1,claimants:[...I].map(v=>D(v,n[v]?.name))})).sort((c,I)=>c.email.localeCompare(I.email)),i=W(),u=l(i)?[]:oo(s,i.value.userId),f=Ut().gitEmail,g=f?mt(s,f).filter(c=>l(i)||c!==i.value.userId):[];return m("Listed contributor emails",{emails:r,mine:u,git:f?{email:f,linkedToMe:u.includes(f),heldByOthers:g.map(c=>D(c,n[c]?.name))}:null})},yi=async(e={})=>{let t=await R(e.repoRoot);if(l(t))return t;let{userId:o,userName:s}=t.value.actor,n=le(t.value.boot.repoRoot),a=await qe({repoRoot:t.value.boot.repoRoot,stateBranch:l(n)?void 0:n.value,names:[s,await fe(t.value.boot.repoRoot)],links:t.value.state.emailLinks});if(l(a))return a;let r=Vo(a.value),i=r.filter(u=>u.looksLikeYours);return m(r.length===0?"No unclaimed addresses in this history":`${r.length} unclaimed address(es) in this history, ${i.length>0?`${i.length} of which look like yours`:"none obviously yours"}. Show them to the user and link only the ones they confirm; each is a permanent event that replicates to every clone.`,{contributor:o,candidates:r,likely:i.length})};var $t=e=>{let t=No(e);return l(t)?d(t.message):m("Listed repos",{repos:t.value,claimedBytes:Do(e),limit:Xt,allowLocal:Kt()})},Ai=async(e={})=>{let t=Se(e.repoRoot);return l(t)?d(t.message):$t(t.value)},Ti=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let{repoRoot:o}=t.value.boot,s=await Fo({repoRoot:o,actorId:t.value.actor.userId,url:e.url,branches:e.branches,since:e.since,label:e.label,mode:e.previewOnly?"preview":e.confirm?"confirm":"auto"});if(l(s))return d(s.message);let n=$t(o);return l(n)?d(n.message):m(s.message,{...n.value,...s.value.kind==="preview"?{preview:s.value.preview}:{}})},Si=async e=>{let t=await R(e.repoRoot);if(l(t))return t;let{repoRoot:o}=t.value.boot,s=ko({repoRoot:o,actorId:t.value.actor.userId,repo:e.repo});if(l(s))return d(s.message);let n=$t(o);return l(n)?d(n.message):m(s.message,n.value)},wi=e=>(Mo("repo"in e?e.repo:qt(e.url.trim())),m("Cancelled",null)),Ei=async(e,t={},o=2e4)=>{if(l(e))return e;let s,n=await Promise.race([Qs(t),new Promise(a=>{s=setTimeout(()=>a(null),o)})]);return clearTimeout(s),n===null?m(`${e.message} \xB7 still fetching other repos; epiq_repo_list shows how they stand`,e.value):l(n)||n.value.length===0?e:m([e.message,Oo(n.value)].filter(Boolean).join(" \xB7 "),{...e.value,repos:n.value})},Qs=async(e={})=>{let t=Se(e.repoRoot);return l(t)?d(t.message):Po({repoRoot:t.value,minIntervalMs:e.minIntervalMs})};var Zs=(e,t)=>{let o=0,s=0,n=null;for(let a of e){if(a.userId!==t)continue;a.action==="add.issue"&&o++,a.action==="add.issue.comment"&&s++;let r=Qt(a.id);if(l(r))continue;let i=r.value.getTime();(n===null||i<n)&&(n=i)}return{tickets:o,comments:s,joinedAt:n}};var xi=async(e={})=>{let t=await R(e.repoRoot);if(l(t))return t;let{userId:o,userName:s}=t.value.actor,n=Zs(t.value.state.eventLog,o),a=le(t.value.boot.repoRoot),r=await qe({repoRoot:t.value.boot.repoRoot,stateBranch:l(a)?void 0:a.value,names:[s,await fe(t.value.boot.repoRoot)],links:t.value.state.emailLinks}),i=l(r)?[]:r.value.filter(u=>u.claimedBy.length===1&&u.claimedBy[0]===o);return m("Derived personal stats",{...n,scanned:!l(r),commits:i.reduce((u,f)=>u+f.commits,0),claimedEmails:i.length})};var en=e=>{if(e.length===0)return null;let t=Math.floor(e.length/2);return e.length%2===0?(e[t-1]+e[t])/2:e[t]};var Ci=8,Bi=(e,t)=>{let o=new Date(e);return o.setHours(0,0,0,0),o.setDate(o.getDate()-(o.getDay()+6)%7),Array.from({length:t},(s,n)=>{let a=new Date(o);return a.setDate(o.getDate()-7*(t-1-n)),a.getTime()})},Li=e=>en([...e].sort((t,o)=>t-o)),tn=({boardId:e,lanes:t,formerLaneIds:o=[],tickets:s,now:n})=>{let a=new Map(t.map((E,x)=>[E.id,x])),r=new Set(o),i=E=>E!=null&&(a.has(E)||r.has(E)),f=s.map(E=>({ticket:E,visits:Nt(E.log,E.createdAt)})).filter(({visits:E})=>E.some(x=>i(x.laneId))),g=E=>{let x=te(E);return x===null?null:Math.min(ye(x,n),n)},c=E=>{let x=E.flatMap(q=>{let G=a.get(q.laneId);return G===void 0?[]:[G]});return x.some((q,G)=>G>0&&q<x[G-1])},I=f.flatMap(({ticket:E,visits:x})=>{let q=E.log.findLast(Re=>Re.action==="close.issue");if(!q||i(E.parentNodeId))return[];let G=g(q),sn=x.at(-2)?.laneId;if(G===null||!i(sn))return[];let nn=x.slice(0,-1).flatMap((Re,rn)=>i(Re.laneId)?[{laneId:Re.laneId,ms:x[rn+1].enteredAt-Re.enteredAt}]:[]);return[{closedAt:G,leadTime:G-E.createdAt,sentBack:c(x),stays:nn}]}),v=Bi(n,Ci),h=v[0],p=I.filter(E=>E.closedAt>=h),b=v.map((E,x)=>{let q=v[x+1]??1/0;return{t:E,closed:p.filter(G=>G.closedAt>=E&&G.closedAt<q).length}}),T=new Map;for(let{stays:E}of p)for(let{laneId:x,ms:q}of E)T.set(x,(T.get(x)??0)+q);let Y=t.reduce((E,x)=>E+(T.get(x.id)??0),0),on=t.flatMap(E=>{let x=T.get(E.id)??0;return x<=0?[]:[{laneId:E.id,title:E.title,meanMs:x/p.length,share:x/Y}]});return{boardId:e,throughput:b,filed:f.filter(({ticket:E,visits:x})=>E.createdAt>=h&&i(x[0]?.laneId)).length,closed:p.length,medianLeadTimeMs:Li(p.map(E=>E.leadTime)),sentBackShare:p.length===0?null:p.filter(E=>E.sentBack).length/p.length,laneTime:on}};var Ni=e=>{let t=B();if(l(t))return t;let o=Object.values(t.value.nodes),s=o.find(r=>r.id===e.boardId);if(!s||s.isDeleted||!$(s))return d("Board not found");let n=o.filter(r=>U(r)&&r.parentNodeId===s.id),a=n.filter(r=>!r.isDeleted).sort((r,i)=>r.rank.localeCompare(i.rank));return m("Derived board overview",tn({boardId:s.id,lanes:a,formerLaneIds:n.filter(r=>r.isDeleted).map(r=>r.id),tickets:o.filter(r=>!r.isDeleted).filter(F).map(r=>({id:r.id,parentNodeId:r.parentNodeId,log:r.log??[],createdAt:O(r.id)})),now:Date.now()}))};export{Nt as a,us as b,z as c,ra as d,xn as e,ia as f,aa as g,fs as h,fe as i,B as j,L as k,kn as l,Pn as m,On as n,Mn as o,Dn as p,vs as q,Fn as r,_n as s,$n as t,Un as u,zn as v,Kn as w,tr as x,sr as y,kt as z,rr as A,ir as B,ar as C,lr as D,dr as E,cr as F,mr as G,fr as H,gr as I,Ir as J,vr as K,hr as L,br as M,Rr as N,yr as O,Ar as P,Tr as Q,Sr as R,wr as S,lt as T,Lc as U,Vs as V,oi as W,si as X,ni as Y,ri as Z,ii as _,ai as $,li as aa,di as ba,ui as ca,mi as da,pi as ea,fi as fa,gi as ga,Ii as ha,vi as ia,hi as ja,bi as ka,Ri as la,yi as ma,xi as na,Ai as oa,Ti as pa,Si as qa,wi as ra,Ei as sa,Qs as ta,en as ua,Ni as va};
|