squad-kit 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +11 -9
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -105,13 +105,13 @@ squad-kit-plan-status: partial
105
105
  \u2026`:input7.description,workItemRefs:input7.workItemIds.map(id2=>({id:String(id2)}))});return this.pr(input7.repo,pr)}async findPullRequest(repo,source){const data=await this.call("GET",`_apis/git/repositories/${encodeURIComponent(repo)}/pullrequests?searchCriteria.sourceRefName=${encodeURIComponent(`refs/heads/${source}`)}&searchCriteria.status=active`);return data.value[0]?this.pr(repo,data.value[0]):null}async attachToPullRequest(repo,prId,file3){const name=path32.basename(file3);const data=await this.call("POST",`_apis/git/repositories/${encodeURIComponent(repo)}/pullRequests/${prId}/attachments/${encodeURIComponent(name)}`,fs40.readFileSync(file3),"application/octet-stream");return data.url}async updatePullRequestDescription(repo,prId,description){await this.call("PATCH",`_apis/git/repositories/${encodeURIComponent(repo)}/pullrequests/${prId}`,{description:description.length>3990?`${description.slice(0,3980)}
106
106
  \u2026`:description})}async getPullRequest(repo,prId){return this.call("GET",`_apis/git/repositories/${encodeURIComponent(repo)}/pullrequests/${prId}`)}async completePullRequest(repo,prId,message){let pr=await this.getPullRequest(repo,prId);if(pr.status!=="completed"){pr=await this.call("PATCH",`_apis/git/repositories/${encodeURIComponent(repo)}/pullrequests/${prId}`,{status:"completed",lastMergeSourceCommit:pr.lastMergeSourceCommit,completionOptions:{mergeStrategy:"noFastForward",deleteSourceBranch:true,mergeCommitMessage:message,transitionWorkItems:false}})}for(let i=0;i<30&&pr.status!=="completed";i++){if(pr.mergeStatus==="conflicts"||pr.mergeStatus==="failure"||pr.mergeStatus==="rejectedByPolicy"){throw new AzureError(`Azure could not merge PR ${prId} (${pr.mergeStatus}). Open it in Azure to see why.`,void 0,pr.mergeStatus==="conflicts"?"clash":"other")}await new Promise(r=>setTimeout(r,2e3));pr=await this.getPullRequest(repo,prId)}if(pr.status!=="completed"){throw new AzureError(`PR ${prId} is not merged yet. It may be waiting for a required review or build in Azure.`)}return{mergeCommit:pr.lastMergeCommit?.commitId??""}}async abandonPullRequest(repo,prId){await this.call("PATCH",`_apis/git/repositories/${encodeURIComponent(repo)}/pullrequests/${prId}`,{status:"abandoned"})}pr(repo,raw){return{id:raw.pullRequestId,status:raw.status,url:`${this.projUrl}/_git/${encodeURIComponent(repo)}/pullrequest/${raw.pullRequestId}`}}row(w){const f=w.fields??{};const points=f["Microsoft.VSTS.Scheduling.StoryPoints"]??f["Microsoft.VSTS.Scheduling.Effort"];return{id:w.id,type:str(f["System.WorkItemType"]),title:str(f["System.Title"]),state:str(f["System.State"]),points:typeof points==="number"?points:void 0,priority:typeof f["Microsoft.VSTS.Common.Priority"]==="number"?f["Microsoft.VSTS.Common.Priority"]:void 0,sprint:str(f["System.IterationPath"]).split("\\").slice(1).join("\\")||void 0,assignedTo:f["System.AssignedTo"]?.displayName,tags:parseTags2(f["System.Tags"]),url:this.webUrl(w.id)}}};var ROW_FIELDS=["System.Id","System.WorkItemType","System.Title","System.State","System.IterationPath","System.AssignedTo","System.Tags","Microsoft.VSTS.Scheduling.StoryPoints","Microsoft.VSTS.Scheduling.Effort","Microsoft.VSTS.Common.Priority"];var PROCESS_TYPES={Agile:{epic:"Epic",feature:"Feature",story:"User Story"},Scrum:{epic:"Epic",feature:"Feature",story:"Product Backlog Item"},CMMI:{epic:"Epic",feature:"Feature",story:"Requirement"},Basic:{epic:"Epic",feature:"Issue",story:"Task"}};function processOf(types){const has3=name=>types.some(t=>t.toLowerCase()===name.toLowerCase());if(has3("User Story"))return"Agile";if(has3("Product Backlog Item"))return"Scrum";if(has3("Requirement"))return"CMMI";if(has3("Issue")&&has3("Task"))return"Basic";return null}var str=v=>typeof v==="string"?v:"";function pickState(states,category,currentIndex=-1){const byCategory=states.filter(s=>s.category===category);if(byCategory.length)return byCategory[0].name;if(category==="Resolved"){const named=states.find(s=>/^resolved$/i.test(s.name));if(named)return named.name;const later=states.filter((s,i)=>s.category==="InProgress"&&i>currentIndex);return later.length>1?later.at(-1).name:void 0}return void 0}function parseTags2(raw){return typeof raw==="string"?raw.split(";").map(s=>s.trim()).filter(Boolean):[]}function htmlToText(html){const breaks=html.replace(/<\s*br\s*\/?>/gi,"\n").replace(/<li[^>]*>/gi,"- ").replace(/<\/(p|div|li|h[1-6]|tr)>/gi,"\n");return withoutTags(breaks).replace(/&nbsp;/g," ").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#39;/g,"'").replace(/&amp;/g,"&").replace(/\n{3,}/g,"\n\n").trim()}function escapeHtml(s){return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function parseAzureRemote(url){const clean4=url.trim().replace(/\.git$/,"");const patterns=[/^https:\/\/(?:[^@/]+@)?dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/?#]+)/i,/^https:\/\/(?:[^@/]+@)?([^.]+)\.visualstudio\.com\/(?:DefaultCollection\/)?([^/]+)\/_git\/([^/?#]+)/i,/^(?:ssh:\/\/)?git@ssh\.dev\.azure\.com:(?:22\/)?v3\/([^/]+)\/([^/]+)\/([^/]+)$/i];for(const p of patterns){const m=clean4.match(p);if(m?.[1]&&m[2]&&m[3])return{organization:decodeURIComponent(m[1]),project:decodeURIComponent(m[2]),repo:decodeURIComponent(m[3])}}return null}function azureForProject(root){const paths=buildPaths(root);if(!fs40.existsSync(paths.configFile))return null;const config2=loadConfig(paths.configFile);if(config2.tracker.type!=="azure")return null;const secrets=overlayTrackerEnv(loadSecrets(paths.secretsFile));const az=secrets.tracker?.azure;const organization=az?.organization??config2.tracker.workspace;const project=az?.project??config2.tracker.project;if(!organization||!project||!az?.pat)return null;return new Azure({organization,project,pat:az.pat})}function gitAuthEnv(pat,remoteUrl){if(!pat||!/^https:\/\//i.test(remoteUrl)||!parseAzureRemote(remoteUrl))return{};const host=new URL(remoteUrl).host;return{GIT_CONFIG_COUNT:"1",GIT_CONFIG_KEY_0:`http.https://${host}/.extraheader`,GIT_CONFIG_VALUE_0:`AUTHORIZATION: Basic ${Buffer.from(`:${pat}`).toString("base64")}`,GIT_TERMINAL_PROMPT:"0"}}function markdownToHtml(md){const out17=[];let list2=null;let para2=[];const flushPara=()=>{if(para2.length)out17.push(`<p>${para2.map(inline).join("<br>")}</p>`);para2=[]};const flushList=()=>{if(list2)out17.push(`<${list2.tag}>${list2.items.map(i=>`<li>${i}</li>`).join("")}</${list2.tag}>`);list2=null};for(const raw of md.replace(/\r\n/g,"\n").split("\n")){const line3=raw.trimEnd();const bullet=/^\s{0,1}[-*] (.*)$/.exec(line3);const numbered=/^\s{0,1}\d+\. (.*)$/.exec(line3);const heading=/^#{1,6} (.*)$/.exec(line3);if(!line3.trim()){flushPara();flushList()}else if(bullet||numbered){flushPara();const tag=bullet?"ul":"ol";if(!list2||list2.tag!==tag){flushList();list2={tag,items:[]}}list2.items.push(inline((bullet??numbered)[1]))}else if(list2&&/^\s{2,}\S/.test(line3)){list2.items[list2.items.length-1]+=`<br>${inline(line3.trim())}`}else if(heading){flushPara();flushList();out17.push(`<h3>${inline(heading[1])}</h3>`)}else{flushList();para2.push(line3.trim())}}flushPara();flushList();return out17.join("")}function inline(text3){return escapeHtml(text3).replace(/`([^`]+)`/g,"<code>$1</code>").replace(/\*\*([^*]+)\*\*/g,"<b>$1</b>").replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g,(_,label2,url)=>`<a href="${url.replace(/"/g,"&quot;")}">${label2}</a>`)}var CRITERIA="Microsoft.VSTS.Common.AcceptanceCriteria";var PARENT="System.LinkTypes.Hierarchy-Reverse";var PREDECESSOR="System.LinkTypes.Dependency-Reverse";var LEVEL_WORD={epic:"epics",feature:"features",story:"stories"};var STAGE={"not-started":"Proposed","in-progress":"InProgress",resolved:"Resolved",closed:"Completed"};var SEVERITY={"stops-work":"1 - Critical",annoying:"3 - Medium",small:"4 - Low"};function num2(id2){const n=Number(id2);if(!Number.isInteger(n)||n<=0)throw new Error(`"${id2}" is not a work item number.`);return n}function row(w){return{...w,id:String(w.id),ref:`AB#${w.id}`,isBug:w.type==="Bug"}}var AzureTracker=class{constructor(az){this.az=az}az;name="azure";label="Azure DevOps";abilities={sprints:true,wiki:true,bugs:true,tags:true,create:true};get identity(){const t=this.az.target;return`${t.organization}/${t.project}/${t.pat.slice(-6)}`}parseRef(text3){const id2=text3.trim().replace(/^AB#/i,"").replace(/^#/,"");return/^\d+$/.test(id2)&&Number(id2)>0?id2:null}refOf(id2){return`AB#${id2}`}whoAmI(){return this.az.whoAmI()}sprints(){return this.az.sprints()}async list(opts={}){return(await this.az.listWorkItems({...opts.sprint?{sprintPath:opts.sprint}:{},...opts.includeDone?{includeDone:true}:{}})).map(row)}async get(id2){const w=await this.az.getWorkItem(num2(id2));return{...row(w),description:w.description,acceptanceCriteria:w.acceptanceCriteria,reproSteps:w.reproSteps,parents:w.parents.map(p=>({...p,id:String(p.id)})),dependsOn:w.dependsOn.map(d=>({ref:`AB#${d.id}`,title:d.title,state:d.state})),attachments:w.attachments}}download(url,file3){return this.az.download(url,file3)}wikiPages(){return this.az.wikiPages()}moveTo(id2,stage){return this.az.moveTo(num2(id2),STAGE[stage])}comment(id2,markdown){return this.az.comment(num2(id2),markdownToHtml(markdown))}addTag(id2,tag){return this.az.addTag(num2(id2),tag)}async fileBug(input7){return row(await this.az.createBug({title:input7.title,reproSteps:markdownToHtml(input7.steps),severity:SEVERITY[input7.severity],tags:input7.tags,relatedIds:input7.related.map(num2)}))}async levels(){const types=(await this.az.workItemTypes()).map(t=>t.name);const process5=processOf(types);if(!process5){throw new AzureError(`Project ${this.az.target.project} on Azure DevOps has no User Story, Product Backlog Item, Requirement or Issue work item type, so squad-kit cannot tell which items to make for the stories.`,void 0,"not-found")}return{...PROCESS_TYPES[process5],process:process5,project:this.az.target.project}}async createItem(input7){const levels=await this.levels();const type=levels[input7.level];if(!(await this.az.workItemTypes()).some(t=>t.name.toLowerCase()===type.toLowerCase())){throw new AzureError(`Project ${this.az.target.project} on Azure DevOps has no ${type} work item type, which the ${levels.process} process uses for ${LEVEL_WORD[input7.level]}. Turn it on in the project's process settings, or ask the person who runs Azure DevOps.`,void 0,"not-found")}const ops=[{op:"add",path:"/fields/System.Title",value:input7.title},...await this.textOps(type,input7.markdown,input7.acceptance)];if(input7.tags?.length)ops.push({op:"add",path:"/fields/System.Tags",value:input7.tags.join("; ")});if(input7.parent)ops.push(this.link(PARENT,num2(input7.parent)));return row(await this.writing(`make ${/^[aeiou]/i.test(type)?"an":"a"} ${type}`,()=>this.az.createWorkItem(type,ops)))}async findMade(input7){const type=(await this.levels())[input7.level];const found2=await this.az.findByTagAndText(type,input7.tag,input7.mark);return found2?row(found2):null}async updateItem(id2,change){const n=num2(id2);const item=await this.az.linksOf(n);const ops=[];if(change.title!==void 0)ops.push({op:"add",path:"/fields/System.Title",value:change.title});if(change.markdown!==void 0||change.acceptance!==void 0)ops.push(...await this.textOps(item.type,change.markdown,change.acceptance));if(change.parent!==void 0){const parent=num2(change.parent);const at3=item.relations.findIndex(r=>r.rel===PARENT);if(item.relations[at3]?.id!==parent){if(at3>=0)ops.push({op:"remove",path:`/relations/${at3}`});ops.push(this.link(PARENT,parent))}}await this.writing(`change ${this.refOf(id2)}`,()=>this.az.updateWorkItem(n,ops))}async linkDepends(id2,on){const n=num2(id2);const item=await this.az.linksOf(n);const have=new Set(item.relations.filter(r=>r.rel===PREDECESSOR).map(r=>r.id));const missing=[...new Set(on.map(num2))].filter(d=>d!==n&&!have.has(d));await this.writing(`link ${this.refOf(id2)} to the items it needs`,()=>this.az.updateWorkItem(n,missing.map(d=>this.link(PREDECESSOR,d))))}async textOps(type,markdown,acceptance){const ops=[];const criteriaField=acceptance!==void 0&&await this.az.typeHasField(type,CRITERIA);if(criteriaField){if(markdown!==void 0)ops.push({op:"add",path:"/fields/System.Description",value:markdownToHtml(markdown)});ops.push({op:"add",path:`/fields/${CRITERIA}`,value:markdownToHtml(acceptance)});return ops}const text3=[markdown??"",acceptance?.trim()?`**Done when**
107
107
 
108
- ${acceptance.trim()}`:""].filter(Boolean).join("\n\n");ops.push({op:"add",path:"/fields/System.Description",value:markdownToHtml(text3)});return ops}link(rel,id2){return{op:"add",path:"/relations/-",value:{rel,url:this.az.itemApiUrl(id2)}}}async writing(what,run5){try{return await run5()}catch(err2){const e=err2;if(e?.kind==="sign-in"||/TF237111|permission/i.test(e?.message??"")){throw new AzureError(`Azure DevOps did not let squad-kit ${what} in project ${this.az.target.project}. The token needs the Work Items (Read & write) scope, and its owner needs the right to add work items there. Check the token in Settings, then try again.`,e.status,"sign-in")}throw err2}}};var azureTracker={name:"azure",label:"Azure DevOps",fields:[{key:"organization",label:"Organization"},{key:"project",label:"Project"},{key:"token",label:"Personal access token",secret:true}],open(root){const az=azureForProject(root);return az?new AzureTracker(az):null},read(root){const paths=buildPaths(root);const config2=loadConfig(paths.configFile);const values=config2.tracker.type==="azure"?{organization:config2.tracker.workspace??"",project:config2.tracker.project??""}:{};return{values,hasSecret:Boolean(loadSecrets(paths.secretsFile).tracker?.azure?.pat||process.env.AZURE_DEVOPS_PAT)}},save(root,values){const organization=values.organization?.trim().replace(/^https:\/\/dev\.azure\.com\//,"").replace(/\/.*$/,"");const project=values.project?.trim();if(!organization||!project)throw new Error("Enter the Azure organization and project.");const paths=buildPaths(root);const config2=loadConfig(paths.configFile);saveConfig(paths.configFile,{...config2,tracker:{...config2.tracker,type:"azure",workspace:organization,project}});const secrets=loadSecrets(paths.secretsFile);saveSecrets(paths.secretsFile,mergeSecrets(secrets,{tracker:{azure:{organization,project,pat:values.token?.trim()||void 0}}}))},guess(remoteUrl){const where=parseAzureRemote(remoteUrl);return where?{organization:where.organization,project:where.project}:null}};var AzureRepo=class{constructor(az,repo,remoteUrl){this.az=az;this.repo=repo;this.remoteUrl=remoteUrl}az;repo;remoteUrl;host="azure";label="Azure Repos";abilities={attachImages:true};gitEnv(){return gitAuthEnv(this.az.target.pat,this.remoteUrl)}findPullRequest(source){return this.az.findPullRequest(this.repo,source)}openPullRequest(input7){return this.az.createPullRequest({repo:this.repo,source:input7.source,target:input7.target,title:input7.title,description:input7.description,workItemIds:input7.items.map(Number).filter(n=>n>0)})}attachImage(prId,file3){return this.az.attachToPullRequest(this.repo,prId,file3)}updateDescription(prId,description){return this.az.updatePullRequestDescription(this.repo,prId,description)}async mergeState(prId){const pr=await this.az.getPullRequest(this.repo,prId);if(pr.status==="completed")return"merged";if(pr.mergeStatus==="conflicts")return"clash";return pr.mergeStatus==="succeeded"?"mergeable":"waiting"}async merge(prId,message){return{commit:(await this.az.completePullRequest(this.repo,prId,message)).mergeCommit}}close(prId){return this.az.abandonPullRequest(this.repo,prId)}refusedPush(message){return/TF402455|not permitted|protected|policy/i.test(message)}};var azureCodeHost={name:"azure",label:"Azure Repos",repoFor(root,remoteUrl){const where=parseAzureRemote(remoteUrl);const pat=where&&azureForProject(root)?.target.pat;if(!where||!pat)return null;return new AzureRepo(new Azure({organization:where.organization,project:where.project,pat}),where.repo,remoteUrl)}};import fs41 from"fs";import path33 from"path";var MUNSIT_URL="https://api.munsit.com/api/v1/audio/transcribe";var MUNSIT_MODEL="munsit-en-ar";function munsitKey(root){if(process.env.MUNSIT_API_KEY?.trim())return process.env.MUNSIT_API_KEY.trim();try{return loadSecrets(buildPaths(root).secretsFile).speech?.munsit?.trim()||void 0}catch{return void 0}}async function transcribe(audio,key,doFetch=fetch){const form=new FormData;form.append("file",new Blob([fs41.readFileSync(audio)]),path33.basename(audio));form.append("model",MUNSIT_MODEL);let res;try{res=await doFetch(MUNSIT_URL,{method:"POST",headers:{authorization:`Bearer ${key}`},body:form,signal:AbortSignal.timeout(5*6e4)})}catch{throw new ModuleError("Could not reach Munsit. Check the connection","offline",void 0)}if(res.status===401||res.status===403)throw new ModuleError("Munsit did not accept the key. Check it in Settings","sign-in",res.status);if(!res.ok)throw new ModuleError(`Munsit answered ${res.status}`,res.status===429?"too-many":"other",res.status);const answer2=await res.json();const words=answer2.data?.timestamps??[];const last=words[words.length-1];if(last&&last.start>30){const blocks2=new Map;for(const w of words){const at3=Math.floor(w.start/10)*10;blocks2.set(at3,[...blocks2.get(at3)??[],w.word])}return[...blocks2].map(([at3,ws])=>`${Math.floor(at3/60)}:${String(at3%60).padStart(2,"0")} ${ws.join(" ")}`).join("\n")}const text3=answer2.data?.transcription?.trim();if(!text3)throw new ModuleError("Munsit heard no words");return text3}async function checkKey(key,doFetch=fetch){const form=new FormData;form.append("file",new Blob([Buffer.from("not sound")]),"check.txt");form.append("model",MUNSIT_MODEL);try{const res=await doFetch(MUNSIT_URL,{method:"POST",headers:{authorization:`Bearer ${key}`},body:form,signal:AbortSignal.timeout(15e3)});return res.status===401||res.status===403?"wrong":"ok"}catch{return"no-answer"}}var munsitSpeech={name:"munsit",label:"Munsit",open(root,opts={}){const key=opts.key??munsitKey(root);if(!key)return null;const doFetch=opts.fetch??fetch;return{name:"munsit",label:"Munsit",transcribe:audio=>transcribe(audio,key,doFetch)}},hasKey:root=>Boolean(munsitKey(root)),checkKey,saveKey(root,key){const file3=buildPaths(root).secretsFile;saveSecrets(file3,mergeSecrets(loadSecrets(file3),{speech:{munsit:key}}))},removeKey(root){const file3=buildPaths(root).secretsFile;const secrets=loadSecrets(file3);delete secrets.speech;saveSecrets(file3,secrets)}};import fs46 from"fs";import path38 from"path";import{createRequire as createRequire2}from"module";import{spawn as spawn4}from"child_process";import fs42 from"fs";import path34 from"path";var SPACE=new Set(["ENOSPC","EDQUOT"]);var FILE_CODES=new Set([...SPACE,"EROFS","EIO","ENOENT","ENOTDIR","EACCES","EPERM"]);function fileTroubleCode(err2){const code=err2?.code;return typeof code==="string"&&FILE_CODES.has(code)?code:null}function isFileTrouble(err2){return fileTroubleCode(err2)!==null}function isDiskTrouble(err2){const code=fileTroubleCode(err2);return code!==null&&(SPACE.has(code)||code==="EROFS")}function fileTroubleWords(err2){const code=fileTroubleCode(err2);if(code&&SPACE.has(code))return"The disk is full, so squad-kit cannot save its work. Free up some space. Nothing is lost, and squad-kit carries on by itself.";if(code==="EROFS")return"The disk cannot be written to right now, so squad-kit cannot save its work. Nothing is lost, and squad-kit carries on by itself once it can.";if(code==="EIO")return"The disk did not answer, so squad-kit cannot save its work. Check the disk. squad-kit carries on by itself once it can.";if(code==="EACCES"||code==="EPERM")return"squad-kit is not allowed to write in the project's folder, so it cannot save its work. Check the folder's permissions. It carries on by itself once it can.";return"The project's folder is not where it was, so squad-kit cannot save its work. Put the folder back, or add it again from Home. The work so far is kept."}var COULD_NOT_SAVE="squad-kit could not save what the agent was doing, so it stopped the run. The work so far is kept.";function asError(err2){return err2 instanceof Error?err2:new Error(String(err2))}var count=n=>typeof n==="number"&&Number.isFinite(n)&&n>=0;function usageFrom(tokens,cost,before){const u={};if(tokens&&count(tokens.inputTokens)){const read5=count(tokens.cachedReadTokens)?tokens.cachedReadTokens:0;const write2=count(tokens.cachedWriteTokens)?tokens.cachedWriteTokens:0;u.inputTokens=tokens.inputTokens+read5+write2;if(count(tokens.cachedReadTokens))u.cachedTokens=read5}if(tokens&&count(tokens.outputTokens))u.outputTokens=tokens.outputTokens;if(count(cost)&&count(before))u.costUsd=cost>=before?cost-before:cost;return Object.keys(u).length?u:void 0}function windowServer(w,http2){if(http2)return{type:"http",name:w.name,url:w.url,headers:[{name:"Authorization",value:`Bearer ${w.key}`}]};return{name:w.name,command:w.bridge.command,args:w.bridge.args,env:[{name:"SQUAD_WINDOW_URL",value:w.url},{name:"SQUAD_WINDOW_KEY",value:w.key}]}}var AcpStartError=class extends Error{};var TookTooLong=class extends Error{};function deadline(work,ms2,message){let timer;const late2=new Promise((_,reject)=>timer=setTimeout(()=>reject(new TookTooLong(message)),ms2));return Promise.race([work,late2]).finally(()=>clearTimeout(timer))}var STOPPED="You stopped the squad.";var RUN_MS=20*6e4;function answerPermission(params){const options=params.options??[];const no=options.find(o=>o.kind==="reject_once")??options.find(o=>o.kind.startsWith("reject"));return no?{outcome:{outcome:"selected",optionId:no.optionId}}:{outcome:{outcome:"cancelled"}}}function outputOf(update){const parts2=[];for(const c of Array.isArray(update.content)?update.content:[]){if(c?.type==="content"&&c.content?.type==="text"&&typeof c.content.text==="string")parts2.push(c.content.text)}const strings2=(v,depth=0)=>{if(typeof v==="string")parts2.push(v);else if(v&&typeof v==="object"&&depth<4)for(const x of Object.values(v))strings2(x,depth+1)};strings2(update.rawOutput);return parts2.join("\n")}var Rpc=class{constructor(child,onNotify,onRequest){this.child=child;this.onNotify=onNotify;this.onRequest=onRequest;child.stdout.on("data",chunk=>this.read(chunk.toString("utf8")));const gone=err2=>{for(const p of this.pending.values())p.reject(new Error(err2?`The ACP adapter could not run: ${err2.message}`:"The ACP adapter stopped."));this.pending.clear()};child.on("exit",()=>gone());child.on("error",err2=>gone(err2))}child;onNotify;onRequest;nextId=1;pending=new Map;buf="";write(msg){if(this.child.stdin.writable)this.child.stdin.write(`${JSON.stringify({jsonrpc:"2.0",...msg})}
108
+ ${acceptance.trim()}`:""].filter(Boolean).join("\n\n");ops.push({op:"add",path:"/fields/System.Description",value:markdownToHtml(text3)});return ops}link(rel,id2){return{op:"add",path:"/relations/-",value:{rel,url:this.az.itemApiUrl(id2)}}}async writing(what,run5){try{return await run5()}catch(err2){const e=err2;if(e?.kind==="sign-in"||/TF237111|permission/i.test(e?.message??"")){throw new AzureError(`Azure DevOps did not let squad-kit ${what} in project ${this.az.target.project}. The token needs the Work Items (Read & write) scope, and its owner needs the right to add work items there. Check the token in Settings, then try again.`,e.status,"sign-in")}throw err2}}};var azureTracker={name:"azure",label:"Azure DevOps",fields:[{key:"organization",label:"Organization"},{key:"project",label:"Project"},{key:"token",label:"Personal access token",secret:true}],open(root){const az=azureForProject(root);return az?new AzureTracker(az):null},read(root){const paths=buildPaths(root);const config2=loadConfig(paths.configFile);const values=config2.tracker.type==="azure"?{organization:config2.tracker.workspace??"",project:config2.tracker.project??""}:{};return{values,hasSecret:Boolean(loadSecrets(paths.secretsFile).tracker?.azure?.pat||process.env.AZURE_DEVOPS_PAT)}},save(root,values){const organization=values.organization?.trim().replace(/^https:\/\/dev\.azure\.com\//,"").replace(/\/.*$/,"");const project=values.project?.trim();if(!organization||!project)throw new Error("Enter the Azure organization and project.");const paths=buildPaths(root);const config2=loadConfig(paths.configFile);saveConfig(paths.configFile,{...config2,tracker:{...config2.tracker,type:"azure",workspace:organization,project}});const secrets=loadSecrets(paths.secretsFile);saveSecrets(paths.secretsFile,mergeSecrets(secrets,{tracker:{azure:{organization,project,pat:values.token?.trim()||void 0}}}))},guess(remoteUrl){const where=parseAzureRemote(remoteUrl);return where?{organization:where.organization,project:where.project}:null}};var AzureRepo=class{constructor(az,repo,remoteUrl){this.az=az;this.repo=repo;this.remoteUrl=remoteUrl}az;repo;remoteUrl;host="azure";label="Azure Repos";abilities={attachImages:true};gitEnv(){return gitAuthEnv(this.az.target.pat,this.remoteUrl)}findPullRequest(source){return this.az.findPullRequest(this.repo,source)}openPullRequest(input7){return this.az.createPullRequest({repo:this.repo,source:input7.source,target:input7.target,title:input7.title,description:input7.description,workItemIds:input7.items.map(Number).filter(n=>n>0)})}attachImage(prId,file3){return this.az.attachToPullRequest(this.repo,prId,file3)}updateDescription(prId,description){return this.az.updatePullRequestDescription(this.repo,prId,description)}async mergeState(prId){const pr=await this.az.getPullRequest(this.repo,prId);if(pr.status==="completed")return"merged";if(pr.mergeStatus==="conflicts")return"clash";return pr.mergeStatus==="succeeded"?"mergeable":"waiting"}async merge(prId,message){return{commit:(await this.az.completePullRequest(this.repo,prId,message)).mergeCommit}}close(prId){return this.az.abandonPullRequest(this.repo,prId)}refusedPush(message){return/TF402455|not permitted|protected|policy/i.test(message)}};var azureCodeHost={name:"azure",label:"Azure Repos",repoFor(root,remoteUrl){const where=parseAzureRemote(remoteUrl);const pat=where&&azureForProject(root)?.target.pat;if(!where||!pat)return null;return new AzureRepo(new Azure({organization:where.organization,project:where.project,pat}),where.repo,remoteUrl)}};import fs41 from"fs";import path33 from"path";var MUNSIT_URL="https://api.munsit.com/api/v1/audio/transcribe";var MUNSIT_MODEL="munsit-en-ar";function munsitKey(root){if(process.env.MUNSIT_API_KEY?.trim())return process.env.MUNSIT_API_KEY.trim();try{return loadSecrets(buildPaths(root).secretsFile).speech?.munsit?.trim()||void 0}catch{return void 0}}async function transcribe(audio,key,doFetch=fetch){const form=new FormData;form.append("file",new Blob([fs41.readFileSync(audio)]),path33.basename(audio));form.append("model",MUNSIT_MODEL);let res;try{res=await doFetch(MUNSIT_URL,{method:"POST",headers:{authorization:`Bearer ${key}`},body:form,signal:AbortSignal.timeout(5*6e4)})}catch{throw new ModuleError("Could not reach Munsit. Check the connection","offline",void 0)}if(res.status===401||res.status===403)throw new ModuleError("Munsit did not accept the key. Check it in Settings","sign-in",res.status);if(!res.ok)throw new ModuleError(`Munsit answered ${res.status}`,res.status===429?"too-many":"other",res.status);const answer2=await res.json();const words=answer2.data?.timestamps??[];const last=words[words.length-1];if(last&&last.start>30){const blocks2=new Map;for(const w of words){const at3=Math.floor(w.start/10)*10;blocks2.set(at3,[...blocks2.get(at3)??[],w.word])}return[...blocks2].map(([at3,ws])=>`${Math.floor(at3/60)}:${String(at3%60).padStart(2,"0")} ${ws.join(" ")}`).join("\n")}const text3=answer2.data?.transcription?.trim();if(!text3)throw new ModuleError("Munsit heard no words");return text3}async function checkKey(key,doFetch=fetch){const form=new FormData;form.append("file",new Blob([Buffer.from("not sound")]),"check.txt");form.append("model",MUNSIT_MODEL);try{const res=await doFetch(MUNSIT_URL,{method:"POST",headers:{authorization:`Bearer ${key}`},body:form,signal:AbortSignal.timeout(15e3)});return res.status===401||res.status===403?"wrong":"ok"}catch{return"no-answer"}}var munsitSpeech={name:"munsit",label:"Munsit",open(root,opts={}){const key=opts.key??munsitKey(root);if(!key)return null;const doFetch=opts.fetch??fetch;return{name:"munsit",label:"Munsit",transcribe:audio=>transcribe(audio,key,doFetch)}},hasKey:root=>Boolean(munsitKey(root)),checkKey,saveKey(root,key){const file3=buildPaths(root).secretsFile;saveSecrets(file3,mergeSecrets(loadSecrets(file3),{speech:{munsit:key}}))},removeKey(root){const file3=buildPaths(root).secretsFile;const secrets=loadSecrets(file3);delete secrets.speech;saveSecrets(file3,secrets)}};import fs46 from"fs";import path38 from"path";import{createRequire as createRequire2}from"module";import{spawn as spawn4}from"child_process";import fs42 from"fs";import path34 from"path";var SPACE=new Set(["ENOSPC","EDQUOT"]);var FILE_CODES=new Set([...SPACE,"EROFS","EIO","ENOENT","ENOTDIR","EACCES","EPERM"]);function fileTroubleCode(err2){const code=err2?.code;return typeof code==="string"&&FILE_CODES.has(code)?code:null}function isFileTrouble(err2){return fileTroubleCode(err2)!==null}function isDiskTrouble(err2){const code=fileTroubleCode(err2);return code!==null&&(SPACE.has(code)||code==="EROFS")}function fileTroubleWords(err2){const code=fileTroubleCode(err2);if(code&&SPACE.has(code))return"The disk is full, so squad-kit cannot save its work. Free up some space. Nothing is lost, and squad-kit carries on by itself.";if(code==="EROFS")return"The disk cannot be written to right now, so squad-kit cannot save its work. Nothing is lost, and squad-kit carries on by itself once it can.";if(code==="EIO")return"The disk did not answer, so squad-kit cannot save its work. Check the disk. squad-kit carries on by itself once it can.";if(code==="EACCES"||code==="EPERM")return"squad-kit is not allowed to write in the project's folder, so it cannot save its work. Check the folder's permissions. It carries on by itself once it can.";return"The project's folder is not where it was, so squad-kit cannot save its work. Put the folder back, or add it again from Home. The work so far is kept."}var COULD_NOT_SAVE="squad-kit could not save what the agent was doing, so it stopped the run. The work so far is kept.";function asError(err2){return err2 instanceof Error?err2:new Error(String(err2))}var count=n=>typeof n==="number"&&Number.isFinite(n)&&n>=0;function planOf(entries){return entries.map(e=>{const x=e??{};return{content:typeof x.content==="string"?x.content:"",status:x.status==="in_progress"||x.status==="completed"?x.status:"pending"}})}function usageFrom(tokens,cost,before){const u={};if(tokens&&count(tokens.inputTokens)){const read5=count(tokens.cachedReadTokens)?tokens.cachedReadTokens:0;const write2=count(tokens.cachedWriteTokens)?tokens.cachedWriteTokens:0;u.inputTokens=tokens.inputTokens+read5+write2;if(count(tokens.cachedReadTokens))u.cachedTokens=read5}if(tokens&&count(tokens.outputTokens))u.outputTokens=tokens.outputTokens;if(count(cost)&&count(before))u.costUsd=cost>=before?cost-before:cost;return Object.keys(u).length?u:void 0}function windowServer(w,http2){if(http2)return{type:"http",name:w.name,url:w.url,headers:[{name:"Authorization",value:`Bearer ${w.key}`}]};return{name:w.name,command:w.bridge.command,args:w.bridge.args,env:[{name:"SQUAD_WINDOW_URL",value:w.url},{name:"SQUAD_WINDOW_KEY",value:w.key}]}}var AcpStartError=class extends Error{};var TookTooLong=class extends Error{};function deadline(work,ms2,message){let timer;const late2=new Promise((_,reject)=>timer=setTimeout(()=>reject(new TookTooLong(message)),ms2));return Promise.race([work,late2]).finally(()=>clearTimeout(timer))}var STOPPED="You stopped the squad.";var RUN_MS=20*6e4;function answerPermission(params){const options=params.options??[];const no=options.find(o=>o.kind==="reject_once")??options.find(o=>o.kind.startsWith("reject"));return no?{outcome:{outcome:"selected",optionId:no.optionId}}:{outcome:{outcome:"cancelled"}}}function outputOf(update){const parts2=[];for(const c of Array.isArray(update.content)?update.content:[]){if(c?.type==="content"&&c.content?.type==="text"&&typeof c.content.text==="string")parts2.push(c.content.text)}const strings2=(v,depth=0)=>{if(typeof v==="string")parts2.push(v);else if(v&&typeof v==="object"&&depth<4)for(const x of Object.values(v))strings2(x,depth+1)};strings2(update.rawOutput);return parts2.join("\n")}var Rpc=class{constructor(child,onNotify,onRequest){this.child=child;this.onNotify=onNotify;this.onRequest=onRequest;child.stdout.on("data",chunk=>this.read(chunk.toString("utf8")));const gone=err2=>{for(const p of this.pending.values())p.reject(new Error(err2?`The ACP adapter could not run: ${err2.message}`:"The ACP adapter stopped."));this.pending.clear()};child.on("exit",()=>gone());child.on("error",err2=>gone(err2))}child;onNotify;onRequest;nextId=1;pending=new Map;buf="";write(msg){if(this.child.stdin.writable)this.child.stdin.write(`${JSON.stringify({jsonrpc:"2.0",...msg})}
109
109
  `)}request(method,params){const id2=this.nextId++;return new Promise((resolve2,reject)=>{this.pending.set(id2,{resolve:resolve2,reject});this.write({id:id2,method,params})})}notify(method,params){this.write({method,params})}read(text3){this.buf+=text3;let i;while((i=this.buf.indexOf("\n"))>=0){const line3=this.buf.slice(0,i).trim();this.buf=this.buf.slice(i+1);if(!line3)continue;let msg;try{msg=JSON.parse(line3)}catch{continue}if(msg.id!==void 0&&!msg.method&&this.pending.has(msg.id)){const p=this.pending.get(msg.id);this.pending.delete(msg.id);if(msg.error)p.reject(new Error(String(msg.error.message??JSON.stringify(msg.error))));else p.resolve(msg.result)}else if(msg.method&&msg.id!==void 0){Promise.resolve().then(()=>this.onRequest(msg.method,msg.params??{})).then(result=>this.write({id:msg.id,result})).catch(err2=>this.write({id:msg.id,error:{code:-32601,message:err2.message}}))}else if(msg.method){this.onNotify(msg.method,msg.params??{})}}}};async function runAcp(launch2,opts){if(opts.signal?.aborted)return{ok:false,sessionId:opts.resume??opts.sessionId??"",text:"",error:STOPPED};if(opts.logDir)fs42.mkdirSync(opts.logDir,{recursive:true});const child=track(spawn4(launch2.command,launch2.args,{cwd:opts.cwd,env:launch2.env,stdio:["pipe","pipe","pipe"],detached:OWN_GROUP}));child.on("error",()=>void 0);child.stdin.on("error",()=>void 0);if(child.pid)opts.onSpawn?.(child.pid);let stderr="";child.stderr.on("data",d=>stderr=(stderr+d.toString("utf8")).slice(-4e3));const tools=new Map;let lastText="";let newMessage=true;let structured;let sessionId=opts.resume??opts.sessionId??"";let cost;let costBefore=0;let tokens;let saveError;const used=()=>{const u=usageFrom(tokens,cost,costBefore);return{...u?{usage:u}:{},...saveError?{saveError}:{}}};const rpc=new Rpc(child,(method,params)=>{if(method!=="session/update"||saveError)return;try{onUpdate(params)}catch(err2){saveError=asError(err2);stop(COULD_NOT_SAVE)}},(method,params)=>{if(method==="session/request_permission")return answerPermission(params);throw new Error(`squad-kit does not offer ${method}`)});function onUpdate(params){const update=params.update;opts.onActivity?.();if(opts.logDir&&sessionId)fs42.appendFileSync(path34.join(opts.logDir,`${sessionId}.jsonl`),`${JSON.stringify({at:new Date().toISOString(),update})}
110
- `);if(update.sessionUpdate==="usage_update"&&update.cost?.currency==="USD"&&count(update.cost.amount))cost=update.cost.amount;if(update.sessionUpdate==="agent_message_chunk"&&update.content?.type==="text"){lastText=newMessage?update.content.text:lastText+update.content.text;newMessage=false;opts.onText?.(update.content.text)}if(update.sessionUpdate==="tool_call")newMessage=true;if(update.sessionUpdate==="tool_call"||update.sessionUpdate==="tool_call_update"){const t=tools.get(update.toolCallId)??{};const name2=launch2.toolName(update)??(update.sessionUpdate==="tool_call"?void 0:t.name);if(name2)t.name=name2;if(update.rawInput&&Object.keys(update.rawInput).length)t.input=update.rawInput;if(launch2.structuredTool&&t.name===launch2.structuredTool&&t.input)structured=t.input;if(!t.reported&&t.name&&t.input&&t.name!==launch2.structuredTool){t.reported=true;opts.onTool?.({name:t.name,input:t.input})}if(update.sessionUpdate==="tool_call_update"&&launch2.blocked&&opts.onBlocked){for(const b of launch2.blocked(outputOf(update)))opts.onBlocked(b)}tools.set(update.toolCallId,t)}}let stoppedBy=null;const stop=reason=>{if(stoppedBy)return;stoppedBy=reason;try{if(sessionId)rpc.notify("session/cancel",{sessionId})}catch{}setTimeout(()=>endGroup(child),1500)};const onStop=()=>stop(STOPPED);opts.signal?.addEventListener("abort",onStop,{once:true});const timer=setTimeout(()=>stop(`It took longer than ${Math.round((opts.timeoutMs??RUN_MS)/6e4)} minutes, so squad-kit stopped it.`),opts.timeoutMs??RUN_MS);const exited=new Promise(resolve2=>{child.on("exit",()=>resolve2());child.on("error",()=>resolve2())});const meta=launch2.meta;const name=launch2.label??"The AI tool";const initializeMs=opts.startLimits?.initializeMs??3e4;const sessionMs=opts.startLimits?.sessionMs??6e4;const late2=(ms2,what)=>{const s=Math.max(1,Math.round(ms2/1e3));return`${name} did not ${what} in ${s} second${s===1?"":"s"}. Check that it opens in a terminal and is logged in, then press Try again.`};try{let mcpHttp=false;try{const init=await deadline(rpc.request("initialize",{protocolVersion:1,clientCapabilities:{fs:{readTextFile:false,writeTextFile:false},terminal:false},clientInfo:{name:"squad-kit",version:"1"}}),initializeMs,late2(initializeMs,"start"));mcpHttp=init?.agentCapabilities?.mcpCapabilities?.http===true}catch(err2){if(stoppedBy)throw err2;const why=err2 instanceof TookTooLong?err2.message:`The ACP adapter did not start: ${err2.message}`;throw new AcpStartError(`${why} ${stderr.slice(-300)}`.trim())}const mcpServers=opts.window?[windowServer(opts.window,mcpHttp)]:[];const existing=sessionId&&launch2.canResume(sessionId);if(existing){costBefore=launch2.costSoFar?.(sessionId);await deadline(rpc.request("session/resume",{sessionId,cwd:opts.cwd,mcpServers,_meta:meta}),sessionMs,late2(sessionMs,"open its session"))}else{const s=await deadline(rpc.request("session/new",{cwd:opts.cwd,mcpServers,_meta:meta}),sessionMs,late2(sessionMs,"open a session"));sessionId=s.sessionId}if(launch2.mode)await rpc.request("session/set_mode",{sessionId,modeId:launch2.mode});const res=await rpc.request("session/prompt",{sessionId,prompt:[{type:"text",text:opts.prompt}]});tokens=res.usage??void 0;if(stoppedBy)return{ok:false,sessionId,text:lastText,error:stoppedBy,...used()};if(res.stopReason==="max_turn_requests")return{ok:false,sessionId,text:lastText,error:"The step reached the most it may do in one run.",capped:{by:"turns"},...used()};if(res.stopReason!=="end_turn"){const why=res.stopReason==="max_tokens"?"The answer was cut off because it got too long.":res.stopReason==="refusal"?"The agent declined to do this step.":res.stopReason==="cancelled"?"The step was cancelled.":`The agent stopped early (${res.stopReason}).`;return{ok:false,sessionId,text:lastText,error:why,...used()}}if(opts.expectStructured&&structured===void 0)return{ok:false,sessionId,text:lastText,error:"The agent finished without giving squad-kit its answer.",...used()};return{ok:true,sessionId,text:lastText,...structured!==void 0?{structured}:{},...used()}}catch(err2){if(err2 instanceof AcpStartError)throw err2;const why=err2 instanceof TookTooLong?err2.message:launch2.explain(`${err2.message}
110
+ `);if(update.sessionUpdate==="usage_update"&&update.cost?.currency==="USD"&&count(update.cost.amount))cost=update.cost.amount;if(update.sessionUpdate==="agent_message_chunk"&&update.content?.type==="text"){lastText=newMessage?update.content.text:lastText+update.content.text;newMessage=false;opts.onText?.(update.content.text)}if(update.sessionUpdate==="plan"&&Array.isArray(update.entries))opts.onPlan?.(planOf(update.entries));if(update.sessionUpdate==="tool_call")newMessage=true;if(update.sessionUpdate==="tool_call"||update.sessionUpdate==="tool_call_update"){const t=tools.get(update.toolCallId)??{};const name2=launch2.toolName(update)??(update.sessionUpdate==="tool_call"?void 0:t.name);if(name2)t.name=name2;if(update.rawInput&&Object.keys(update.rawInput).length)t.input=update.rawInput;if(launch2.structuredTool&&t.name===launch2.structuredTool&&t.input)structured=t.input;if(!t.reported&&t.name&&t.input&&t.name!==launch2.structuredTool){t.reported=true;opts.onTool?.({name:t.name,input:t.input})}if(update.sessionUpdate==="tool_call_update"&&launch2.blocked&&opts.onBlocked){for(const b of launch2.blocked(outputOf(update)))opts.onBlocked(b)}tools.set(update.toolCallId,t)}}let stoppedBy=null;const stop=reason=>{if(stoppedBy)return;stoppedBy=reason;try{if(sessionId)rpc.notify("session/cancel",{sessionId})}catch{}setTimeout(()=>endGroup(child),1500)};const onStop=()=>stop(STOPPED);opts.signal?.addEventListener("abort",onStop,{once:true});const timer=setTimeout(()=>stop(`It took longer than ${Math.round((opts.timeoutMs??RUN_MS)/6e4)} minutes, so squad-kit stopped it.`),opts.timeoutMs??RUN_MS);const exited=new Promise(resolve2=>{child.on("exit",()=>resolve2());child.on("error",()=>resolve2())});const meta=launch2.meta;const name=launch2.label??"The AI tool";const initializeMs=opts.startLimits?.initializeMs??3e4;const sessionMs=opts.startLimits?.sessionMs??6e4;const late2=(ms2,what)=>{const s=Math.max(1,Math.round(ms2/1e3));return`${name} did not ${what} in ${s} second${s===1?"":"s"}. Check that it opens in a terminal and is logged in, then press Try again.`};try{let mcpHttp=false;try{const init=await deadline(rpc.request("initialize",{protocolVersion:1,clientCapabilities:{fs:{readTextFile:false,writeTextFile:false},terminal:false},clientInfo:{name:"squad-kit",version:"1"}}),initializeMs,late2(initializeMs,"start"));mcpHttp=init?.agentCapabilities?.mcpCapabilities?.http===true}catch(err2){if(stoppedBy)throw err2;const why=err2 instanceof TookTooLong?err2.message:`The ACP adapter did not start: ${err2.message}`;throw new AcpStartError(`${why} ${stderr.slice(-300)}`.trim())}const mcpServers=opts.window?[windowServer(opts.window,mcpHttp)]:[];const existing=sessionId&&launch2.canResume(sessionId);if(existing){costBefore=launch2.costSoFar?.(sessionId);await deadline(rpc.request("session/resume",{sessionId,cwd:opts.cwd,mcpServers,_meta:meta}),sessionMs,late2(sessionMs,"open its session"))}else{const s=await deadline(rpc.request("session/new",{cwd:opts.cwd,mcpServers,_meta:meta}),sessionMs,late2(sessionMs,"open a session"));sessionId=s.sessionId}if(launch2.mode)await rpc.request("session/set_mode",{sessionId,modeId:launch2.mode});const res=await rpc.request("session/prompt",{sessionId,prompt:[{type:"text",text:opts.prompt}]});tokens=res.usage??void 0;if(stoppedBy)return{ok:false,sessionId,text:lastText,error:stoppedBy,...used()};if(res.stopReason==="max_turn_requests")return{ok:false,sessionId,text:lastText,error:"The step reached the most it may do in one run.",capped:{by:"turns"},...used()};if(res.stopReason!=="end_turn"){const why=res.stopReason==="max_tokens"?"The answer was cut off because it got too long.":res.stopReason==="refusal"?"The agent declined to do this step.":res.stopReason==="cancelled"?"The step was cancelled.":`The agent stopped early (${res.stopReason}).`;return{ok:false,sessionId,text:lastText,error:why,...used()}}if(opts.expectStructured&&structured===void 0)return{ok:false,sessionId,text:lastText,error:"The agent finished without giving squad-kit its answer.",...used()};return{ok:true,sessionId,text:lastText,...structured!==void 0?{structured}:{},...used()}}catch(err2){if(err2 instanceof AcpStartError)throw err2;const why=err2 instanceof TookTooLong?err2.message:launch2.explain(`${err2.message}
111
111
  ${stderr}`);const detail=`${err2.message}
112
112
  ${stderr.slice(-1500)}`.trim();return{ok:false,sessionId,text:lastText,error:stoppedBy??why,...stoppedBy?{}:{detail},...used()}}finally{clearTimeout(timer);opts.signal?.removeEventListener("abort",onStop);child.stdin.end();await Promise.race([exited,new Promise(r=>setTimeout(r,3e3))]);endGroup(child);child.stdout.destroy();child.stderr.destroy()}}import{spawn as spawn5,execFileSync as execFileSync5}from"child_process";import fs45 from"fs";import os5 from"os";import{randomUUID as randomUUID2}from"crypto";import path37 from"path";function withIpv4First(env){const current=env.NODE_OPTIONS??"";const flags=["--dns-result-order=ipv4first","--no-network-family-autoselection"].filter(f=>!current.includes(f.replace(/^--(no-)?/,"").split("=")[0]));return flags.length?{...env,NODE_OPTIONS:`${current} ${flags.join(" ")}`.trim()}:env}var ENGINE_ONLY_ENV_KEYS=["AZURE_DEVOPS_PAT","SQUAD_TRACKER_API_KEY","JIRA_API_TOKEN","MUNSIT_API_KEY"];function childEnv(){const env=withIpv4First({...process.env});for(const key of Object.keys(env)){if(SUBSCRIPTION_WITHHELD_ENV_KEYS.includes(key.toUpperCase())||ENGINE_ONLY_ENV_KEYS.includes(key.toUpperCase()))delete env[key]}return env}import fs43 from"fs";import path35 from"path";var RETRY_MS=10*6e4;var AFTER_RESET_MS=3e4;var LONGEST_MS=8*864e5;function pauseFile(){return path35.join(squadHome(),"usage-limit.json")}function readPause(){try{const p=JSON.parse(fs43.readFileSync(pauseFile(),"utf8"));return p&&typeof p.until==="string"&&Number.isFinite(Date.parse(p.until))?p:null}catch{return null}}function activePause(now2=Date.now()){const p=readPause();return p&&Date.parse(p.until)>now2?p:null}function startPause(limit,where={},now2=Date.now()){const reset=limit.resetsAt?Date.parse(limit.resetsAt):NaN;const known=Number.isFinite(reset)&&reset>now2-6e4&&reset-now2<LONGEST_MS;const until=known?Math.max(reset,now2)+AFTER_RESET_MS:now2+RETRY_MS;const before=activePause(now2);const keep=before&&before.name===limit.name&&Date.parse(before.until)>=until;const pause2=keep?before:{name:limit.name,since:before?.since??new Date(now2).toISOString(),...known?{resetsAt:new Date(reset).toISOString()}:{},until:new Date(until).toISOString(),...where.project?{project:where.project}:{},...where.ticket?{ticket:where.ticket}:{}};if(!keep){fs43.mkdirSync(squadHome(),{recursive:true});fs43.writeFileSync(pauseFile(),`${JSON.stringify(pause2,null,2)}
113
113
  `,"utf8")}return pause2}function endPause(now2=Date.now()){const was=activePause(now2)!==null;fs43.rmSync(pauseFile(),{force:true});return was}function resetWords(iso2,now2=Date.now()){const d=new Date(iso2);const time2=d.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit"}).replace(/\s/g," ");const day=x=>new Date(x.getFullYear(),x.getMonth(),x.getDate()).getTime();const days=Math.round((day(d)-day(new Date(now2)))/864e5);if(days<=0)return`at ${time2}`;if(days===1)return`tomorrow at ${time2}`;if(days<7)return`on ${d.toLocaleDateString("en-US",{weekday:"long"})} at ${time2}`;return`on ${d.toLocaleDateString("en-US",{month:"short",day:"numeric"})} at ${time2}`}function limitText(p,now2=Date.now()){const when=p.resetsAt?`${resetWords(p.resetsAt,now2)} by itself`:"by itself when the limit resets";return`${p.name}'s usage limit is reached. The squad carries on ${when}`}function isLimitText(status){return Boolean(status&&/ usage limit is reached\. The squad carries on /.test(status))}function chatWaitText(p,hasMessage,now2=Date.now()){const when=p.resetsAt?resetWords(p.resetsAt,now2):"once the limit resets";return hasMessage?`${p.name}'s usage limit is reached. Your message waits and gets its answer ${when}`:`${p.name}'s usage limit is reached. The chat waits and the answer comes ${when}`}function chatWaitNote(c,p,now2=Date.now()){const last=c.messages.at(-1);const hasMessage=[...c.messages].reverse().find(m=>m.note!=="waits")?.who==="you";const text3=chatWaitText(p,hasMessage,now2);if(last?.note==="waits"&&last.text===text3)return null;return{message:{who:"squad",text:text3,at:new Date(now2).toISOString(),note:"waits"},replace:last?.note==="waits"}}var LimitReached=class extends Error{constructor(pause2){super(pause2.resetsAt?`${pause2.name}'s usage limit is reached. It resets ${resetWords(pause2.resetsAt)}.`:`${pause2.name}'s usage limit is reached. Try again once it resets.`);this.pause=pause2;this.name="LimitReached"}pause};var LIMIT=[/\byou've (?:hit|reached) your\b/i,/\byou're out of (?:extra )?usage\b/i,/\byour (?:org|organization|seat|group)\b.*\busage\b/i,/\busage allocation has been disabled\b/i,/\b(?:usage|spend|session|weekly|5-hour|daily|monthly)(?: credit)? limit (?:reached|exceeded|hit)\b/i,/\busage limit\b/i,/\brate[_ ]limit(?:ed|_error)?\b/i,/\b429\b.*\b(?:too many requests|rate)\b/i];var NOT_LIMIT=[/\bnot logged in\b/i,/\bmax(?:imum)? (?:output )?tokens\b/i,/\bcontext (?:window|length)\b/i];var MONTHS=["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"];function isClaudeLimit(raw){return LIMIT.some(r=>r.test(raw))&&!NOT_LIMIT.some(r=>r.test(raw))}function claudeLimit(raw,now2=Date.now()){if(!raw||!isClaudeLimit(raw))return null;const at3=resetTime(raw,now2);return at3===null?{}:{resetsAt:new Date(at3).toISOString()}}function resetTime(raw,now2=Date.now()){const epoch=/limit reached\|(\d{10,13})\b/i.exec(raw);if(epoch){const n=Number(epoch[1]);return n<1e12?n*1e3:n}const inside3=/\bresets? in\s+(?:(\d+)\s*h(?:ours?|rs?)?)?\s*(?:(\d+)\s*m(?:in(?:ute)?s?)?)?/i.exec(raw);if(inside3&&(inside3[1]||inside3[2]))return now2+(Number(inside3[1]??0)*60+Number(inside3[2]??0))*6e4;const iso2=/\breset(?:s)?(?: at| on)?\s+(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?(?:\.\d+)?\s*(Z|UTC|GMT|[+-]\d{2}:?\d{2})?/i.exec(raw);if(iso2){const[,y,mo,d,h,mi,s,zone2]=iso2;const wall=Date.UTC(Number(y),Number(mo)-1,Number(d),Number(h),Number(mi),Number(s??0));if(!zone2)return localTime(Number(y),Number(mo)-1,Number(d),Number(h),Number(mi));if(/^(z|utc|gmt)$/i.test(zone2))return wall;const sign=zone2.startsWith("-")?-1:1;const digits=zone2.replace(/[^\d]/g,"");return wall-sign*(Number(digits.slice(0,2))*60+Number(digits.slice(2,4)))*6e4}const clock3=/\breset(?:s)?(?:\s+at|\s+on)?\s+(?:([a-z]{3})[a-z]*\.?\s+(\d{1,2})(?:,?\s+(\d{4}))?,?\s+(?:at\s+)?)?(\d{1,2})(?::(\d{2}))?\s*(?:([ap])\.?\s?m\.?)?(?:\s*\(([^)]+)\))?/i.exec(raw);if(!clock3)return null;const[,mon,dayText2,yearText,hourText,minText,half,zoneText]=clock3;if(!half&&!minText)return null;let hour=Number(hourText);if(half)hour=hour%12+(half.toLowerCase()==="p"?12:0);const minute=Number(minText??0);if(hour>23||minute>59)return null;const zone=zoneText&&validZone(zoneText.trim())?zoneText.trim():void 0;const month=mon?MONTHS.indexOf(mon.toLowerCase()):-1;if(mon&&month<0)return null;const today=partsIn(now2,zone);if(month>=0){let year=yearText?Number(yearText):today.year;let at4=wallTime(year,month,Number(dayText2),hour,minute,zone);if(!yearText&&at4<now2-864e5)at4=wallTime(year+=1,month,Number(dayText2),hour,minute,zone);return at4}let at3=wallTime(today.year,today.month,today.day,hour,minute,zone);if(at3<=now2-6e4)at3=wallTime(today.year,today.month,today.day+1,hour,minute,zone);return at3}function validZone(zone){try{new Intl.DateTimeFormat("en-US",{timeZone:zone});return true}catch{return false}}function partsIn(ms2,zone){if(!zone){const d=new Date(ms2);return{year:d.getFullYear(),month:d.getMonth(),day:d.getDate(),hour:d.getHours(),minute:d.getMinutes()}}const parts2=new Intl.DateTimeFormat("en-US",{timeZone:zone,hourCycle:"h23",year:"numeric",month:"numeric",day:"numeric",hour:"numeric",minute:"numeric"}).formatToParts(new Date(ms2));const n=type=>Number(parts2.find(p=>p.type===type)?.value??0);return{year:n("year"),month:n("month")-1,day:n("day"),hour:n("hour")%24,minute:n("minute")}}function localTime(year,month,day,hour,minute){return new Date(year,month,day,hour,minute).getTime()}function wallTime(year,month,day,hour,minute,zone){if(!zone)return localTime(year,month,day,hour,minute);const wall=Date.UTC(year,month,day,hour,minute);const offset=ms2=>{const p=partsIn(ms2,zone);return Date.UTC(p.year,p.month,p.day,p.hour,p.minute)-Math.floor(ms2/6e4)*6e4};let at3=wall-offset(wall);const again2=offset(at3);if(again2!==wall-at3)at3=wall-again2;return at3}import{execFileSync as execFileSync4}from"child_process";import fs44 from"fs";import os4 from"os";import path36 from"path";function claudePrivate(home2){return[path36.join(home2,".claude",".credentials.json"),path36.join(home2,".claude","projects"),path36.join(home2,".claude.json")]}function claudePrivateFiles(home2,platform=process.platform){const desktop=platform==="darwin"?["Claude","Claude-3p"].map(d=>path36.join(home2,"Library","Application Support",d)):[path36.join(home2,".config","Claude")];return[...claudePrivate(home2),...desktop]}var CLAUDE_ENV=["ANTHROPIC_*","CLAUDE_*","CLAUDECODE"];function claudeSandbox(ask2,platform=process.platform,home2=os4.homedir()){return{enabled:true,failIfUnavailable:false,autoAllowBashIfSandboxed:false,allowUnsandboxedCommands:false,network:{allowedDomains:ask2.hosts,strictAllowlist:true,...platform==="darwin"?{allowLocalBinding:true}:{}},filesystem:{allowWrite:ask2.write,denyWrite:ask2.noWrite,denyRead:[...new Set([...ask2.noRead,...claudePrivate(home2)])],allowRead:ask2.read}}}function sandboxSupportOn(h){if(h.platform==="darwin"){return h.has("/usr/bin/sandbox-exec")?{available:true,why:"Claude Code's sandbox (macOS Seatbelt)"}:{available:false,why:"macOS sandbox-exec is missing on this Mac"}}if(h.platform==="win32")return{available:false,why:"Claude Code's sandbox does not run on Windows yet"};if(h.platform!=="linux")return{available:false,why:"Claude Code's sandbox does not run on this system"};if(/-Microsoft$/i.test(h.release))return{available:false,why:"WSL 1 has no sandbox",fix:"Move this Linux to WSL 2: wsl --set-version <name> 2"};const missing=["bubblewrap","socat"].filter(p=>!h.has(p==="bubblewrap"?"bwrap":p));if(missing.length){return{available:false,why:`${missing.join(" and ")} ${missing.length===1?"is":"are"} not installed`,fix:`Install ${missing.length===1?"it":"them"}, for example: sudo apt install ${missing.join(" ")}`}}if(!h.bwrapWorks()){return{available:false,why:"bubblewrap cannot make a sandbox on this Linux",fix:"Allow unprivileged user namespaces for bwrap (on Ubuntu 24.04 this needs an AppArmor profile for it)"}}return{available:true,why:"Claude Code's sandbox (bubblewrap)"}}function onPath(program2){if(path36.isAbsolute(program2))return fs44.existsSync(program2);try{execFileSync4(process.platform==="win32"?"where":"which",[program2],{stdio:"ignore",timeout:5e3});return true}catch{return false}}function bwrapWorks(){try{execFileSync4("bwrap",["--ro-bind","/","/","--dev","/dev","--unshare-net","true"],{stdio:"ignore",timeout:5e3});return true}catch{return false}}var support=null;function claudeSandboxSupport(){return support??=sandboxSupportOn({platform:process.platform,release:os4.release(),has:onPath,bwrapWorks})}var VIOLATIONS=/<sandbox_violations>([\s\S]*?)<\/sandbox_violations>/g;var HOST=/network-outbound\s+(\[[^\]\s]+\]|[^\s:()]+)/;var FILE=/deny(?:\(\d+\))?\s+file-(read|write)[\w-]*\s+(\/.*?)\s*$/;function claudeBlocked(output){const out17=[];const seen=new Set;for(const block3 of output.matchAll(VIOLATIONS)){for(const line3 of block3[1].split("\n")){const host=HOST.exec(line3);const file3=host?null:FILE.exec(line3);const action=host?{kind:"host",target:host[1].replace(/^\[|\]$/g,"")}:file3?{kind:file3[1]==="read"?"read":"write",target:file3[2]}:null;if(!action||seen.has(`${action.kind}:${action.target}`))continue;seen.add(`${action.kind}:${action.target}`);out17.push(action)}}return out17}var TURNS=/error_max_turns|maximum number of turns/i;var SPEND=/error_max_budget_usd|maximum budget/i;function withCap(r,caps){if(r.ok||!caps||r.limit)return r;const said=`${r.detail??""}
114
- ${r.error??""}`;let by2=SPEND.test(said)?"spend":TURNS.test(said)?"turns":r.capped?.by;if(r.capped&&!SPEND.test(said)&&!TURNS.test(said)&&caps.spendUsd&&(r.usage?.costUsd??0)>=caps.spendUsd*.95)by2="spend";if(!by2||(by2==="turns"?!caps.turns:!caps.spendUsd))return r;const error2=by2==="turns"?`It used the ${caps.turns} turns this step may take.`:`It reached the $${caps.spendUsd} this step may spend.`;return{...r,capped:{by:by2},error:error2,detail:r.detail??said.trim()}}var cachedBinary=null;function claudeBinary(){if(cachedBinary)return cachedBinary;try{const found2=execFileSync5(process.platform==="win32"?"where":"which",["claude"],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).split("\n")[0]?.trim();if(found2)return cachedBinary=found2}catch{}const bundled=resolveClaudeBinary();return cachedBinary=bundled?.path??null}function toolVersion(bin){try{return execFileSync5(bin,["--version"],{encoding:"utf8",stdio:["ignore","pipe","ignore"],timeout:8e3}).trim()}catch{return null}}function sessionFile(id2){const dir=path37.join(os5.homedir(),".claude","projects");try{const found2=fs45.readdirSync(dir).find(d=>fs45.existsSync(path37.join(dir,d,`${id2}.jsonl`)));return found2?path37.join(dir,found2,`${id2}.jsonl`):null}catch{return null}}function sessionExists(id2){return sessionFile(id2)!==null}function costSoFar(id2){const file3=sessionFile(id2);if(!file3)return 0;try{const size=fs45.statSync(file3).size;const TAIL=1<<20;for(const from of size>TAIL?[size-TAIL,0]:[0]){const buf=Buffer.alloc(size-from);const fd=fs45.openSync(file3,"r");try{fs45.readSync(fd,buf,0,buf.length,from)}finally{fs45.closeSync(fd)}const lines3=buf.toString("utf8").split("\n");for(let i=lines3.length-1;i>=0;i-=1){if(!lines3[i].includes('"cost-state"'))continue;try{const e=JSON.parse(lines3[i]);if(e.type==="cost-state"&&(!e.sessionId||e.sessionId===id2)&&typeof e.totalCostUSD==="number")return e.totalCostUSD}catch{}}}return 0}catch{return void 0}}function claudeUsage(result,before){const u=result.usage??{};const n=v=>typeof v==="number"?v:void 0;return usageFrom({inputTokens:n(u.input_tokens),outputTokens:n(u.output_tokens),cachedReadTokens:n(u.cache_read_input_tokens),cachedWriteTokens:n(u.cache_creation_input_tokens)},n(result.total_cost_usd),before)}function windowConfig(w){return{mcpServers:{[w.name]:{type:"http",url:w.url,headers:{Authorization:`Bearer ${w.key}`}}}}}function claudeEnv(){return{...childEnv(),CLAUDE_CODE_DISABLE_BACKGROUND_TASKS:"1"}}function deniedTools(opts){const files=new Set;for(const f of opts.secretFiles??[]){files.add(path37.resolve(f));if(fs45.existsSync(f))files.add(fs45.realpathSync(f))}const allowed=opts.allowedTools??[];const readOnly=allowed.length>0&&!allowed.some(t=>WRITE_TOOLS.includes(t));const places=(opts.privatePlaces??[]).flatMap(p=>[`Read(/${p})`,`Read(/${p}/**)`,`Edit(/${p})`,`Edit(/${p}/**)`]);return[...opts.disallowedTools??[],...readOnly?WRITE_TOOLS:[],...[...files].flatMap(f=>[`Read(/${f})`,`Edit(/${f})`]),...places]}var WRITE_TOOLS=["Edit","MultiEdit","Write","NotebookEdit"];function runClaude(opts){if(opts.signal?.aborted)return Promise.resolve({ok:false,sessionId:opts.resume??opts.sessionId??"",text:"",error:"You stopped the squad."});const bin=claudeBinary();const sessionId=opts.resume??opts.sessionId??randomUUID2();if(!bin){return Promise.resolve({ok:false,sessionId,text:"",error:"Claude Code is not installed. Install it and log in, then try again."})}const args=["-p",opts.prompt,"--output-format","stream-json","--verbose"];const goesOn=Boolean(opts.resume||sessionExists(sessionId));if(goesOn)args.push("--resume",sessionId);else args.push("--session-id",sessionId);const costBefore=goesOn?costSoFar(sessionId):0;if(opts.allowedTools?.length)args.push("--allowedTools",opts.allowedTools.join(","));const denied=deniedTools(opts);if(denied.length)args.push("--disallowedTools",denied.join(","));args.push("--permission-mode",opts.permissionMode??"default");if(opts.schema)args.push("--json-schema",JSON.stringify(opts.schema));if(opts.appendSystemPrompt)args.push("--append-system-prompt",opts.appendSystemPrompt);if(opts.model)args.push("--model",opts.model);if(opts.effort)args.push("--effort",opts.effort);for(const d of opts.addDirs??[])args.push("--add-dir",d);if(opts.sandbox)args.push("--settings",JSON.stringify({sandbox:opts.sandbox}));if(opts.maxTurns)args.push("--max-turns",String(opts.maxTurns));if(opts.maxBudgetUsd)args.push("--max-budget-usd",String(opts.maxBudgetUsd));if(opts.window)args.push("--mcp-config",JSON.stringify(windowConfig(opts.window)));return new Promise(resolve2=>{const child=track(spawn5(bin,args,{cwd:opts.cwd,env:{...claudeEnv(),...opts.env,...opts.window?{MCP_TOOL_TIMEOUT:"240000"}:{}},stdio:["ignore","pipe","pipe"]}));if(child.pid)opts.onSpawn?.(child.pid);opts.signal?.addEventListener("abort",()=>{result={ok:false,sessionId,text:lastText,error:"You stopped the squad."};child.kill("SIGTERM")});let buffer="";let stderr="";let result=null;let lastText="";const timer=setTimeout(()=>{child.kill("SIGTERM");result={ok:false,sessionId,text:lastText,error:`Stopped after ${Math.round((opts.timeoutMs??0)/6e4)} minutes without finishing.`}},opts.timeoutMs??20*6e4);const handle=line3=>{if(!line3.trim())return;let event;try{event=JSON.parse(line3)}catch{return}opts.onActivity?.();if(event.type==="assistant"){for(const part of event.message?.content??[]){if(part.type==="tool_use"&&part.name!=="StructuredOutput")opts.onTool?.({name:part.name,input:part.input??{}});if(part.type==="text"&&part.text){lastText=part.text;opts.onText?.(part.text)}}}else if(event.type==="user"){if(!opts.onBlocked)return;for(const part of event.message?.content??[]){if(part?.type!=="tool_result")continue;const out17=typeof part.content==="string"?part.content:Array.isArray(part.content)?part.content.map(c=>c?.text??"").join("\n"):"";for(const b of claudeBlocked(out17))opts.onBlocked(b)}}else if(event.type==="result"){const isError=Boolean(event.is_error)||event.subtype!=="success";const usage=claudeUsage(event,costBefore);const errors=Array.isArray(event.errors)?event.errors.filter(e=>typeof e==="string"):[];const said=typeof event.result==="string"?event.result:[String(event.subtype),...errors].join(": ");const capped=event.subtype==="error_max_turns"?"turns":event.subtype==="error_max_budget_usd"?"spend":null;result={ok:!isError,sessionId:event.session_id??sessionId,text:typeof event.result==="string"?event.result:lastText,structured:event.structured_output??void 0,...isError?{error:plainError(said),detail:said.trim().slice(-2e3)}:{},...usage?{usage}:{},...capped?{capped:{by:capped}}:{}}}};let saveError;const saving=line3=>{if(saveError)return;try{handle(line3)}catch(err2){saveError=asError(err2);result={ok:false,sessionId,text:lastText,error:COULD_NOT_SAVE,saveError};child.kill("SIGTERM")}};child.stdout.on("data",chunk=>{buffer+=chunk.toString("utf8");let nl;while((nl=buffer.indexOf("\n"))>=0){const line3=buffer.slice(0,nl);buffer=buffer.slice(nl+1);saving(line3)}});child.stderr.on("data",chunk=>{stderr=(stderr+chunk.toString("utf8")).slice(-4e3)});child.on("error",err2=>{clearTimeout(timer);resolve2({ok:false,sessionId,text:"",error:`Could not start Claude Code: ${err2.message}`})});void whenExited(child).then(code=>{clearTimeout(timer);if(buffer)saving(buffer);if(result)return resolve2(result);const said=stderr.trim()||`Claude Code stopped (exit ${code}).`;resolve2({ok:false,sessionId,text:lastText,error:plainError(said),detail:said.slice(-2e3)})})})}var OWN_WORDS=[/^The squad was interrupted before it finished, for example because the board was closed\. Press Try again to carry on\.$/,/^Claude's usage limit is reached\. (It resets .{1,80}; press Try again after that|Press Try again once the limit resets)\.$/,/^Claude Code is signed out on this computer\. Open the Terminal app, type claude and press Enter, then sign in when it asks\. Then press Try again\.$/,/^The Claude account has no credit left\. Add credit or sign in with another account, then press Try again\.$/,/^Claude is busy right now\. Wait a few minutes, then press Try again\.$/,/^Claude Code could not reach the internet\. Check the connection, then press Try again\.$/,/^Claude's service had a problem on its side\. Wait a few minutes, then press Try again\.$/,/^Claude Code is not installed\. Install it and log in, then try again\.$/];var PASSING=[/^Claude is busy right now\. /,/^Claude Code could not reach the internet\. /,/^Claude's service had a problem on its side\. /];function passing(said){return PASSING.some(re=>re.test(said.trim()))}function ownWords(said){return OWN_WORDS.some(re=>re.test(said.trim()))}var SIGNED_OUT=[/\bnot logged in\b/i,/\b(?:please )?run \/login\b/i,/\binvalid api key\b/i,/\bfailed to authenticate\. api error\b/i,/\bAPI Error: 401\b/,/"type":\s*"authentication_error"/,/\boauth token (?:has )?(?:expired|been revoked|is expired|is invalid)\b/i,/^authentication required\b/im];function plainError(raw,now2=Date.now()){const s=raw.toLowerCase();if(s.includes("exit 143")||s.includes("sigterm")){return"The squad was interrupted before it finished, for example because the board was closed. Press Try again to carry on."}const limit=claudeLimit(raw,now2);if(limit){return limit.resetsAt?`Claude's usage limit is reached. It resets ${resetWords(limit.resetsAt,now2)}; press Try again after that.`:"Claude's usage limit is reached. Press Try again once the limit resets."}if(SIGNED_OUT.some(re=>re.test(raw))){return"Claude Code is signed out on this computer. Open the Terminal app, type claude and press Enter, then sign in when it asks. Then press Try again."}if(s.includes("credit balance")&&s.includes("low")){return"The Claude account has no credit left. Add credit or sign in with another account, then press Try again."}if(s.includes("overloaded")||/\b529\b/.test(s)){return"Claude is busy right now. Wait a few minutes, then press Try again."}if(/\b(enotfound|econnreset|econnrefused|etimedout|getaddrinfo|fetch failed|network error|connection error|socket hang up)\b/.test(s)){return"Claude Code could not reach the internet. Check the connection, then press Try again."}if(/api error:? 5\d\d\b/.test(s)){return"Claude's service had a problem on its side. Wait a few minutes, then press Try again."}return raw.split("\n").filter(Boolean).slice(-3).join(" ").slice(0,400)}function describeTool(e,cwd){const rel=p=>{if(typeof p!=="string")return"";const r=path37.relative(cwd,p);return!r?p:r.startsWith("..")?path37.basename(p):r};const i=e.input;switch(e.name){case"Read":return`Reading ${rel(i.file_path)}`;case"Edit":case"MultiEdit":return`Editing ${rel(i.file_path)}`;case"Write":return`Writing ${rel(i.file_path)}`;case"Glob":return`Looking for ${String(i.pattern??"files")}`;case"Grep":return`Searching for "${String(i.pattern??"").slice(0,40)}"`;case"LS":return`Listing ${rel(i.path)||"the folder"}`;case"Bash":{const cmd13=String(i.command??"").split("\n")[0].replace(/^cd\s+("[^"]*"|'[^']*'|\S+)\s*&&\s*/,"").slice(0,70);return cmd13?`Running ${cmd13}`:null}case"WebFetch":case"WebSearch":return`Looking up ${String(i.url??i.query??"").slice(0,60)}`;default:return null}}import{execFile}from"child_process";var CLOUD={bedrock:"AWS Bedrock",vertex:"Google Vertex AI",foundry:"Azure AI Foundry",anthropicAws:"Anthropic on AWS",anthropicGoogleCloud:"Anthropic on Google Cloud",gateway:"a model gateway"};function planLabel(plan){if(typeof plan!=="string"||!plan.trim())return"Claude plan";const titled=plan.trim().replace(/\S+/g,w=>w.charAt(0).toUpperCase()+w.slice(1));return/^claude(\s|$)/i.test(titled)?titled:`Claude ${titled}`}function billingFrom(stdout){let s;try{s=JSON.parse(stdout)}catch{return null}if(!s||typeof s!=="object"||Array.isArray(s))return null;const o=s;const provider=typeof o.apiProvider==="string"?o.apiProvider:"firstParty";if(provider!=="firstParty")return{kind:"api-key",label:CLOUD[provider]??provider};if(typeof o.apiKeySource==="string"&&o.apiKeySource)return{kind:"api-key",label:"Anthropic API key"};if(o.authMethod==="api_key"||o.authMethod==="api_key_helper")return{kind:"api-key",label:"Anthropic API key"};if(o.authMethod==="third_party")return{kind:"api-key",label:"a cloud account"};if(o.authMethod==="claude.ai"||o.authMethod==="oauth_token"||typeof o.subscriptionType==="string")return{kind:"subscription",label:planLabel(o.subscriptionType)};return null}function claudeBilling(timeoutMs=8e3){const bin=claudeBinary();if(!bin)return Promise.resolve(null);return new Promise(resolve2=>{execFile(bin,["auth","status","--json"],{env:claudeEnv(),timeout:timeoutMs,encoding:"utf8",windowsHide:true},(_err,stdout)=>{resolve2(billingFrom(String(stdout??"")))})})}function quoted(s){return/^[\w@%+=:,./-]+$/.test(s)?s:`'${s.replace(/'/g,`'\\''`)}'`}function claudeResume(sessionId,cwd){return`${cwd?`cd ${quoted(cwd)} && `:""}claude --resume ${sessionId}`}var READ_SHELL=["cat","ls","grep","rg","head","tail","wc","cd","pwd","git log","git show","git diff","git status"].map(c=>`Bash(${c}:*)`);var READ_ONLY=["Read","Glob","Grep","LS",...READ_SHELL];var GIT_READ=["git diff","git status","git log","git show"].map(c=>`Bash(${c}:*)`);var BUILD_TOOLS=["Read","Edit","MultiEdit","Write","Glob","Grep","LS","Bash","TodoWrite"];var RUN_TOOLS=["Read","Glob","Grep","LS","Bash","TodoWrite"];var NO_GIT_WRITES=["Bash(git commit:*)","Bash(git push:*)","Bash(git checkout:*)","Bash(git switch:*)","Bash(git reset:*)","Bash(git rebase:*)","Bash(git merge:*)","Bash(git branch:*)","Bash(git tag:*)","Bash(git stash:*)","Bash(git clean:*)","Bash(git pull:*)","Bash(git cherry-pick:*)","Bash(git revert:*)","Bash(git am:*)","Bash(git update-ref:*)","Bash(git worktree:*)","Bash(git -C:*)","Bash(gh pr:*)","Bash(gh repo:*)"];var FILE_TOOLS=["Edit","Write","MultiEdit"];function protectedRules(names2){return names2.flatMap(n=>{const name=n.replace(/^\//,"");const pattern=name.endsWith("/")?`**/${name}**`:`**/${name}`;return[pattern,`//${pattern}`].flatMap(p=>FILE_TOOLS.map(t=>`${t}(${p})`))})}function windowTools(w){return w?w.tools.map(t=>`mcp__${w.name}__${t}`):[]}var LEVEL_MODEL={strongest:"opus",balanced:"sonnet",fast:"haiku"};function claudeRunOptions(r){const edits=r.permissions.includes("edit");const own2=edits?BUILD_TOOLS:r.permissions.includes("run")?RUN_TOOLS:[...new Set([...READ_ONLY,...r.permissions.includes("git-read")?GIT_READ:[]])];const access={allowedTools:[...own2,...windowTools(r.window)],disallowedTools:[...NO_GIT_WRITES,...protectedRules(r.protectedFiles??[])],permissionMode:edits?"acceptEdits":"default"};return{cwd:r.cwd,prompt:r.prompt,...r.sessionId?{sessionId:r.sessionId}:{},...r.resume?{resume:r.resume}:{},...access,...r.secretFiles?{secretFiles:r.secretFiles}:{},...r.privatePlaces?{privatePlaces:r.privatePlaces}:{},...r.schema?{schema:r.schema}:{},...r.instructions?{appendSystemPrompt:r.instructions}:{},...r.model||r.level?{model:r.model||LEVEL_MODEL[r.level]}:{},...r.effort?{effort:r.effort}:{},...r.addDirs?{addDirs:r.addDirs}:{},...r.sandbox?{sandbox:claudeSandbox(r.sandbox)}:{},...r.env?{env:r.env}:{},...r.caps?.turns?{maxTurns:r.caps.turns}:{},...r.caps?.spendUsd?{maxBudgetUsd:r.caps.spendUsd}:{},...r.window?{window:r.window}:{},...r.timeoutMs?{timeoutMs:r.timeoutMs}:{},...r.signal?{signal:r.signal}:{},...r.onTool?{onTool:r.onTool}:{},...r.onBlocked?{onBlocked:r.onBlocked}:{},...r.onText?{onText:r.onText}:{},...r.onActivity?{onActivity:r.onActivity}:{},...r.onSpawn?{onSpawn:r.onSpawn}:{}}}function acpAdapterPath(){try{const require2=createRequire2(import.meta.url);const pkg=require2.resolve("@agentclientprotocol/claude-agent-acp/package.json");const entry=path38.join(path38.dirname(pkg),"dist","index.js");return fs46.existsSync(entry)?entry:null}catch{return null}}var WINDOW_ENV={MCP_TOOL_TIMEOUT:"240000"};function claudeLaunch(opts,adapter,bin){const denied=deniedTools(opts);const options={...opts.allowedTools?.length?{allowedTools:opts.allowedTools}:{},...denied.length?{disallowedTools:denied}:{},allowDangerouslySkipPermissions:false,...opts.permissionMode?{permissionMode:opts.permissionMode}:{},...opts.schema?{outputFormat:{type:"json_schema",schema:opts.schema}}:{},...opts.appendSystemPrompt?{systemPrompt:{type:"preset",preset:"claude_code",append:opts.appendSystemPrompt}}:{},...opts.model?{model:opts.model}:{},...opts.effort?{effort:opts.effort}:{},...opts.addDirs?.length?{additionalDirectories:opts.addDirs}:{},...opts.sandbox?{sandbox:opts.sandbox}:{},...opts.maxTurns?{maxTurns:opts.maxTurns}:{},...opts.maxBudgetUsd?{maxBudgetUsd:opts.maxBudgetUsd}:{}};return{label:"Claude Code",command:process.execPath,args:[adapter],env:{...claudeEnv(),...opts.env,CLAUDE_CODE_EXECUTABLE:bin,...opts.model?{ANTHROPIC_MODEL:opts.model}:{},...opts.window?WINDOW_ENV:{}},meta:{claudeCode:{options}},mode:opts.permissionMode??"default",canResume:sessionExists,costSoFar,explain:plainError,toolName:update=>update._meta?.claudeCode?.toolName,structuredTool:"StructuredOutput",blocked:claudeBlocked}}async function runClaudeAcp(opts){const adapter=acpAdapterPath();if(!adapter)throw new AcpStartError("The Claude ACP adapter is not installed.");const bin=claudeBinary();if(!bin)return{ok:false,sessionId:opts.resume??opts.sessionId??"",text:"",error:"Claude Code is not installed. Install it and log in, then try again."};return runAcp(claudeLaunch(opts,adapter,bin),{...opts,...opts.logDir?{logDir:opts.logDir}:{}})}var SAID_LIMIT=/^(you've (hit|reached) your [\w' -]{0,40}limit\b|you're out of (extra )?usage|claude (ai )?usage limit reached|(5-hour|weekly|session) limit reached)/i;function withLimit(r){if(r.limit)return r;if(r.ok){const said=(r.text??"").trim();if(r.structured!==void 0||said.length>400||!SAID_LIMIT.test(said))return r;return withLimit({...r,ok:false,error:plainError(said),detail:said})}const found2=claudeLimit(r.detail??r.error??"");return found2?{...r,limit:{name:"Claude",...found2}}:r}function adapterVersion(){try{const require2=createRequire2(import.meta.url);return require2("@agentclientprotocol/claude-agent-acp/package.json").version??null}catch{return null}}var claudeTool={name:"claude",label:"Claude Code",plan:"Claude",canRun:true,supportsEffort:true,efforts:["xhigh","high","medium","low"],instructionFiles:["CLAUDE.md","CLAUDE.local.md",".claude/"],privateFiles:home2=>claudePrivateFiles(home2),secretEnv:CLAUDE_ENV,modelFor:level=>({strongest:"Opus",balanced:"Sonnet",fast:"Haiku"})[level],ready(){const bin=claudeBinary();return Boolean(bin&&toolVersion(bin))},version(){const bin=claudeBinary();const v=bin?toolVersion(bin):null;const acp=adapterVersion();return v?`${v}${acp?`, ACP adapter ${acp}`:""}`:null},sandbox:claudeSandboxSupport,caps:{turns:true,spend:true},blocked:claudeBlocked,async run(r){const opts=claudeRunOptions(r);const done=res=>withCap(withLimit(res),r.caps);if(r.direct||!acpAdapterPath())return done(await runClaude(opts));try{return done(await runClaudeAcp({...opts,...r.logDir?{logDir:r.logDir}:{}}))}catch(err2){if(!(err2 instanceof AcpStartError))throw err2;if(r.signal?.aborted)return{ok:false,sessionId:opts.resume??opts.sessionId??"",text:"",error:"You stopped the squad."};r.onFallback?.(err2.message);return done(await runClaude(opts))}},ownWords,passing,describe:describeTool,billing:()=>claudeBilling(),resumeCommand:claudeResume};import{execSync}from"child_process";import path39 from"path";var codexTool={name:"codex",label:"Codex",plan:"ChatGPT",canRun:false,supportsEffort:false,instructionFiles:["AGENTS.md"],privateFiles:home2=>["auth.json","sessions","history.jsonl"].map(f=>path39.join(home2,".codex",f)),secretEnv:["OPENAI_*","CODEX_*"],modelFor:()=>"",ready(){try{execSync(process.platform==="win32"?"where codex":"which codex",{stdio:"ignore"});return true}catch{return false}},run:()=>Promise.reject(new Error("squad-kit cannot run Codex yet.")),describe:()=>null};import{execFile as execFile2}from"child_process";import fs47 from"fs";import path40 from"path";import{pathToFileURL}from"url";import os6 from"os";import net from"net";var PRIVATE_V4=[["0.0.0.0",8],["10.0.0.0",8],["100.64.0.0",10],["127.0.0.0",8],["169.254.0.0",16],["172.16.0.0",12],["192.168.0.0",16],["198.18.0.0",15]];function v4Number(ip){return ip.split(".").reduce((n,part)=>n*256+Number(part),0)}function privateV4(ip){const n=v4Number(ip);return PRIVATE_V4.some(([net4,bits])=>Math.floor(n/2**(32-bits))===Math.floor(v4Number(net4)/2**(32-bits)))}function v6Words(ip){let s=ip.toLowerCase().replace(/%.*$/,"");const tail2=/(\d+\.\d+\.\d+\.\d+)$/.exec(s);if(tail2){const n=v4Number(tail2[1]);s=`${s.slice(0,-tail2[1].length)}${(n>>>16).toString(16)}:${(n&65535).toString(16)}`}const[head,rest]=s.split("::");const a=head?head.split(":"):[];const b=rest!==void 0&&rest!==""?rest.split(":"):[];const fill2=rest===void 0?0:8-a.length-b.length;if(fill2<0)return null;const words=[...a,...Array(fill2).fill("0"),...b].map(w=>parseInt(w||"0",16));return words.length===8&&words.every(w=>Number.isInteger(w)&&w>=0&&w<=65535)?words:null}function privateV6(ip){const w=v6Words(ip);if(!w)return true;const v4=(hi,lo)=>`${hi>>>8}.${hi&255}.${lo>>>8}.${lo&255}`;if(w.slice(0,5).every(x=>x===0)&&(w[5]===0||w[5]===65535))return w[5]===0&&w[6]===0?w[7]<=1:privateV4(v4(w[6],w[7]));if(w[0]===100&&w[1]===65435&&w.slice(2,6).every(x=>x===0))return privateV4(v4(w[6],w[7]));if(w[0]===8194)return privateV4(v4(w[1],w[2]));return(w[0]&65024)===64512||(w[0]&65472)===65152}function isLocalHost(host){const h=host.toLowerCase().replace(/^\[|\]$/g,"").replace(/\.$/,"");if(h==="localhost"||h.endsWith(".localhost")||h.endsWith(".local"))return true;const kind=net.isIP(h);if(kind===4)return privateV4(h);if(kind===6)return privateV6(h);return false}function isAppAddress(url,origin){let u;let o;try{u=new URL(url);o=new URL(origin)}catch{return false}if(u.origin===o.origin)return true;const loop=x=>["localhost","127.0.0.1","[::1]"].includes(x.hostname);return loop(u)&&loop(o)&&u.protocol===o.protocol&&u.port===o.port}function blockedFrom(url,origin,also=[]){let u;try{u=new URL(url)}catch{return false}if(!/^(https?|wss?):$/.test(u.protocol))return false;return!isAppAddress(url,origin)&&!also.some(a=>isAppAddress(url,a))&&isLocalHost(u.hostname)}var StepFailure=class extends Error{constructor(kind,message,address){super(message);this.kind=kind;this.address=address}kind;address};var BrowserSetupError=class extends Error{constructor(message,detail){super(message);this.detail=detail}detail};var SETTING_UP="Setting up squad-kit's screenshot browser. This happens only once.";var PLAYWRIGHT_VERSION="1.64.1";function playwrightDir(){return path40.join(squadHome(),"tools","playwright")}var run=(file3,args,o)=>new Promise(resolve2=>{const env=Object.fromEntries(Object.entries(process.env).filter(([k])=>!/^npm_/i.test(k)));execFile2(file3,args,{cwd:o.cwd,timeout:o.timeoutMs,maxBuffer:8*1024*1024,shell:process.platform==="win32",env:{...env,npm_config_update_notifier:"false",ELECTRON_RUN_AS_NODE:"1",PLAYWRIGHT_SKIP_BROWSER_GC:"1"}},(err2,stdout,stderr)=>resolve2({ok:!err2,output:`${stdout}
114
+ ${r.error??""}`;let by2=SPEND.test(said)?"spend":TURNS.test(said)?"turns":r.capped?.by;if(r.capped&&!SPEND.test(said)&&!TURNS.test(said)&&caps.spendUsd&&(r.usage?.costUsd??0)>=caps.spendUsd*.95)by2="spend";if(!by2||(by2==="turns"?!caps.turns:!caps.spendUsd))return r;const error2=by2==="turns"?`It used the ${caps.turns} turns this step may take.`:`It reached the $${caps.spendUsd} this step may spend.`;return{...r,capped:{by:by2},error:error2,detail:r.detail??said.trim()}}var cachedBinary=null;function claudeBinary(){if(cachedBinary)return cachedBinary;try{const found2=execFileSync5(process.platform==="win32"?"where":"which",["claude"],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).split("\n")[0]?.trim();if(found2)return cachedBinary=found2}catch{}const bundled=resolveClaudeBinary();return cachedBinary=bundled?.path??null}function toolVersion(bin){try{return execFileSync5(bin,["--version"],{encoding:"utf8",stdio:["ignore","pipe","ignore"],timeout:8e3}).trim()}catch{return null}}function sessionFile(id2){const dir=path37.join(os5.homedir(),".claude","projects");try{const found2=fs45.readdirSync(dir).find(d=>fs45.existsSync(path37.join(dir,d,`${id2}.jsonl`)));return found2?path37.join(dir,found2,`${id2}.jsonl`):null}catch{return null}}function sessionExists(id2){return sessionFile(id2)!==null}function costSoFar(id2){const file3=sessionFile(id2);if(!file3)return 0;try{const size=fs45.statSync(file3).size;const TAIL=1<<20;for(const from of size>TAIL?[size-TAIL,0]:[0]){const buf=Buffer.alloc(size-from);const fd=fs45.openSync(file3,"r");try{fs45.readSync(fd,buf,0,buf.length,from)}finally{fs45.closeSync(fd)}const lines3=buf.toString("utf8").split("\n");for(let i=lines3.length-1;i>=0;i-=1){if(!lines3[i].includes('"cost-state"'))continue;try{const e=JSON.parse(lines3[i]);if(e.type==="cost-state"&&(!e.sessionId||e.sessionId===id2)&&typeof e.totalCostUSD==="number")return e.totalCostUSD}catch{}}}return 0}catch{return void 0}}function claudeUsage(result,before){const u=result.usage??{};const n=v=>typeof v==="number"?v:void 0;return usageFrom({inputTokens:n(u.input_tokens),outputTokens:n(u.output_tokens),cachedReadTokens:n(u.cache_read_input_tokens),cachedWriteTokens:n(u.cache_creation_input_tokens)},n(result.total_cost_usd),before)}function windowConfig(w){return{mcpServers:{[w.name]:{type:"http",url:w.url,headers:{Authorization:`Bearer ${w.key}`}}}}}function claudeEnv(){return{...childEnv(),CLAUDE_CODE_DISABLE_BACKGROUND_TASKS:"1"}}function deniedTools(opts){const files=new Set;for(const f of opts.secretFiles??[]){files.add(path37.resolve(f));if(fs45.existsSync(f))files.add(fs45.realpathSync(f))}const allowed=opts.allowedTools??[];const readOnly=allowed.length>0&&!allowed.some(t=>WRITE_TOOLS.includes(t));const places=(opts.privatePlaces??[]).flatMap(p=>[`Read(/${p})`,`Read(/${p}/**)`,`Edit(/${p})`,`Edit(/${p}/**)`]);return[...opts.disallowedTools??[],...readOnly?WRITE_TOOLS:[],...[...files].flatMap(f=>[`Read(/${f})`,`Edit(/${f})`]),...places]}var WRITE_TOOLS=["Edit","MultiEdit","Write","NotebookEdit"];function runClaude(opts){if(opts.signal?.aborted)return Promise.resolve({ok:false,sessionId:opts.resume??opts.sessionId??"",text:"",error:"You stopped the squad."});const bin=claudeBinary();const sessionId=opts.resume??opts.sessionId??randomUUID2();if(!bin){return Promise.resolve({ok:false,sessionId,text:"",error:"Claude Code is not installed. Install it and log in, then try again."})}const args=["-p",opts.prompt,"--output-format","stream-json","--verbose"];const goesOn=Boolean(opts.resume||sessionExists(sessionId));if(goesOn)args.push("--resume",sessionId);else args.push("--session-id",sessionId);const costBefore=goesOn?costSoFar(sessionId):0;if(opts.allowedTools?.length)args.push("--allowedTools",opts.allowedTools.join(","));const denied=deniedTools(opts);if(denied.length)args.push("--disallowedTools",denied.join(","));args.push("--permission-mode",opts.permissionMode??"default");if(opts.schema)args.push("--json-schema",JSON.stringify(opts.schema));if(opts.appendSystemPrompt)args.push("--append-system-prompt",opts.appendSystemPrompt);if(opts.model)args.push("--model",opts.model);if(opts.effort)args.push("--effort",opts.effort);for(const d of opts.addDirs??[])args.push("--add-dir",d);if(opts.sandbox)args.push("--settings",JSON.stringify({sandbox:opts.sandbox}));if(opts.maxTurns)args.push("--max-turns",String(opts.maxTurns));if(opts.maxBudgetUsd)args.push("--max-budget-usd",String(opts.maxBudgetUsd));if(opts.window)args.push("--mcp-config",JSON.stringify(windowConfig(opts.window)));return new Promise(resolve2=>{const child=track(spawn5(bin,args,{cwd:opts.cwd,env:{...claudeEnv(),...opts.env,...opts.window?{MCP_TOOL_TIMEOUT:"240000"}:{}},stdio:["ignore","pipe","pipe"]}));if(child.pid)opts.onSpawn?.(child.pid);opts.signal?.addEventListener("abort",()=>{result={ok:false,sessionId,text:lastText,error:"You stopped the squad."};child.kill("SIGTERM")});let buffer="";let stderr="";let result=null;let lastText="";const timer=setTimeout(()=>{child.kill("SIGTERM");result={ok:false,sessionId,text:lastText,error:`Stopped after ${Math.round((opts.timeoutMs??0)/6e4)} minutes without finishing.`}},opts.timeoutMs??20*6e4);const handle=line3=>{if(!line3.trim())return;let event;try{event=JSON.parse(line3)}catch{return}opts.onActivity?.();if(event.type==="assistant"){for(const part of event.message?.content??[]){if(part.type==="tool_use"&&part.name!=="StructuredOutput")opts.onTool?.({name:part.name,input:part.input??{}});if(part.type==="tool_use"&&part.name==="TodoWrite"&&opts.onPlan){const todos=part.input?.todos;if(Array.isArray(todos))opts.onPlan(planOf(todos))}if(part.type==="text"&&part.text){lastText=part.text;opts.onText?.(part.text)}}}else if(event.type==="user"){if(!opts.onBlocked)return;for(const part of event.message?.content??[]){if(part?.type!=="tool_result")continue;const out17=typeof part.content==="string"?part.content:Array.isArray(part.content)?part.content.map(c=>c?.text??"").join("\n"):"";for(const b of claudeBlocked(out17))opts.onBlocked(b)}}else if(event.type==="result"){const isError=Boolean(event.is_error)||event.subtype!=="success";const usage=claudeUsage(event,costBefore);const errors=Array.isArray(event.errors)?event.errors.filter(e=>typeof e==="string"):[];const said=typeof event.result==="string"?event.result:[String(event.subtype),...errors].join(": ");const capped=event.subtype==="error_max_turns"?"turns":event.subtype==="error_max_budget_usd"?"spend":null;result={ok:!isError,sessionId:event.session_id??sessionId,text:typeof event.result==="string"?event.result:lastText,structured:event.structured_output??void 0,...isError?{error:plainError(said),detail:said.trim().slice(-2e3)}:{},...usage?{usage}:{},...capped?{capped:{by:capped}}:{}}}};let saveError;const saving=line3=>{if(saveError)return;try{handle(line3)}catch(err2){saveError=asError(err2);result={ok:false,sessionId,text:lastText,error:COULD_NOT_SAVE,saveError};child.kill("SIGTERM")}};child.stdout.on("data",chunk=>{buffer+=chunk.toString("utf8");let nl;while((nl=buffer.indexOf("\n"))>=0){const line3=buffer.slice(0,nl);buffer=buffer.slice(nl+1);saving(line3)}});child.stderr.on("data",chunk=>{stderr=(stderr+chunk.toString("utf8")).slice(-4e3)});child.on("error",err2=>{clearTimeout(timer);resolve2({ok:false,sessionId,text:"",error:`Could not start Claude Code: ${err2.message}`})});void whenExited(child).then(code=>{clearTimeout(timer);if(buffer)saving(buffer);if(result)return resolve2(result);const said=stderr.trim()||`Claude Code stopped (exit ${code}).`;resolve2({ok:false,sessionId,text:lastText,error:plainError(said),detail:said.slice(-2e3)})})})}var OWN_WORDS=[/^The squad was interrupted before it finished, for example because the board was closed\. Press Try again to carry on\.$/,/^Claude's usage limit is reached\. (It resets .{1,80}; press Try again after that|Press Try again once the limit resets)\.$/,/^Claude Code is signed out on this computer\. Open the Terminal app, type claude and press Enter, then sign in when it asks\. Then press Try again\.$/,/^The Claude account has no credit left\. Add credit or sign in with another account, then press Try again\.$/,/^Claude is busy right now\. Wait a few minutes, then press Try again\.$/,/^Claude Code could not reach the internet\. Check the connection, then press Try again\.$/,/^Claude's service had a problem on its side\. Wait a few minutes, then press Try again\.$/,/^Claude Code is not installed\. Install it and log in, then try again\.$/];var PASSING=[/^Claude is busy right now\. /,/^Claude Code could not reach the internet\. /,/^Claude's service had a problem on its side\. /];function passing(said){return PASSING.some(re=>re.test(said.trim()))}function ownWords(said){return OWN_WORDS.some(re=>re.test(said.trim()))}var SIGNED_OUT=[/\bnot logged in\b/i,/\b(?:please )?run \/login\b/i,/\binvalid api key\b/i,/\bfailed to authenticate\. api error\b/i,/\bAPI Error: 401\b/,/"type":\s*"authentication_error"/,/\boauth token (?:has )?(?:expired|been revoked|is expired|is invalid)\b/i,/^authentication required\b/im];function plainError(raw,now2=Date.now()){const s=raw.toLowerCase();if(s.includes("exit 143")||s.includes("sigterm")){return"The squad was interrupted before it finished, for example because the board was closed. Press Try again to carry on."}const limit=claudeLimit(raw,now2);if(limit){return limit.resetsAt?`Claude's usage limit is reached. It resets ${resetWords(limit.resetsAt,now2)}; press Try again after that.`:"Claude's usage limit is reached. Press Try again once the limit resets."}if(SIGNED_OUT.some(re=>re.test(raw))){return"Claude Code is signed out on this computer. Open the Terminal app, type claude and press Enter, then sign in when it asks. Then press Try again."}if(s.includes("credit balance")&&s.includes("low")){return"The Claude account has no credit left. Add credit or sign in with another account, then press Try again."}if(s.includes("overloaded")||/\b529\b/.test(s)){return"Claude is busy right now. Wait a few minutes, then press Try again."}if(/\b(enotfound|econnreset|econnrefused|etimedout|getaddrinfo|fetch failed|network error|connection error|socket hang up)\b/.test(s)){return"Claude Code could not reach the internet. Check the connection, then press Try again."}if(/api error:? 5\d\d\b/.test(s)){return"Claude's service had a problem on its side. Wait a few minutes, then press Try again."}return raw.split("\n").filter(Boolean).slice(-3).join(" ").slice(0,400)}function describeTool(e,cwd){const rel=p=>{if(typeof p!=="string")return"";const r=path37.relative(cwd,p);return!r?p:r.startsWith("..")?path37.basename(p):r};const i=e.input;switch(e.name){case"Read":return`Reading ${rel(i.file_path)}`;case"Edit":case"MultiEdit":return`Editing ${rel(i.file_path)}`;case"Write":return`Writing ${rel(i.file_path)}`;case"Glob":return`Looking for ${String(i.pattern??"files")}`;case"Grep":return`Searching for "${String(i.pattern??"").slice(0,40)}"`;case"LS":return`Listing ${rel(i.path)||"the folder"}`;case"Bash":{const cmd13=String(i.command??"").split("\n")[0].replace(/^cd\s+("[^"]*"|'[^']*'|\S+)\s*&&\s*/,"").slice(0,70);return cmd13?`Running ${cmd13}`:null}case"WebFetch":case"WebSearch":return`Looking up ${String(i.url??i.query??"").slice(0,60)}`;default:return null}}import{execFile}from"child_process";var CLOUD={bedrock:"AWS Bedrock",vertex:"Google Vertex AI",foundry:"Azure AI Foundry",anthropicAws:"Anthropic on AWS",anthropicGoogleCloud:"Anthropic on Google Cloud",gateway:"a model gateway"};function planLabel(plan){if(typeof plan!=="string"||!plan.trim())return"Claude plan";const titled=plan.trim().replace(/\S+/g,w=>w.charAt(0).toUpperCase()+w.slice(1));return/^claude(\s|$)/i.test(titled)?titled:`Claude ${titled}`}function billingFrom(stdout){let s;try{s=JSON.parse(stdout)}catch{return null}if(!s||typeof s!=="object"||Array.isArray(s))return null;const o=s;const provider=typeof o.apiProvider==="string"?o.apiProvider:"firstParty";if(provider!=="firstParty")return{kind:"api-key",label:CLOUD[provider]??provider};if(typeof o.apiKeySource==="string"&&o.apiKeySource)return{kind:"api-key",label:"Anthropic API key"};if(o.authMethod==="api_key"||o.authMethod==="api_key_helper")return{kind:"api-key",label:"Anthropic API key"};if(o.authMethod==="third_party")return{kind:"api-key",label:"a cloud account"};if(o.authMethod==="claude.ai"||o.authMethod==="oauth_token"||typeof o.subscriptionType==="string")return{kind:"subscription",label:planLabel(o.subscriptionType)};return null}function claudeBilling(timeoutMs=8e3){const bin=claudeBinary();if(!bin)return Promise.resolve(null);return new Promise(resolve2=>{execFile(bin,["auth","status","--json"],{env:claudeEnv(),timeout:timeoutMs,encoding:"utf8",windowsHide:true},(_err,stdout)=>{resolve2(billingFrom(String(stdout??"")))})})}function quoted(s){return/^[\w@%+=:,./-]+$/.test(s)?s:`'${s.replace(/'/g,`'\\''`)}'`}function claudeResume(sessionId,cwd){return`${cwd?`cd ${quoted(cwd)} && `:""}claude --resume ${sessionId}`}var READ_SHELL=["cat","ls","grep","rg","head","tail","wc","cd","pwd","git log","git show","git diff","git status"].map(c=>`Bash(${c}:*)`);var READ_ONLY=["Read","Glob","Grep","LS",...READ_SHELL];var GIT_READ=["git diff","git status","git log","git show"].map(c=>`Bash(${c}:*)`);var TODO_TOOLS=["TodoWrite","TaskCreate","TaskUpdate","TaskList","TaskGet"];var BUILD_TOOLS=["Read","Edit","MultiEdit","Write","Glob","Grep","LS","Bash",...TODO_TOOLS];var RUN_TOOLS=["Read","Glob","Grep","LS","Bash","TodoWrite"];var NO_GIT_WRITES=["Bash(git commit:*)","Bash(git push:*)","Bash(git checkout:*)","Bash(git switch:*)","Bash(git reset:*)","Bash(git rebase:*)","Bash(git merge:*)","Bash(git branch:*)","Bash(git tag:*)","Bash(git stash:*)","Bash(git clean:*)","Bash(git pull:*)","Bash(git cherry-pick:*)","Bash(git revert:*)","Bash(git am:*)","Bash(git update-ref:*)","Bash(git worktree:*)","Bash(git -C:*)","Bash(gh pr:*)","Bash(gh repo:*)"];var FILE_TOOLS=["Edit","Write","MultiEdit"];function protectedRules(names2){return names2.flatMap(n=>{const name=n.replace(/^\//,"");const pattern=name.endsWith("/")?`**/${name}**`:`**/${name}`;return[pattern,`//${pattern}`].flatMap(p=>FILE_TOOLS.map(t=>`${t}(${p})`))})}function windowTools(w){return w?w.tools.map(t=>`mcp__${w.name}__${t}`):[]}var LEVEL_MODEL={strongest:"opus",balanced:"sonnet",fast:"haiku"};function claudeRunOptions(r){const edits=r.permissions.includes("edit");const own2=edits?BUILD_TOOLS:r.permissions.includes("run")?RUN_TOOLS:[...new Set([...READ_ONLY,...r.permissions.includes("git-read")?GIT_READ:[]])];const access={allowedTools:[...own2,...windowTools(r.window)],disallowedTools:[...NO_GIT_WRITES,...protectedRules(r.protectedFiles??[])],permissionMode:edits?"acceptEdits":"default"};return{cwd:r.cwd,prompt:r.prompt,...r.sessionId?{sessionId:r.sessionId}:{},...r.resume?{resume:r.resume}:{},...access,...r.secretFiles?{secretFiles:r.secretFiles}:{},...r.privatePlaces?{privatePlaces:r.privatePlaces}:{},...r.schema?{schema:r.schema}:{},...r.instructions?{appendSystemPrompt:r.instructions}:{},...r.model||r.level?{model:r.model||LEVEL_MODEL[r.level]}:{},...r.effort?{effort:r.effort}:{},...r.addDirs?{addDirs:r.addDirs}:{},...r.sandbox?{sandbox:claudeSandbox(r.sandbox)}:{},...r.env?{env:r.env}:{},...r.caps?.turns?{maxTurns:r.caps.turns}:{},...r.caps?.spendUsd?{maxBudgetUsd:r.caps.spendUsd}:{},...r.window?{window:r.window}:{},...r.timeoutMs?{timeoutMs:r.timeoutMs}:{},...r.signal?{signal:r.signal}:{},...r.onTool?{onTool:r.onTool}:{},...r.onBlocked?{onBlocked:r.onBlocked}:{},...r.onText?{onText:r.onText}:{},...r.onPlan?{onPlan:r.onPlan}:{},...r.onActivity?{onActivity:r.onActivity}:{},...r.onSpawn?{onSpawn:r.onSpawn}:{}}}function acpAdapterPath(){try{const require2=createRequire2(import.meta.url);const pkg=require2.resolve("@agentclientprotocol/claude-agent-acp/package.json");const entry=path38.join(path38.dirname(pkg),"dist","index.js");return fs46.existsSync(entry)?entry:null}catch{return null}}var WINDOW_ENV={MCP_TOOL_TIMEOUT:"240000"};function claudeLaunch(opts,adapter,bin){const denied=deniedTools(opts);const options={...opts.allowedTools?.length?{allowedTools:opts.allowedTools}:{},...denied.length?{disallowedTools:denied}:{},allowDangerouslySkipPermissions:false,...opts.permissionMode?{permissionMode:opts.permissionMode}:{},...opts.schema?{outputFormat:{type:"json_schema",schema:opts.schema}}:{},...opts.appendSystemPrompt?{systemPrompt:{type:"preset",preset:"claude_code",append:opts.appendSystemPrompt}}:{},...opts.model?{model:opts.model}:{},...opts.effort?{effort:opts.effort}:{},...opts.addDirs?.length?{additionalDirectories:opts.addDirs}:{},...opts.sandbox?{sandbox:opts.sandbox}:{},...opts.maxTurns?{maxTurns:opts.maxTurns}:{},...opts.maxBudgetUsd?{maxBudgetUsd:opts.maxBudgetUsd}:{}};return{label:"Claude Code",command:process.execPath,args:[adapter],env:{...claudeEnv(),...opts.env,CLAUDE_CODE_EXECUTABLE:bin,...opts.model?{ANTHROPIC_MODEL:opts.model}:{},...opts.window?WINDOW_ENV:{}},meta:{claudeCode:{options}},mode:opts.permissionMode??"default",canResume:sessionExists,costSoFar,explain:plainError,toolName:update=>update._meta?.claudeCode?.toolName,structuredTool:"StructuredOutput",blocked:claudeBlocked}}async function runClaudeAcp(opts){const adapter=acpAdapterPath();if(!adapter)throw new AcpStartError("The Claude ACP adapter is not installed.");const bin=claudeBinary();if(!bin)return{ok:false,sessionId:opts.resume??opts.sessionId??"",text:"",error:"Claude Code is not installed. Install it and log in, then try again."};return runAcp(claudeLaunch(opts,adapter,bin),{...opts,...opts.logDir?{logDir:opts.logDir}:{}})}var SAID_LIMIT=/^(you've (hit|reached) your [\w' -]{0,40}limit\b|you're out of (extra )?usage|claude (ai )?usage limit reached|(5-hour|weekly|session) limit reached)/i;function withLimit(r){if(r.limit)return r;if(r.ok){const said=(r.text??"").trim();if(r.structured!==void 0||said.length>400||!SAID_LIMIT.test(said))return r;return withLimit({...r,ok:false,error:plainError(said),detail:said})}const found2=claudeLimit(r.detail??r.error??"");return found2?{...r,limit:{name:"Claude",...found2}}:r}function adapterVersion(){try{const require2=createRequire2(import.meta.url);return require2("@agentclientprotocol/claude-agent-acp/package.json").version??null}catch{return null}}var claudeTool={name:"claude",label:"Claude Code",plan:"Claude",canRun:true,supportsEffort:true,efforts:["xhigh","high","medium","low"],instructionFiles:["CLAUDE.md","CLAUDE.local.md",".claude/"],privateFiles:home2=>claudePrivateFiles(home2),secretEnv:CLAUDE_ENV,modelFor:level=>({strongest:"Opus",balanced:"Sonnet",fast:"Haiku"})[level],ready(){const bin=claudeBinary();return Boolean(bin&&toolVersion(bin))},version(){const bin=claudeBinary();const v=bin?toolVersion(bin):null;const acp=adapterVersion();return v?`${v}${acp?`, ACP adapter ${acp}`:""}`:null},sandbox:claudeSandboxSupport,caps:{turns:true,spend:true},blocked:claudeBlocked,async run(r){const opts=claudeRunOptions(r);const done=res=>withCap(withLimit(res),r.caps);if(r.direct||!acpAdapterPath())return done(await runClaude(opts));try{return done(await runClaudeAcp({...opts,...r.logDir?{logDir:r.logDir}:{}}))}catch(err2){if(!(err2 instanceof AcpStartError))throw err2;if(r.signal?.aborted)return{ok:false,sessionId:opts.resume??opts.sessionId??"",text:"",error:"You stopped the squad."};r.onFallback?.(err2.message);return done(await runClaude(opts))}},ownWords,passing,describe:describeTool,billing:()=>claudeBilling(),resumeCommand:claudeResume};import{execSync}from"child_process";import path39 from"path";var codexTool={name:"codex",label:"Codex",plan:"ChatGPT",canRun:false,supportsEffort:false,instructionFiles:["AGENTS.md"],privateFiles:home2=>["auth.json","sessions","history.jsonl"].map(f=>path39.join(home2,".codex",f)),secretEnv:["OPENAI_*","CODEX_*"],modelFor:()=>"",ready(){try{execSync(process.platform==="win32"?"where codex":"which codex",{stdio:"ignore"});return true}catch{return false}},run:()=>Promise.reject(new Error("squad-kit cannot run Codex yet.")),describe:()=>null};import{execFile as execFile2}from"child_process";import fs47 from"fs";import path40 from"path";import{pathToFileURL}from"url";import os6 from"os";import net from"net";var PRIVATE_V4=[["0.0.0.0",8],["10.0.0.0",8],["100.64.0.0",10],["127.0.0.0",8],["169.254.0.0",16],["172.16.0.0",12],["192.168.0.0",16],["198.18.0.0",15]];function v4Number(ip){return ip.split(".").reduce((n,part)=>n*256+Number(part),0)}function privateV4(ip){const n=v4Number(ip);return PRIVATE_V4.some(([net4,bits])=>Math.floor(n/2**(32-bits))===Math.floor(v4Number(net4)/2**(32-bits)))}function v6Words(ip){let s=ip.toLowerCase().replace(/%.*$/,"");const tail2=/(\d+\.\d+\.\d+\.\d+)$/.exec(s);if(tail2){const n=v4Number(tail2[1]);s=`${s.slice(0,-tail2[1].length)}${(n>>>16).toString(16)}:${(n&65535).toString(16)}`}const[head,rest]=s.split("::");const a=head?head.split(":"):[];const b=rest!==void 0&&rest!==""?rest.split(":"):[];const fill2=rest===void 0?0:8-a.length-b.length;if(fill2<0)return null;const words=[...a,...Array(fill2).fill("0"),...b].map(w=>parseInt(w||"0",16));return words.length===8&&words.every(w=>Number.isInteger(w)&&w>=0&&w<=65535)?words:null}function privateV6(ip){const w=v6Words(ip);if(!w)return true;const v4=(hi,lo)=>`${hi>>>8}.${hi&255}.${lo>>>8}.${lo&255}`;if(w.slice(0,5).every(x=>x===0)&&(w[5]===0||w[5]===65535))return w[5]===0&&w[6]===0?w[7]<=1:privateV4(v4(w[6],w[7]));if(w[0]===100&&w[1]===65435&&w.slice(2,6).every(x=>x===0))return privateV4(v4(w[6],w[7]));if(w[0]===8194)return privateV4(v4(w[1],w[2]));return(w[0]&65024)===64512||(w[0]&65472)===65152}function isLocalHost(host){const h=host.toLowerCase().replace(/^\[|\]$/g,"").replace(/\.$/,"");if(h==="localhost"||h.endsWith(".localhost")||h.endsWith(".local"))return true;const kind=net.isIP(h);if(kind===4)return privateV4(h);if(kind===6)return privateV6(h);return false}function isAppAddress(url,origin){let u;let o;try{u=new URL(url);o=new URL(origin)}catch{return false}if(u.origin===o.origin)return true;const loop=x=>["localhost","127.0.0.1","[::1]"].includes(x.hostname);return loop(u)&&loop(o)&&u.protocol===o.protocol&&u.port===o.port}function blockedFrom(url,origin,also=[]){let u;try{u=new URL(url)}catch{return false}if(!/^(https?|wss?):$/.test(u.protocol))return false;return!isAppAddress(url,origin)&&!also.some(a=>isAppAddress(url,a))&&isLocalHost(u.hostname)}var StepFailure=class extends Error{constructor(kind,message,address){super(message);this.kind=kind;this.address=address}kind;address};var BrowserSetupError=class extends Error{constructor(message,detail){super(message);this.detail=detail}detail};var SETTING_UP="Setting up squad-kit's screenshot browser. This happens only once.";var PLAYWRIGHT_VERSION="1.64.1";function playwrightDir(){return path40.join(squadHome(),"tools","playwright")}var run=(file3,args,o)=>new Promise(resolve2=>{const env=Object.fromEntries(Object.entries(process.env).filter(([k])=>!/^npm_/i.test(k)));execFile2(file3,args,{cwd:o.cwd,timeout:o.timeoutMs,maxBuffer:8*1024*1024,shell:process.platform==="win32",env:{...env,npm_config_update_notifier:"false",ELECTRON_RUN_AS_NODE:"1",PLAYWRIGHT_SKIP_BROWSER_GC:"1"}},(err2,stdout,stderr)=>resolve2({ok:!err2,output:`${stdout}
115
115
  ${stderr}
116
116
  ${err2?err2.message:""}`.trim().slice(-4e3)}))});var flights=new Map;function singleFlight(key,job){const going=flights.get(key);if(going)return going;const p=job().finally(()=>flights.delete(key));flights.set(key,p);return p}function installedVersion(dir){try{return JSON.parse(fs47.readFileSync(path40.join(dir,"node_modules","playwright-core","package.json"),"utf8")).version??null}catch{return null}}async function readyPlaywright(o={}){const dir=o.dir??playwrightDir();const entry=path40.join(dir,"node_modules","playwright-core","index.mjs");if(installedVersion(dir)===PLAYWRIGHT_VERSION&&fs47.existsSync(entry))return entry;await singleFlight(`install:${dir}`,async()=>{if(installedVersion(dir)===PLAYWRIGHT_VERSION)return;o.onLine?.(SETTING_UP);const part=`${dir}.part-${process.pid}-${Date.now()}`;fs47.mkdirSync(part,{recursive:true});fs47.writeFileSync(path40.join(part,"package.json"),`${JSON.stringify({name:"squad-kit-screenshots",private:true},null,2)}
117
117
  `);const args=["install",`playwright-core@${PLAYWRIGHT_VERSION}`,"--save-exact","--no-audit","--no-fund","--ignore-scripts","--prefer-offline","--loglevel=error"];const r=await(o.run??run)(npmCommand(),args,{cwd:part,timeoutMs:5*6e4});if(!r.ok||installedVersion(part)!==PLAYWRIGHT_VERSION){fs47.rmSync(part,{recursive:true,force:true});throw new BrowserSetupError(`Installing Playwright ${PLAYWRIGHT_VERSION} did not work`,`npm ${args.join(" ")} in ${part}: ${r.output}`)}fs47.rmSync(dir,{recursive:true,force:true});fs47.mkdirSync(path40.dirname(dir),{recursive:true});fs47.renameSync(part,dir)});return entry}function npmCommand(){const beside=path40.join(path40.dirname(process.execPath),process.platform==="win32"?"npm.cmd":"npm");if(fs47.existsSync(beside))return beside;return process.platform==="win32"?"npm.cmd":"npm"}function installBrowser(dir,runner){const cli=path40.join(dir,"node_modules","playwright-core","cli.js");return runner(process.execPath,[cli,"install","--only-shell","--no-remove","chromium"],{cwd:dir,timeoutMs:10*6e4})}function browsersDir(dir){const set2=process.env.PLAYWRIGHT_BROWSERS_PATH;if(set2==="0")return path40.join(dir,"node_modules","playwright-core",".local-browsers");if(set2)return path40.resolve(set2);const home2=os6.homedir();const cache4=process.platform==="darwin"?path40.join(home2,"Library","Caches"):process.platform==="win32"?process.env.LOCALAPPDATA??path40.join(home2,"AppData","Local"):process.env.XDG_CACHE_HOME??path40.join(home2,".cache");return path40.join(cache4,"ms-playwright")}function browserThere(dir){try{const json2=JSON.parse(fs47.readFileSync(path40.join(dir,"node_modules","playwright-core","browsers.json"),"utf8"));const shell=json2.browsers?.find(b=>b.name==="chromium-headless-shell");if(!shell||shell.revisionOverrides)return null;return fs47.existsSync(path40.join(browsersDir(dir),`chromium_headless_shell-${shell.revision}`,"INSTALLATION_COMPLETE"))}catch{return null}}function ownBrowserFiles(dir=playwrightDir()){try{const json2=JSON.parse(fs47.readFileSync(path40.join(dir,"node_modules","playwright-core","browsers.json"),"utf8"));const shell=json2.browsers?.find(b=>b.name==="chromium-headless-shell");return shell?[path40.join(browsersDir(dir),`chromium_headless_shell-${shell.revision}`)]:[]}catch{return[]}}var prepared=new Set;async function preparePlaywright(o={}){const dir=o.dir??playwrightDir();if(prepared.has(dir))return;let said=false;const once=text3=>{if(said)return;said=true;o.onLine?.(text3)};await readyPlaywright({...o,onLine:once});const there=browserThere(dir);if(!there){if(there===false)once(SETTING_UP);const got=await singleFlight(`browser:${dir}`,()=>installBrowser(dir,o.run??run));if(!got.ok)throw new BrowserSetupError("Getting Chromium did not work",got.output)}prepared.add(dir)}var sleep=ms2=>new Promise(r=>setTimeout(r,ms2));function originOf(url){try{return new URL(url).origin}catch{return null}}var FIELD_ROLES=["textbox","combobox","listbox","searchbox","spinbutton","checkbox","radio","switch","slider"];function locate(page,t,exact){if(t.role)return page.getByRole(t.role,t.name?{name:t.name,exact}:{});if(t.label&&exact)return FIELD_ROLES.reduce((loc,role)=>loc.or(page.getByRole(role,{name:t.label,exact:true})),page.getByLabel(t.label,{exact:true}));if(t.label)return page.getByLabel(t.label,{exact});if(t.placeholder)return page.getByPlaceholder(t.placeholder,{exact});if(t.text)return page.getByText(t.text,{exact});if(t.testId)return page.getByTestId(t.testId);return page.locator(t.css??"")}async function find(page,t,ms2){const until=Date.now()+ms2;const ways=[locate(page,t,true),locate(page,t,false)];for(;;){for(const loc of ways){const n=await loc.count().catch(()=>0);if(n===1)return loc;if(n>1){const shown2=loc.filter({visible:true});return await shown2.count().catch(()=>0)?shown2.first():loc.first()}}if(Date.now()>=until)throw new StepFailure("missing",`Nothing on the page matches ${JSON.stringify(t)}`);await sleep(200)}}function failure2(err2){if(err2 instanceof StepFailure)return err2;const msg=err2?.message??String(err2);if(/has been closed|Target closed/i.test(msg))return new StepFailure("closed",msg);if(/did not find some options|not find option/i.test(msg))return new StepFailure("option",msg);if(/element is not enabled|element is disabled/i.test(msg))return new StepFailure("disabled",msg);if(/element is not visible|element is outside of the viewport/i.test(msg))return new StepFailure("hidden",msg);if(/ERR_BLOCKED_BY_CLIENT/i.test(msg))return new StepFailure("blocked",msg);if(err2?.name==="TimeoutError"||/Timeout \d+ms exceeded/i.test(msg))return new StepFailure("timeout",msg);return new StepFailure("other",msg)}function wrapPage(page,context,origin,stopped){let blocked=null;page.on("dialog",d=>void d.accept().catch(()=>void 0));page.on("filechooser",()=>void 0);page.on("framenavigated",f=>{if(f.parentFrame()||f.url()==="about:blank"||originOf(f.url())===origin)return;blocked=f.url().startsWith("chrome-error://")&&stopped.at?stopped.at:f.url();stopped.at=null;void page.goto("about:blank",{timeout:5e3,waitUntil:"load"}).catch(()=>void 0)});const outside=()=>{if(!blocked)return;const to=blocked;blocked=null;throw new StepFailure("blocked",`The page tried to open ${to}`,to)};const afterAction=async ms2=>{await sleep(150);await page.waitForLoadState("load",{timeout:Math.min(ms2,5e3)}).catch(()=>void 0);outside()};return{async goto(url,ms2){blocked=null;try{const res=await page.goto(url,{timeout:ms2,waitUntil:"load"});outside();return res?.status()??null}catch(err2){outside();throw failure2(err2)}},async act(step2,ms2){blocked=null;const until=Date.now()+ms2;const left=()=>Math.max(1e3,until-Date.now());try{const loc=step2.target?await find(page,step2.target,ms2):null;switch(step2.action){case"click":await loc.click({timeout:left()});break;case"fill":await loc.fill(step2.value??"",{timeout:left()});break;case"select":await loc.selectOption(step2.value??"",{timeout:left()});break;case"check":await loc.check({timeout:left()});break;case"uncheck":await loc.uncheck({timeout:left()});break;case"hover":await loc.hover({timeout:left()});break;case"press":if(loc)await loc.press(step2.key??"Enter",{timeout:left()});else await page.keyboard.press(step2.key??"Enter");break;case"wait":if(loc)await loc.waitFor({state:"visible",timeout:left()});else await page.waitForTimeout(Math.min(step2.ms??0,5e3));break;default:throw new StepFailure("other",`${step2.action} is not a step the browser knows`)}await afterAction(left())}catch(err2){outside();throw failure2(err2)}},async settle(ms2){await page.waitForLoadState("load",{timeout:ms2}).catch(()=>void 0);await page.waitForLoadState("networkidle",{timeout:Math.min(ms2,3e3)}).catch(()=>void 0);await page.evaluate("document.fonts ? document.fonts.ready.then(() => true) : true").catch(()=>void 0);await sleep(150)},async snapshot(){return page.locator("body").ariaSnapshot({timeout:5e3})},url(){return page.url()},async screenshot(){return page.screenshot({animations:"disabled",caret:"hide",timeout:15e3})},async close(){await context.close().catch(()=>void 0)}}}function wrapBrowser(browser){return{async page(o){const context=await browser.newContext({viewport:{width:o.width,height:o.height},deviceScaleFactor:1,...o.phone?{isMobile:true,hasTouch:true}:{},acceptDownloads:false,serviceWorkers:"block",ignoreHTTPSErrors:true});const stopped={at:null};await context.route("**/*",async route=>{const req=route.request();const url=req.url();let top=false;try{top=req.isNavigationRequest()&&!req.frame().parentFrame()}catch{top=false}if(top&&originOf(url)!==o.origin){stopped.at=url;return route.abort("blockedbyclient").catch(()=>void 0)}if(blockedFrom(url,o.origin,o.also))return route.abort("blockedbyclient").catch(()=>void 0);return route.continue().catch(()=>void 0)});const page=await context.newPage();context.on("page",p=>{if(p!==page)void p.close().catch(()=>void 0)});return wrapPage(page,context,o.origin,stopped)},async close(){await browser.close().catch(()=>void 0)}}}var MISSING_BROWSER=/Executable doesn't exist|playwright install|browserType\.launch: .*not found/i;async function launchPlaywright(o={}){const dir=o.dir??playwrightDir();await preparePlaywright(o);const entry=await readyPlaywright(o);let pw;try{pw=await import(pathToFileURL(entry).href)}catch(err2){throw new BrowserSetupError("Playwright did not load",`${entry}: ${err2.message}`)}const start=()=>pw.chromium.launch({headless:true,timeout:6e4});try{return wrapBrowser(await start())}catch(err2){const msg=err2.message??String(err2);if(!MISSING_BROWSER.test(msg))throw new BrowserSetupError("Chromium did not start",msg)}o.onLine?.(SETTING_UP);const got=await singleFlight(`browser:${dir}`,()=>installBrowser(dir,o.run??run));if(!got.ok)throw new BrowserSetupError("Getting Chromium did not work",got.output);try{return wrapBrowser(await start())}catch(err2){throw new BrowserSetupError("Chromium did not start",err2.message??String(err2))}}var playwrightBrowser={name:"playwright",label:"Playwright",launch:(o={})=>launchPlaywright(o.onLine?{onLine:o.onLine}:{}),prepare:(o={})=>preparePlaywright(o.onLine?{onLine:o.onLine}:{}),ownFiles:()=>ownBrowserFiles()};var TRACKERS=[azureTracker];var CODE_HOSTS=[azureCodeHost];var SPEECH=[munsitSpeech];var AI_TOOLS=[claudeTool,codexTool];var BROWSERS=[playwrightBrowser];function rootOf(bp){return bp.root}function trackerModule(name){return TRACKERS.find(m=>m.name===name)}function trackerFor(bp){const root=rootOf(bp);const configFile=buildPaths(root).configFile;if(!fs48.existsSync(configFile))return null;return trackerModule(loadConfig(configFile).tracker.type)?.open(root)??null}function repoHostFor(bp,remoteUrl){const root=rootOf(bp);for(const m of CODE_HOSTS){const host=m.repoFor(root,remoteUrl);if(host)return host}return null}function guessTracker(remoteUrls){for(const url of remoteUrls){for(const m of TRACKERS){const values=m.guess(url);if(values)return{module:m,values}}}return null}function speechFor(bp,opts={}){const root=rootOf(bp);for(const m of SPEECH){const s=m.open(root,opts);if(s)return s}return null}function speechModule(){return SPEECH[0]}function shotBrowser(){return BROWSERS[0]}function aiTool(name){return AI_TOOLS.find(t=>t.name===name)}function describeToolEvent(e,cwd){for(const t of AI_TOOLS){const line3=t.describe(e,cwd);if(line3)return line3}return null}import fs49 from"fs";import os7 from"os";import path41 from"path";var PACKAGE_HOSTS=["registry.npmjs.org","registry.yarnpkg.com","repo.yarnpkg.com","pypi.org","files.pythonhosted.org","repo.maven.apache.org","repo1.maven.org","plugins.gradle.org","services.gradle.org","downloads.gradle.org","api.nuget.org","globalcdn.nuget.org","rubygems.org","index.rubygems.org","crates.io","index.crates.io","static.crates.io","proxy.golang.org","sum.golang.org","github.com","codeload.github.com","objects.githubusercontent.com","release-assets.githubusercontent.com","cdn.playwright.dev","playwright.download.prss.microsoft.com","playwright.azureedge.net"];var PACKAGE_CACHES=[".npm/_cacache",".yarn/berry/cache","Library/Caches/Yarn",".cache/yarn","Library/pnpm/store",".local/share/pnpm/store","Library/Caches/pnpm",".cache/pnpm",".cargo/registry",".cargo/git","go/pkg/mod"];var PRIVATE=[".ssh",".aws",".gnupg",".config/gh",".azure",".kube",".docker/config.json",".netrc",".git-credentials"];var PUSH_ADDRESSES=["/",".","file://","https://","http://","ssh://","git://","git@"];function noPushEnv(env=process.env){const from=Number(env.GIT_CONFIG_COUNT)||0;const out17={GIT_CONFIG_COUNT:String(from+PUSH_ADDRESSES.length)};PUSH_ADDRESSES.forEach((address,i)=>{out17[`GIT_CONFIG_KEY_${from+i}`]="url.squad-kit-no-push://.pushInsteadOf";out17[`GIT_CONFIG_VALUE_${from+i}`]=address});return out17}function ticketCacheDir(bp,id2){return path41.join(bp.localDir,"caches",id2.toLowerCase())}function projectCacheDir(bp){return path41.join(bp.localDir,"caches","project")}function dropTicketCache(bp,id2){fs49.rmSync(ticketCacheDir(bp,id2),{recursive:true,force:true})}function packageCacheEnv(dir,env=process.env){const at3=(...p)=>path41.join(dir,...p);const gems=[env.GEM_HOME,...(env.GEM_PATH??"").split(path41.delimiter)].filter(p=>Boolean(p?.trim()));return{NUGET_PACKAGES:at3("nuget","packages"),NUGET_HTTP_CACHE_PATH:at3("nuget","http-cache"),NUGET_PLUGINS_CACHE_PATH:at3("nuget","plugins-cache"),MAVEN_OPTS:[env.MAVEN_OPTS?.trim(),`-Dmaven.repo.local=${at3("maven","repository")}`].filter(Boolean).join(" "),GRADLE_USER_HOME:at3("gradle"),GEM_HOME:at3("ruby","gems"),GEM_PATH:`${[...new Set(gems)].join(path41.delimiter)}${path41.delimiter}`,GEM_SPEC_CACHE:at3("ruby","specs"),BUNDLE_PATH:at3("ruby","bundle"),BUNDLE_USER_CACHE:at3("ruby","bundle-cache")}}function gitPlaces(cwd){const link=path41.join(cwd,".git");try{const own2=path41.resolve(cwd,/^gitdir: (.*)$/m.exec(fs49.readFileSync(link,"utf8"))[1]);return[link,own2,path41.resolve(own2,fs49.readFileSync(path41.join(own2,"commondir"),"utf8").trim())]}catch{return[link]}}function withReal(dirs){const out17=new Set;for(const d of dirs){out17.add(d);try{out17.add(fs49.realpathSync(d))}catch{}}return[...out17]}function sandboxAsk(o){const home2=o.home??os7.homedir();const temp=withReal([os7.tmpdir(),...process.platform==="win32"?[]:["/tmp"]]);const caches=PACKAGE_CACHES.map(c=>path41.join(home2,c)).filter(c=>fs49.existsSync(c));const env=[".env",".env.*","**/.env","**/.env.*"].map(p=>`${o.root}/${p}`);const own2=o.cache?withReal([o.cache]):[];return{write:[...new Set([...withReal([o.proof]),...own2,...temp,...caches])],noRead:[...new Set([...PRIVATE.map(p=>path41.join(home2,p)),...withReal([squadHome()]),...o.secrets,...env])],read:[...new Set(withReal([o.cwd,o.proof,...o.cache?[o.cache]:[],...o.read]))],noWrite:[...new Set(withReal([...gitPlaces(o.cwd),...copyDirsIn(o.cwd).flatMap(gitPlaces)]))],hosts:[...new Set([...PACKAGE_HOSTS,...o.hosts??[]])]}}function sandboxOf(tool5){return tool5.sandbox?.()??{available:false,why:`${tool5.label} has no sandbox squad-kit can use`}}function sandboxStatus(){return AI_TOOLS.filter(t=>t.canRun).map(t=>({tool:t.label,...sandboxOf(t)}))}function guardNote(platform=process.platform,tools=sandboxStatus()){if(platform!=="win32"||tools.some(t=>t.available))return null;return{title:"Windows: less protected",text:"On Windows, squad-kit does not guard the agents' own commands, or the project's own tests and app, so they can reach other files on this computer. Use it on projects you trust."}}function shown(file3,home2){return file3===home2?"~":file3.startsWith(`${home2}${path41.sep}`)?`~${file3.slice(home2.length)}`:file3}function blockedLine(b,home2=os7.homedir()){if(b.kind==="host")return`Reaching ${b.target} was blocked: not a package registry. Allow it on the Harness page, under Safety.`;const file3=shown(b.target,home2);if(b.kind==="write")return`Writing ${file3} was blocked: the squad writes only in the ticket's copy, the proof folder and temp folders.`;const base2=path41.basename(b.target);const why=/^\.env(\.|$)/.test(base2)?"the project's .env files stay private":b.target.startsWith(squadHome())?"squad-kit's own files stay private":"keys and sign-ins stay private";return`Reading ${file3} was blocked: ${why}.`}function blockedIn(output){if(!output)return[];for(const t of AI_TOOLS){const found2=t.blocked?.(output)??[];if(found2.length)return found2}return[]}function parseHosts(list2){const hosts=[];const bad=[];for(const raw of list2.flatMap(x=>String(x).split(/[\s,]+/))){const s=raw.trim().toLowerCase().replace(/^[a-z][a-z0-9+.-]*:\/\//,"").replace(/[/?#].*$/,"").replace(/:\d+$/,"");if(!raw.trim())continue;if(/^(\*\.)?([a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(s)&&s!=="*")hosts.push(s);else bad.push(raw.trim())}return{hosts:[...new Set(hosts)],bad}}var SEATBELT="/usr/bin/sandbox-exec";function writableCaches(home2){return PACKAGE_CACHES.map(c=>path42.join(home2,c))}function browserProfiles(home2,platform){if(platform==="darwin"){const support3=path42.join(home2,"Library","Application Support");return["Google/Chrome","Chromium","Firefox","Microsoft Edge","BraveSoftware","Arc","Vivaldi","com.operasoftware.Opera"].map(b=>path42.join(support3,b)).concat(path42.join(home2,"Library","Safari"),path42.join(home2,"Library","Application Support","Firefox"))}return[".config/google-chrome",".config/chromium",".config/BraveSoftware",".config/microsoft-edge",".mozilla"].map(b=>path42.join(home2,b))}var FINCH_ON_MAC="/Applications/Finch/lima/data";function runtimeSockets(home2,platform,env=process.env){const inHome=[".docker",".colima",".config/colima",".lima",".rd",".orbstack",".finch",".local/share/containers/podman"];if(platform==="darwin"){for(const app of["Containers/com.docker.docker","Group Containers/group.com.docker","Group Containers/HUAUUMT7XC.dev.orbstack","Application Support/rancher-desktop"])inHome.push(path42.join("Library",app))}const inRun=["docker.sock","podman.sock","podman","finch.sock","containerd"];const uid=process.getuid?.();const own2=[env.XDG_RUNTIME_DIR,...platform==="linux"&&uid!==void 0?[`/run/user/${uid}`]:[]].filter(d=>Boolean(d));const named=/^unix:\/\/(\/.+)$/.exec(env.DOCKER_HOST?.trim()??"")?.[1];return[...inHome.map(p=>path42.join(home2,p)),...["/var/run","/run",...own2].flatMap(d=>inRun.map(p=>path42.join(d,p))),...[os8.tmpdir(),"/tmp"].map(d=>path42.join(d,"podman")),...platform==="darwin"?[path42.join(FINCH_ON_MAC,"finch/sock")]:[],...named?[named]:[]]}var RUNTIME_KEYS=[".lima/_config",".colima/_lima/_config",".orbstack/ssh",".local/share/containers/podman/machine/machine",".finch/lima/data/_config",".local/share/rancher-desktop/lima/_config","Library/Application Support/rancher-desktop/lima/_config"];var RUNTIME_PORTS=[2375,2376];function tokenFiles(home2){const out17=[path42.join(home2,".pypirc")];const npmrc=path42.join(home2,".npmrc");try{if(/(_authToken|_auth|_password)\s*=/.test(fs50.readFileSync(npmrc,"utf8")))out17.push(npmrc)}catch{}return out17}function aiToolPrivate(home2){return AI_TOOLS.flatMap(t=>t.privateFiles?.(home2)??[])}function squadOwnFiles(){return BROWSERS.flatMap(b=>b.ownFiles?.()??[])}function ticketGuard(bp,o){const home2=o.home??os8.homedir();const cache4=ticketCacheDir(bp,o.id);return{...buildGuard({cwd:o.cwd,root:bp.root,cache:cache4,extraWrite:[bp.proofDir,cache4],extraRead:[cache4,...o.read??[]],home:home2,platform:o.platform??process.platform,...o.allow?{allow:o.allow}:{}}),noPush:true}}function projectGuardFor(bp,o){const home2=o.home??os8.homedir();const cache4=projectCacheDir(bp);return buildGuard({cwd:o.cwd,root:bp.root,cache:cache4,extraWrite:[bp.proofDir,cache4],extraRead:[cache4,...o.read??[]],home:home2,platform:o.platform??process.platform,...o.allow?{allow:o.allow}:{}})}function realPlace(p){const rest=[];for(let at3=p;;at3=path42.dirname(at3)){try{return path42.join(fs50.realpathSync(at3),...rest)}catch{if(at3===path42.dirname(at3))return p;rest.unshift(path42.basename(at3))}}}function withReal2(dirs){const out17=new Set;for(const d of dirs){if(!d)continue;out17.add(d);out17.add(realPlace(d))}return[...out17]}function allowPath(p,home2=os8.homedir()){return path42.resolve(p.trim().replace(/^~(?=$|\/)/,home2))}function sharedPath(p,home2=os8.homedir()){const full=allowPath(p,home2);return full===home2||full.startsWith(`${home2}${path42.sep}`)?`~${full.slice(home2.length).split(path42.sep).join("/")}`:full}function privatePlaces(home2,platform){return[...PRIVATE.map(p=>path42.join(home2,p)),path42.join(home2,".config","gcloud"),path42.join(home2,".docker"),...RUNTIME_KEYS.map(p=>path42.join(home2,p)),...platform==="darwin"?[path42.join(FINCH_ON_MAC,"_config")]:[],path42.join(home2,"Library","Keychains"),...browserProfiles(home2,platform),...tokenFiles(home2),...aiToolPrivate(home2)]}function agentPrivatePlaces(home2=os8.homedir(),platform=process.platform){const own2=squadHome();return[...privatePlaces(home2,platform),...["secrets.yaml","app.json","console.json"].map(f=>path42.join(own2,f)),...["secrets.yaml","console.json"].map(f=>path42.join(own2,"projects","*",f))]}function buildGuard(o){const home2=o.home;const temp=withReal2([os8.tmpdir(),...o.platform==="win32"?[]:["/tmp","/private/tmp","/private/var/folders"]]);const noRead=[...privatePlaces(home2,o.platform),...withReal2([squadHome()])];const real2=p=>{try{return fs50.realpathSync(p)}catch{return p}};return{cwd:real2(o.cwd),root:o.root,write:[...new Set(withReal2([...o.extraWrite??[],...temp,...writableCaches(home2)]))],read:[...new Set(withReal2([...o.extraRead??[]]))],noRead:[...new Set(noRead)],noWrite:[...new Set(withReal2([...squadOwnFiles(),...gitPlaces(real2(o.cwd))]))],noConnect:[...new Set(withReal2(runtimeSockets(home2,o.platform)))],...o.cache?{cache:real2(o.cache)}:{},...o.allow?{allow:{...o.allow.allowRead?{allowRead:withReal2(o.allow.allowRead.map(p=>allowPath(p,home2)))}:{},...o.allow.allowWrite?{allowWrite:withReal2(o.allow.allowWrite.map(p=>allowPath(p,home2)))}:{}}}:{}}}function sb(s){return`"${s.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`}function sbRegex(s){return`#"${s.replace(/"/g,'\\"')}"`}function ancestorsOf(paths){const out17=new Set;for(const p of paths){let dir=path42.dirname(p);while(dir&&dir!==path42.dirname(dir)){out17.add(dir);dir=path42.dirname(dir)}}return[...out17]}function envRegexFor(root){const base2=root.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/\/$/,"");return`^${base2}/(.*/)?\\.env(\\.[^/]*)?$`}function profileText(spec){const lines3=["(version 1)","(allow default)"];const allowRead=spec.allow?.allowRead??[];const allowWrite=spec.allow?.allowWrite??[];const reAllow=[spec.cwd,...spec.read,...allowRead,...spec.hookGit?[spec.hookGit]:[]];const envRule=`(deny file-read* (regex ${sbRegex(envRegexFor(realPlace(spec.root)))}))`;lines3.push(";; reads: hide keys, sign-ins and squad-kit, then allow the copy back");for(const d of spec.noRead)lines3.push(`(deny file-read* (subpath ${sb(d)}))`);lines3.push(envRule);const writable=[spec.cwd,...spec.write,...allowWrite];for(const a of ancestorsOf([...reAllow,...writable]))lines3.push(`(allow file-read-metadata (literal ${sb(a)}))`);for(const r of reAllow)lines3.push(`(allow file-read* (subpath ${sb(r)}))`);const realRoot=realPlace(spec.root);if(!spec.ownEnv&&(spec.cwd===realRoot||spec.cwd.startsWith(`${realRoot}/`))){lines3.push(envRule);for(const a of allowRead)lines3.push(`(allow file-read* (subpath ${sb(a)}))`)}lines3.push(";; writes: only the copy and the caches");lines3.push('(deny file-write* (subpath "/"))');for(const w of writable)lines3.push(`(allow file-write* (subpath ${sb(w)}))`);for(const dev of["/dev/null","/dev/zero","/dev/dtracehelper","/dev/tty","/dev/random","/dev/urandom","/dev/fd","/dev/stdout","/dev/stderr"])lines3.push(`(allow file-write* (subpath ${sb(dev)}))`);for(const s of spec.noConnect)lines3.push(`(deny file-write* (subpath ${sb(s)}))`);for(const a of allowWrite)lines3.push(`(allow file-write* (subpath ${sb(a)}))`);for(const a of[spec.cwd,...ancestorsOf([spec.cwd,...spec.noWrite])])lines3.push(`(deny file-write-unlink (literal ${sb(a)}))`,`(deny file-write-create (literal ${sb(a)}))`);for(const n of spec.noWrite)lines3.push(`(deny file-write* (subpath ${sb(n)}))`);if(spec.hookGit)lines3.push(`(allow file-write* (subpath ${sb(spec.hookGit)}))`);lines3.push(";; connections: nothing in a hidden place, and no container runtime, unless an engineer allowed its folder");for(const d of spec.noRead)lines3.push(`(deny network-outbound (subpath ${sb(d)}))`);for(const a of[spec.cwd,...spec.read,...spec.write,...spec.hookGit?[spec.hookGit]:[]])lines3.push(`(allow network-outbound (subpath ${sb(a)}))`);for(const s of spec.noConnect)lines3.push(`(deny network-outbound (subpath ${sb(s)}))`);for(const port of RUNTIME_PORTS)lines3.push(`(deny network-outbound (remote tcp "localhost:${port}"))`);for(const a of[...allowRead,...allowWrite])lines3.push(`(allow network-outbound (subpath ${sb(a)}))`);if(spec.openSocket)lines3.push(`(allow network-outbound (literal ${sb(spec.openSocket)}))`);lines3.push("(deny signal)","(allow signal (target same-sandbox))");lines3.push("(deny job-creation)");return`${lines3.join("\n")}
@@ -145,7 +145,7 @@ New files in ${named}/:
145
145
  ${added2.map(f=>`- ${f}`).join("\n")||"- none"}
146
146
  `)}var HOOK_MS=5*6e4;var SHORT_HOOK_MS=6e4;var CommitStopped=class extends Error{owed=false;constructor(){super("Stopped while the project's own check ran.");this.name="CommitStopped"}};var HookRefused=class extends Error{constructor(said,timedOut,limitMs){super(`The project's commit hook ${timedOut?`did not finish in ${Math.round(limitMs/6e4)} minutes`:"said no"}: ${said}`);this.said=said;this.timedOut=timedOut;this.limitMs=limitMs;this.name="HookRefused"}said;timedOut;limitMs;owed=false};function hookFolder(dir){return runGit(dir,[...PROGRAMS_OFF,"rev-parse","--git-path","hooks"],{allowFail:true})}function hasHook(dir,where,name){try{fs52.accessSync(path44.resolve(dir,where,name),fs52.constants.X_OK);return true}catch{return false}}function commitHooks(dir){const where=hookFolder(dir);return where?["pre-commit","prepare-commit-msg","commit-msg"].filter(name=>hasHook(dir,where,name)):[]}function conventionalWords(type,text3,id2){const tail2=id2?` (${id2})`:"";const room=Math.max(10,72-type.length-2-tail2.length);const subject=text3.replace(/\s+/g," ").trim().replace(/[.!?]+$/,"");return`${type}: ${subject.charAt(0).toLowerCase()}${subject.slice(1,room).trimEnd()}${tail2}`}var COMMIT_RULES=["commitlint.config.js","commitlint.config.cjs","commitlint.config.mjs","commitlint.config.ts",".commitlintrc",".commitlintrc.json",".commitlintrc.js",".commitlintrc.cjs",".commitlintrc.yaml",".commitlintrc.yml"];function keepsCommitRules(dir){if(COMMIT_RULES.some(f=>fs52.existsSync(path44.join(dir,f))))return true;try{const pkg=JSON.parse(fs52.readFileSync(path44.join(dir,"package.json"),"utf8"));return Boolean(pkg.commitlint||pkg.devDependencies?.["@commitlint/cli"]||pkg.dependencies?.["@commitlint/cli"])}catch{return false}}function commitArgs(dir,message){return[...authorArgs(dir),"commit","-m",message]}function quoted2(s){return`'${s.replace(/'/g,`'\\''`)}'`}async function stagedHookFiles(dir){const names2=hookFiles(dir);const staged=new Map;await gitRecords(dir,["ls-files","-s","-z"],"\0",line3=>{const entry=/^(\d+) ([0-9a-f]+) \d+\t(.*)$/s.exec(line3);if(entry&&isHookFile(entry[3],names2))staged.set(entry[3],`${entry[1]},${entry[2]}`)});return staged}async function keepHookFiles(dir,before){const now2=await stagedHookFiles(dir);for(const rel of new Set([...before.keys(),...now2.keys()])){const was=before.get(rel);if(was===now2.get(rel))continue;if(was){git(dir,["update-index","--add","--replace","--cacheinfo",`${was},${rel}`]);git(dir,["checkout-index","-f","--",rel])}else{git(dir,["update-index","--force-remove","--",rel]);removeInCopy(dir,rel)}}}function removeInCopy(dir,rel){const from=path44.join(dir,path44.dirname(rel));try{if(process.platform==="win32")fs52.rmSync(path44.join(dir,rel),{force:true});else execFileSync8("/bin/sh",["-c",'cd -P -- "$2" && case "$(pwd -P)/" in "$1"/*) rm -f -- "$3" ;; esac',"sh",realOf(dir),from,path44.basename(rel)],{stdio:"ignore"})}catch{}}var STALE_LOCK_MS=2*6e4;var LEFT_BY_OLD_HOOKS=["COMMIT_EDITMSG","ORIG_HEAD","logs/HEAD","MERGE_HEAD","MERGE_MODE","MERGE_MSG","SQUASH_MSG","CHERRY_PICK_HEAD","REVERT_HEAD","AUTO_MERGE"];function leaveOldStandIn(dir,own2){const head=fs52.readFileSync(path44.join(own2,"HEAD"),"utf8");const name=/^ref: refs\/heads\/squad\/saving\/(.+)\/now\s*$/.exec(head)?.[1];if(!name)return;const common=path44.resolve(dir,git(dir,["rev-parse","--git-common-dir"]));if(fs52.lstatSync(path44.join(own2,"logs"),{throwIfNoEntry:false})?.isSymbolicLink())fs52.rmSync(path44.join(own2,"logs"),{force:true});for(const left of LEFT_BY_OLD_HOOKS)fs52.rmSync(path44.join(own2,left),{force:true});const detached=/^detached\/([0-9a-f]+)$/.exec(name);if(detached)git(dir,["update-ref","--no-deref","HEAD",detached[1]]);else git(dir,["symbolic-ref","HEAD",`refs/heads/${name}`]);for(const at3 of["","logs"])fs52.rmSync(path44.join(common,at3,"refs","heads","squad","saving"),{recursive:true,force:true});for(const ref2 of git(dir,["for-each-ref","--format=%(refname)","refs/heads/squad/saving"],{allowFail:true}).split("\n").filter(Boolean))git(dir,["update-ref","--no-deref","-d",ref2],{allowFail:true})}function cutOffWords(ref2){return`squad-kit stopped while the project's own check ran, so this ticket's files went back as they were saved. What was on disk then is kept: an engineer can see it with git show ${ref2}`}function settleCopy(dir){const own2=git(dir,["rev-parse","--absolute-git-dir"],{allowFail:true});if(!own2)return null;leaveOldStandIn(dir,own2);for(const name of["index.lock","HEAD.lock"]){const lock=path44.join(own2,name);try{if(Date.now()-fs52.statSync(lock).mtimeMs>STALE_LOCK_MS)fs52.rmSync(lock,{force:true})}catch{}}if(!hookGitLeft(dir))return null;endHookGroup(dir);try{const kept=putFilesBack(dir);clearHookGit(dir);return kept}catch{return null}}function realOf(p){try{return fs52.realpathSync(p)}catch{return p}}function trialArgs(message){return["commit","-m",message]}function hookEnv(dir,gitDir){const env={...projectEnv(),...GIT_LOCALE,...hookGitEnv(dir,gitDir)};delete env.LANGUAGE;return env}function hooksCommit(dir,start,message,limitMs,how){return new Promise(done=>{const gitDir=makeHookGit(dir,start,message);const env=hookEnv(dir,gitDir);const child=how.guard?spawnProject(["git",...trialArgs(message)].map(quoted2).join(" "),{cwd:dir,env,guard:commitGuard(how.guard,{own:realOf(git(dir,["rev-parse","--absolute-git-dir"])),common:realOf(path44.resolve(dir,git(dir,["rev-parse","--git-common-dir"]))),gitDir})}):track(spawn7("git",trialArgs(message),{cwd:dir,env,stdio:["ignore","pipe","pipe"],detached:OWN_GROUP}));if(child.pid)noteHookGroup(dir,child.pid);let said="";let timedOut=false;let stopped=false;const keep=d=>said=`${said}${d.toString("utf8")}`.slice(-4e3);child.stdout?.on("data",keep);child.stderr?.on("data",keep);const timer=setTimeout(()=>{timedOut=true;endGroup(child,2e3)},limitMs);const onStop=()=>{stopped=true;endGroup(child,2e3)};if(how.signal?.aborted)onStop();else how.signal?.addEventListener("abort",onStop,{once:true});const end=()=>{clearTimeout(timer);how.signal?.removeEventListener("abort",onStop)};child.on("error",err2=>{end();done({ok:false,said:err2.message,timedOut:false})});void whenExited(child).then(code=>{end();signalGroup(child,"SIGKILL");const commits=commitsIn(gitDir);const ok2=!timedOut&&!stopped&&(code===0||commits>0&&/nothing (?:added )?to commit|cannot lock ref 'HEAD'/.test(said));done({ok:ok2,said:said.trim(),timedOut,...stopped?{stopped}:{},...ok2?{words:code===0&&commits<=1?hookWords(gitDir,message):message}:{}})})})}function hookSaid(said,how){const blocked=how.guard?guardBlockedPath(said,how.guard):null;return blocked?`${guardBlockedDetail(blocked,"the commit hook",how.guard)}
147
147
  ${said}`:said}function gitTrouble(dir){const own2=git(dir,["rev-parse","--absolute-git-dir"],{allowFail:true});if(!own2)return"this folder is no longer a git repository";if(fs52.existsSync(path44.join(own2,"index.lock")))return"another git process holds the index (index.lock)";try{git(dir,["write-tree"]);return null}catch(err2){return err2.message}}function hookOutcome(dir,t,limitMs,how){if(t.stopped)throw new CommitStopped;if(t.timedOut)throw new HookRefused(hookSaid(t.said,how),true,limitMs);const trouble=gitTrouble(dir);if(trouble)throw new RawError(`git commit failed: ${trouble}`)}async function filesFromHooks(dir,stagedBefore){await stageAllLater(dir);await keepHookFiles(dir,stagedBefore)}async function hooksLook(dir,start,message,words,limitMs,how,whole){const stagedBefore=await stagedHookFiles(dir);let last;try{last=await hooksCommit(dir,start,message,limitMs,how);if(!last.ok){hookOutcome(dir,last,limitMs,how);await filesFromHooks(dir,stagedBefore);last=await hooksCommit(dir,start,commitHooks(dir).includes("commit-msg")?words:message,limitMs,how)}if(!last.ok){hookOutcome(dir,last,limitMs,how);await filesFromHooks(dir,stagedBefore);if(!whole&&!somethingToSave(dir))return null;throw new HookRefused(hookSaid(last.said,how),false,limitMs)}return last.words??message}finally{if(last?.stopped||last?.timedOut)await putFilesBackLater(dir);else await filesFromHooks(dir,stagedBefore);clearHookGit(dir)}}function bringHookRunner(dir){const where=projectHooksPath(dir);if(!where||path44.isAbsolute(where)||where.startsWith("~"))return;const root=checkoutOf(dir);if(path44.resolve(root)===path44.resolve(dir))return;if(fs52.lstatSync(path44.join(dir,where),{throwIfNoEntry:false})?.isSymbolicLink())fs52.rmSync(path44.join(dir,where),{force:true});const inCopy=`${realOf(dir)}${path44.sep}`;const ignored=git(root,["ls-files","-z","--others","--ignored","--exclude-standard","--",where],{allowFail:true}).split("\0").filter(Boolean);for(const rel of ignored){const to=path44.join(dir,rel);try{fs52.mkdirSync(path44.dirname(to),{recursive:true});if(!`${realOf(path44.dirname(to))}${path44.sep}`.startsWith(inCopy))continue;fs52.rmSync(to,{force:true});fs52.copyFileSync(path44.join(root,rel),to)}catch{}}}function checkoutOf(dir){const common=path44.resolve(dir,git(dir,["rev-parse","--git-common-dir"],{allowFail:true})||".git");const set2=git(dir,["config","--file",path44.join(common,"config"),"--get","core.worktree"],{allowFail:true});return set2?path44.resolve(common,set2):path44.dirname(common)}async function commitChecked(dir,message,words,limitMs=HOOK_MS,how={}){settleCopy(dir);if(!how.noRunner)bringHookRunner(dir);const where=hookFolder(dir);if(!commitHooks(dir).length&&!(where&&hasHook(dir,where,"post-commit"))){if(somethingToSave(dir))await commitLater(dir,message);return}const merging=isMerging(dir);const whole=Boolean(how.from)||merging;const savedFirst=whole&&somethingToSave(dir);if(savedFirst)await commitLater(dir,keepsCommitRules(dir)?conventionalWords("chore",`work so far: ${message}`):`Work so far: ${message}`);const tip=git(dir,["rev-parse","HEAD"]);let start=tip;if(how.from){start=git(dir,["merge-base",how.from,tip],{allowFail:true})||git(dir,["rev-list","--max-parents=0",tip]).split("\n").at(-1)}else if(merging){start=git(dir,["rev-parse",`${tip}^1`])}if(whole&&(start===tip||!git(dir,["diff","--name-only",start,tip],{allowFail:true})))return;const saying=await hooksLook(dir,start,message,words,limitMs,how,whole).catch(err2=>{if(savedFirst&&(err2 instanceof HookRefused||err2 instanceof CommitStopped))err2.owed=true;throw err2});if(saying===null||!whole&&!somethingToSave(dir))return;await gitLater(dir,[...authorArgs(dir),"commit",...whole?["--allow-empty"]:[],"-m",saying])}async function commitAll(dir,message,words=message,how={}){await stageAllLater(dir);if(!somethingToSave(dir))return{commit:null};let said;try{await commitChecked(dir,message,words,SHORT_HOOK_MS,how)}catch(err2){if(!(err2 instanceof HookRefused))throw err2;said=err2.said;if(somethingToSave(dir))await commitLater(dir,message)}return{commit:git(dir,["rev-parse","--short","HEAD"]),...said!==void 0?{hookSaid:said}:{}}}function commitWithoutHooks(dir,message){git(dir,commitArgs(dir,message))}async function commitLater(dir,message){await gitLater(dir,commitArgs(dir,message))}function authorArgs(dir){const name=git(dir,["config","user.name"],{allowFail:true});const email=git(dir,["config","user.email"],{allowFail:true});return name&&email?[]:["-c","user.name=squad-kit","-c","user.email=squad-kit@localhost"]}function commitArgsFor(dir){return authorArgs(dir)}function moveBase(dir,base2,commit,keep){const before=git(dir,["rev-parse",base2]);if(currentBranch(dir)!==base2){git(dir,["update-ref",`refs/heads/${base2}`,commit,before]);return{moved:true}}try{keepingFiles(dir,keep,before,commit,()=>git(dir,["merge","--ff-only",commit]));return{moved:true}}catch(err2){return{moved:false,note:`Your ${base2} checkout was not moved (${err2.message.replace(/^git merge failed: /,"")}). Run: git merge --ff-only ${commit.slice(0,8)}`}}}var MergeClash=class extends Error{};var MIN_GIT=[2,38];var GIT_UPDATE="Update git: on a Mac run brew install git (or xcode-select --install); on Linux use your package manager";function gitVersion(){try{return execFileSync8("git",["--version"],{encoding:"utf8",stdio:["ignore","pipe","ignore"],env:gitEnv()}).match(/(\d+\.\d+(?:\.\d+)?)/)?.[1]??null}catch{return null}}function gitIsNewEnough(version){const[major2=0,minor=0]=version.split(".").map(Number);return major2>MIN_GIT[0]||major2===MIN_GIT[0]&&minor>=MIN_GIT[1]}function mergeIntoBase(dir,base2,branch,message,keep,oneCommit=false){const baseSha=git(dir,["rev-parse",base2]);const branchSha=git(dir,["rev-parse",branch]);if(isAncestor(dir,branchSha,baseSha))return{commit:baseSha,moved:true};const tree=mergedTree(dir,base2,branch,baseSha,branchSha);if(oneCommit&&tree===git(dir,["rev-parse",`${baseSha}^{tree}`]))return{commit:baseSha,moved:true};const commit=git(dir,[...authorArgs(dir),"commit-tree",tree,"-p",baseSha,...oneCommit?[]:["-p",branchSha],"-m",message]);return{commit,...moveBase(dir,base2,commit,keep)}}function mergeWouldClash(dir,base2,branch){const baseSha=git(dir,["rev-parse",base2]);const branchSha=git(dir,["rev-parse",branch]);if(isAncestor(dir,branchSha,baseSha))return false;try{mergedTree(dir,base2,branch,baseSha,branchSha);return false}catch(err2){if(err2 instanceof MergeClash)return true;throw err2}}function mergedTree(dir,base2,branch,baseSha,branchSha){let tree;try{tree=execFileSync8("git",gitArgs(["merge-tree","--write-tree",baseSha,branchSha]),{cwd:dir,encoding:"utf8",stdio:["ignore","pipe","pipe"],maxBuffer:MOST_OUTPUT,env:gitEnv()}).split("\n")[0].trim()}catch(err2){const e=err2;if(e.status===1||e.code==="ENOBUFS")throw new MergeClash(`${branch} clashes with changes already in ${base2}.`);const version=gitVersion();const detail=(e.stderr?e.stderr.toString():e.message).trim().split("\n").slice(-2).join(" ");const old=version&&!gitIsNewEnough(version);throw new Error(old?`git ${version} cannot merge ${branch} into ${base2}: squad-kit needs git ${MIN_GIT.join(".")} or newer. ${GIT_UPDATE}, then try again.`:`git could not merge ${branch} into ${base2} (git ${version??"not found"}): ${detail}`)}return tree}function keepingFiles(dir,keep,from,to,step2){if(!keep)return step2();const lines3=args=>git(dir,args,{allowFail:true}).split("\n").filter(Boolean);const changed=lines3(["diff","--name-only",from,to,"--",keep]);const edited=lines3(["diff","--name-only","HEAD","--",keep]);const files=[...new Set([...changed,...edited])].map(f=>path44.join(dir,f)).filter(f=>fs52.existsSync(f));const saved=new Map(files.map(f=>[f,fs52.readFileSync(f)]));if(edited.length)git(dir,["checkout","HEAD","--",...edited]);let result;try{result=step2()}catch(err2){for(const[file3,content]of saved)fs52.writeFileSync(file3,content);throw err2}for(const[file3,content]of saved){fs52.mkdirSync(path44.dirname(file3),{recursive:true});fs52.writeFileSync(file3,content)}return result}function isAncestor(dir,a,b){try{execFileSync8("git",gitArgs(["merge-base","--is-ancestor",a,b]),{cwd:dir,stdio:"ignore",env:gitEnv()});return true}catch{return false}}import fs54 from"fs";import path46 from"path";import{createHash as createHash3,randomUUID as randomUUID4}from"crypto";import yaml6 from"js-yaml";var BOARD_STAGES=["tickets","specs","implement","test","review","release","bugs"];var DECISION_AREAS=["Structure","Data","Security","Interface","Rules"];var TECH_ROLES=["Language","Framework","Interface","Data","Testing","Tooling","Services"];import fs53 from"fs";import path45 from"path";var KEEP=20;var LEARN_AFTER=3;var DEFAULT_USUAL={questions:[60,180],spec:[60,180],design:[120,300],build:[120,240],fix:[120,300],checks:[60,180],review:[120,240],proof:[60,180]};function usualKey(who4,step2,track2){return`${who4}.${step2}.${track2??"full"}`}function usualFile(bp){return path45.join(bp.localDir,"usual-times.json")}function readUsual(bp){try{const raw=JSON.parse(fs53.readFileSync(usualFile(bp),"utf8"));if(!raw||typeof raw!=="object")return{};const book={};for(const[k,v]of Object.entries(raw)){if(Array.isArray(v))book[k]=v.filter(n=>typeof n==="number"&&Number.isFinite(n)&&n>0)}return book}catch{return{}}}function recordUsual(bp,key,seconds2){if(!Number.isFinite(seconds2)||seconds2<1)return;try{const book=readUsual(bp);book[key]=[...book[key]??[],Math.round(seconds2)].slice(-KEEP);fs53.mkdirSync(path45.dirname(usualFile(bp)),{recursive:true});const tmp=`${usualFile(bp)}.${process.pid}.tmp`;fs53.writeFileSync(tmp,`${JSON.stringify(book,null,1)}
148
- `);fs53.renameSync(tmp,usualFile(bp))}catch{}}function quantile(sorted,q){const at3=(sorted.length-1)*q;const lo=Math.floor(at3);const hi=Math.ceil(at3);return sorted[lo]+(sorted[hi]-sorted[lo])*(at3-lo)}function usualOf(book,who4,step2,track2){const runs2=book[usualKey(who4,step2,track2)]??[];if(runs2.length<LEARN_AFTER){const[low,high]=DEFAULT_USUAL[step2];return{low,high,learned:false,runs:runs2.length}}const sorted=[...runs2].sort((a,b)=>a-b);return{low:quantile(sorted,.25),high:quantile(sorted,.75),learned:true,runs:runs2.length}}function middle(u){return(u.low+u.high)/2}function rangeWords(u){if(u.high<60)return"under 1 min";const low=Math.max(1,Math.round(u.low/60));const high=Math.max(low,Math.round(u.high/60));return low===high?`about ${low} min`:`${low} to ${high} min`}function usualWords(u){return`usually ${rangeWords(u)}`}function durationWords(seconds2){const s=Math.max(0,seconds2);if(s<60)return`${Math.max(1,Math.floor(s))} s`;if(s<3600)return`${Math.floor(s/60)} min`;const h=Math.floor(s/3600);const m=Math.floor(s%3600/60);return m?`${h} h ${m} min`:`${h} h`}function minutesWords(seconds2){if(seconds2<60){const s=Math.max(1,Math.floor(seconds2));return`${s} second${s===1?"":"s"}`}const m=Math.floor(seconds2/60);return`${m} minute${m===1?"":"s"}`}var WHO={analyst:"Analyst",architect:"Architect",builder:"Builder",reviewer:"Reviewer",tester:"Tester",squad:"squad-kit"};function iso(at3){return new Date(at3).toISOString()}function firstOpenStep(t){const steps=t.steps??[];if(!steps.length)return void 0;const i=steps.findIndex(s=>s.status!=="done");return i>=0?i+1:steps.length}var TIMED=new Set(["questions","spec","design","build","fix","checks","review","proof"]);function timedStep(step2){return step2&&TIMED.has(step2)?step2:null}function startStep(t,c,at3=Date.now()){if(t.waiting)return;const same5=t.clock?.who===c.who&&t.clock.step===c.step?t.clock:void 0;const part=c.part??same5?.part??(c.who==="builder"&&c.step==="build"?firstOpenStep(t):void 0);const limit=c.limit??same5?.limit;const waited=same5?.paused?Math.max(0,at3-Date.parse(same5.paused)):0;const since=same5?iso(Date.parse(same5.since)+waited):iso(at3);const{paused:_paused,...goesOn}=same5??{};t.clock={...goesOn,who:c.who,step:c.step,since,began:same5?.began?iso(Date.parse(same5.began)+waited):since,...part?{part}:{},...limit?{limit}:{}}}function waitsNow(t){return Boolean(t.waiting)||!t.live&&!t.squad}function pauseClock(t,at3=Date.now()){if(t.clock&&!t.clock.paused)t.clock.paused=iso(at3)}function stepSeconds(c,at3=Date.now()){const end=c.paused?Math.min(at3,Date.parse(c.paused)):at3;return Math.max(0,(end-Date.parse(c.since))/1e3)}var HISTORY=80;function resumeClock(t,at3=Date.now()){if(t.clock)t.clock.resumed=iso(at3)}function endStep(bp,t,at3=Date.now()){const c=t.clock;if(!c)return;if(!c.resumed)recordUsual(bp,usualKey(c.who,c.step,t.track),stepSeconds(c,at3));t.history=[...t.history??[],{who:c.who,step:c.step,from:c.began??c.since,to:iso(c.paused?Math.min(at3,Date.parse(c.paused)):at3)}].slice(-HISTORY);delete t.clock}function nextPart(bp,t,part,at3=Date.now()){const c=t.clock;if(!c||c.step!=="build"||c.part===part)return;if(c.part!==void 0&&part>c.part&&!c.resumed)recordUsual(bp,usualKey(c.who,c.step,t.track),stepSeconds(c,at3));c.part=part;c.since=iso(at3);delete c.resumed;delete c.paused}function stepDoing(t,line3,at3=Date.now()){if(!t.clock||!line3)return;t.clock.doing=line3;t.clock.doingAt=iso(at3)}function trackStage(t,before,at3=Date.now()){const st=t.stageTimes??{stage:before,since:before==="tickets"||before==="bugs"?t.createdAt:iso(at3),spent:{}};if(st.stage!==t.stage){const secs=Math.max(0,Math.round((at3-Date.parse(st.since))/1e3));st.spent={...st.spent,[st.stage]:(st.spent[st.stage]??0)+(Number.isFinite(secs)?secs:0)};st.stage=t.stage;st.since=iso(at3)}t.stageTimes=st}var SLOW_FLOOR_S=5*60;function usualSentence(u){if(u.high<60)return"under a minute";const low=Math.max(1,Math.round(u.low/60));const high=Math.max(low,Math.round(u.high/60));return low===high?`about ${low} minute${low===1?"":"s"}`:`${low} to ${high} minutes`}function slowAfter(u){return Math.max(2*u.high,SLOW_FLOOR_S)}function slowStep(t,book,at3=Date.now()){const c=t.clock;if(!c||t.waiting||c.paused)return null;const u=usualOf(book,c.who,c.step,t.track);const secs=stepSeconds(c,at3);if(secs<slowAfter(u))return null;const detail=c.who==="squad"?`The checks have been running for ${minutesWords(secs)}. They usually take ${usualSentence(u)}.`:`The ${WHO[c.who]} has been on this step for ${minutesWords(secs)}. It usually takes ${usualSentence(u)}.`;return{since:c.since,kind:"long",detail}}function oldStop(t){if(!t.stuck)return false;if(t.stage==="done"||t.mergedAt)return true;if(t.stage!=="release")return false;return t.waiting?.kind!=="stuck"||["checks","review","proof"].includes(t.stuck.gate??"")}function clearStaleStop(t){if(t.stuck)t.stuck=void 0;if(t.waiting?.kind==="stuck")t.waiting=null}var ORDER=["tickets","specs","implement","test","review"];function hasSpec(t){return Boolean(t.story?.trim()||t.criteria?.length)}function hasDesign(t){return Boolean(t.decision||t.steps?.length)}function leavingAt(t){if(t.stage==="implement")return"the build";if(t.stage==="test")return"the checks";if(t.stage==="review")return t.waiting?.kind==="proof"?"the proof":"the review";if(t.stage==="specs")return t.specApproved?"the design":"the spec";return hasSpec(t)?"the spec":void 0}function outsideWords(o){return o.from?`Continued outside from ${o.from}`:"Worked outside squad-kit"}function showOutsideStep(t){const left=t.takenOver?.free;if(!left)return;const reached=t.worktree?"implement":hasSpec(t)||hasDesign(t)?"specs":"tickets";const rank=s=>Math.max(ORDER.indexOf(s),0);t.stage=rank(left.stage)>=rank(reached)?left.stage:reached;const doing=t.worktree?"building":hasDesign(t)?"the design is written":hasSpec(t)?"the spec is written":"not started yet";t.status=`In a conversation outside squad-kit: ${doing}`}var OUTSIDE_WAITS="In a conversation outside squad-kit. Bring it back when you are done";var NO_DOCS="none";function leaveForOutside(t,by2,at3,seen){const from=leavingAt(t);const phase=t.squad?.phase;const left={stage:t.stage,status:t.status,waiting:t.waiting,...phase?{phase}:{},...seen,...from?{from}:{}};t.takenOver={by:by2,at:at3,session:t.sessions?.build??"",cwd:t.storyDir??t.worktree??"",free:left};t.live=false;t.squad=null;t.waiting={kind:"yours",summary:OUTSIDE_WAITS};showOutsideStep(t)}function gatesPassed(g){return g?.review==="pass"&&g.proof==="pass"&&(g.checks==="pass"||g.checks==="unchecked")}function markCheckedOutside(t){if(!t.outside)return;const g=t.gates;t.outside={...t.outside,...gatesPassed(g)?{checked:{at:new Date().toISOString(),by:"squad-kit",...g?.checks==="unchecked"?{noTests:true}:{}}}:{checked:void 0}}}function dropOldChecked(t){if(t.outside?.checked&&!gatesPassed(t.gates))t.outside={...t.outside,checked:void 0}}function changesScreens(t){return(t.work?.screens??0)>0||Boolean(t.outside&&!t.work)}var IS={text:x=>typeof x==="string",texts:x=>Array.isArray(x)&&x.every(y=>typeof y==="string"),object:x=>isPlain(x),objects:x=>Array.isArray(x)&&x.every(isPlain),"yes-no":x=>typeof x==="boolean",number:x=>typeof x==="number"&&Number.isFinite(x)};function isPlain(x){return typeof x==="object"&&x!==null&&!Array.isArray(x)}function wrongField(o,fields){for(const[key,shape2]of Object.entries(fields)){if(o[key]!==void 0&&o[key]!==null&&!IS[shape2](o[key]))return`"${key}" is not in the right form.`}return null}function hasMergeMarks(text3){return/^<{7}( |$)[\s\S]*?^={7}\s*$[\s\S]*?^>{7}( |$)/m.test(text3)}import yaml5 from"js-yaml";function enc(v){return v.includes("\n")||v.startsWith('"')||v!==v.trim()?JSON.stringify(v):v}function dec(s){if(s.startsWith('"')&&s.endsWith('"')&&s.length>=2){try{return JSON.parse(s)}catch{return s}}return s}var MARKER=/^(\\*)(#|- \[|\d+\. |Answer:|Chosen:|To:|- (Area|Saved as|Replaces|Check with the client|Before|After|Decision|Where|More):)/;function escText(text3){return text3.split("\n").map(l=>MARKER.test(l)?`\\${l}`:l).join("\n")}function unescText(text3){return text3.split("\n").map(l=>/^\\+/.test(l)&&MARKER.test(l.slice(1))?l.slice(1):l).join("\n")}function clean(v){return JSON.parse(JSON.stringify(v))}function withHeader(header,body10){const head=yaml5.dump(clean(header),{lineWidth:110,noRefs:true}).trimEnd();return`---
148
+ `);fs53.renameSync(tmp,usualFile(bp))}catch{}}function quantile(sorted,q){const at3=(sorted.length-1)*q;const lo=Math.floor(at3);const hi=Math.ceil(at3);return sorted[lo]+(sorted[hi]-sorted[lo])*(at3-lo)}function usualOf(book,who4,step2,track2){const runs2=book[usualKey(who4,step2,track2)]??[];if(runs2.length<LEARN_AFTER){const[low,high]=DEFAULT_USUAL[step2];return{low,high,learned:false,runs:runs2.length}}const sorted=[...runs2].sort((a,b)=>a-b);return{low:quantile(sorted,.25),high:quantile(sorted,.75),learned:true,runs:runs2.length}}function middle(u){return(u.low+u.high)/2}function rangeWords(u){if(u.high<60)return"under 1 min";const low=Math.max(1,Math.round(u.low/60));const high=Math.max(low,Math.round(u.high/60));return low===high?`about ${low} min`:`${low} to ${high} min`}function usualWords(u){return`usually ${rangeWords(u)}`}function durationWords(seconds2){const s=Math.max(0,seconds2);if(s<60)return`${Math.max(1,Math.floor(s))} s`;if(s<3600)return`${Math.floor(s/60)} min`;const h=Math.floor(s/3600);const m=Math.floor(s%3600/60);return m?`${h} h ${m} min`:`${h} h`}function minutesWords(seconds2){if(seconds2<60){const s=Math.max(1,Math.floor(seconds2));return`${s} second${s===1?"":"s"}`}const m=Math.floor(seconds2/60);return`${m} minute${m===1?"":"s"}`}var WHO={analyst:"Analyst",architect:"Architect",builder:"Builder",reviewer:"Reviewer",tester:"Tester",squad:"squad-kit"};function iso(at3){return new Date(at3).toISOString()}function firstOpenStep(t){const steps=t.steps??[];if(!steps.length)return void 0;const i=steps.findIndex(s=>s.status!=="done");return i>=0?i+1:steps.length}var TIMED=new Set(["questions","spec","design","build","fix","checks","review","proof"]);function timedStep(step2){return step2&&TIMED.has(step2)?step2:null}function startStep(t,c,at3=Date.now()){if(t.waiting)return;const same5=t.clock?.who===c.who&&t.clock.step===c.step?t.clock:void 0;const part=c.part??same5?.part??(c.who==="builder"&&c.step==="build"?firstOpenStep(t):void 0);const limit=c.limit??same5?.limit;const waited=same5?.paused?Math.max(0,at3-Date.parse(same5.paused)):0;const since=same5?iso(Date.parse(same5.since)+waited):iso(at3);const{paused:_paused,...goesOn}=same5??{};t.clock={...goesOn,who:c.who,step:c.step,since,began:same5?.began?iso(Date.parse(same5.began)+waited):since,...part?{part}:{},...limit?{limit}:{}}}function waitsNow(t){return Boolean(t.waiting)||!t.live&&!t.squad}function pauseClock(t,at3=Date.now()){if(t.clock&&!t.clock.paused)t.clock.paused=iso(at3)}function stepSeconds(c,at3=Date.now()){const end=c.paused?Math.min(at3,Date.parse(c.paused)):at3;return Math.max(0,(end-Date.parse(c.since))/1e3)}var HISTORY=80;function resumeClock(t,at3=Date.now()){if(t.clock)t.clock.resumed=iso(at3)}function endStep(bp,t,at3=Date.now()){const c=t.clock;if(!c)return;if(!c.resumed)recordUsual(bp,usualKey(c.who,c.step,t.track),stepSeconds(c,at3));t.history=[...t.history??[],{who:c.who,step:c.step,from:c.began??c.since,to:iso(c.paused?Math.min(at3,Date.parse(c.paused)):at3)}].slice(-HISTORY);delete t.clock}function nextPart(bp,t,part,at3=Date.now()){const c=t.clock;if(!c||c.step!=="build"||c.part===part)return;if(c.part!==void 0&&part>c.part&&!c.resumed)recordUsual(bp,usualKey(c.who,c.step,t.track),stepSeconds(c,at3));c.part=part;c.since=iso(at3);delete c.resumed;delete c.paused}function stepDoing(t,line3,at3=Date.now()){if(!t.clock||!line3)return;t.clock.doing=line3;t.clock.doingAt=iso(at3)}function trackStage(t,before,at3=Date.now()){const st=t.stageTimes??{stage:before,since:before==="tickets"||before==="bugs"?t.createdAt:iso(at3),spent:{}};if(st.stage!==t.stage){const secs=Math.max(0,Math.round((at3-Date.parse(st.since))/1e3));st.spent={...st.spent,[st.stage]:(st.spent[st.stage]??0)+(Number.isFinite(secs)?secs:0)};st.stage=t.stage;st.since=iso(at3)}t.stageTimes=st}var SLOW_FLOOR_S=5*60;function usualSentence(u){if(u.high<60)return"under a minute";const low=Math.max(1,Math.round(u.low/60));const high=Math.max(low,Math.round(u.high/60));return low===high?`about ${low} minute${low===1?"":"s"}`:`${low} to ${high} minutes`}function slowAfter(u){return Math.max(2*u.high,SLOW_FLOOR_S)}function slowStep(t,book,at3=Date.now()){const c=t.clock;if(!c||t.waiting||c.paused)return null;const u=usualOf(book,c.who,c.step,t.track);const n=t.steps?.length??0;const heard=t.steps?.some(s=>s.status==="doing"||s.status==="done");if(c.who==="builder"&&c.step==="build"&&n>1&&!heard){const all={low:u.low*n,high:u.high*n};const secs2=Math.max(0,(at3-Date.parse(c.began??c.since))/1e3);if(secs2<slowAfter(all))return null;return{since:c.began??c.since,kind:"long",detail:`The Builder has been building for ${minutesWords(secs2)}. ${n} steps usually take ${usualSentence(all)}.`}}const secs=stepSeconds(c,at3);if(secs<slowAfter(u))return null;const detail=c.who==="squad"?`The checks have been running for ${minutesWords(secs)}. They usually take ${usualSentence(u)}.`:`The ${WHO[c.who]} has been on this step for ${minutesWords(secs)}. It usually takes ${usualSentence(u)}.`;return{since:c.since,kind:"long",detail}}function oldStop(t){if(!t.stuck)return false;if(t.stage==="done"||t.mergedAt)return true;if(t.stage!=="release")return false;return t.waiting?.kind!=="stuck"||["checks","review","proof"].includes(t.stuck.gate??"")}function clearStaleStop(t){if(t.stuck)t.stuck=void 0;if(t.waiting?.kind==="stuck")t.waiting=null}var ORDER=["tickets","specs","implement","test","review"];function hasSpec(t){return Boolean(t.story?.trim()||t.criteria?.length)}function hasDesign(t){return Boolean(t.decision||t.steps?.length)}function leavingAt(t){if(t.stage==="implement")return"the build";if(t.stage==="test")return"the checks";if(t.stage==="review")return t.waiting?.kind==="proof"?"the proof":"the review";if(t.stage==="specs")return t.specApproved?"the design":"the spec";return hasSpec(t)?"the spec":void 0}function outsideWords(o){return o.from?`Continued outside from ${o.from}`:"Worked outside squad-kit"}function showOutsideStep(t){const left=t.takenOver?.free;if(!left)return;const reached=t.worktree?"implement":hasSpec(t)||hasDesign(t)?"specs":"tickets";const rank=s=>Math.max(ORDER.indexOf(s),0);t.stage=rank(left.stage)>=rank(reached)?left.stage:reached;const doing=t.worktree?"building":hasDesign(t)?"the design is written":hasSpec(t)?"the spec is written":"not started yet";t.status=`In a conversation outside squad-kit: ${doing}`}var OUTSIDE_WAITS="In a conversation outside squad-kit. Bring it back when you are done";var NO_DOCS="none";function leaveForOutside(t,by2,at3,seen){const from=leavingAt(t);const phase=t.squad?.phase;const left={stage:t.stage,status:t.status,waiting:t.waiting,...phase?{phase}:{},...seen,...from?{from}:{}};t.takenOver={by:by2,at:at3,session:t.sessions?.build??"",cwd:t.storyDir??t.worktree??"",free:left};t.live=false;t.squad=null;t.waiting={kind:"yours",summary:OUTSIDE_WAITS};showOutsideStep(t)}function gatesPassed(g){return g?.review==="pass"&&g.proof==="pass"&&(g.checks==="pass"||g.checks==="unchecked")}function markCheckedOutside(t){if(!t.outside)return;const g=t.gates;t.outside={...t.outside,...gatesPassed(g)?{checked:{at:new Date().toISOString(),by:"squad-kit",...g?.checks==="unchecked"?{noTests:true}:{}}}:{checked:void 0}}}function dropOldChecked(t){if(t.outside?.checked&&!gatesPassed(t.gates))t.outside={...t.outside,checked:void 0}}function changesScreens(t){return(t.work?.screens??0)>0||Boolean(t.outside&&!t.work)}var IS={text:x=>typeof x==="string",texts:x=>Array.isArray(x)&&x.every(y=>typeof y==="string"),object:x=>isPlain(x),objects:x=>Array.isArray(x)&&x.every(isPlain),"yes-no":x=>typeof x==="boolean",number:x=>typeof x==="number"&&Number.isFinite(x)};function isPlain(x){return typeof x==="object"&&x!==null&&!Array.isArray(x)}function wrongField(o,fields){for(const[key,shape2]of Object.entries(fields)){if(o[key]!==void 0&&o[key]!==null&&!IS[shape2](o[key]))return`"${key}" is not in the right form.`}return null}function hasMergeMarks(text3){return/^<{7}( |$)[\s\S]*?^={7}\s*$[\s\S]*?^>{7}( |$)/m.test(text3)}import yaml5 from"js-yaml";function enc(v){return v.includes("\n")||v.startsWith('"')||v!==v.trim()?JSON.stringify(v):v}function dec(s){if(s.startsWith('"')&&s.endsWith('"')&&s.length>=2){try{return JSON.parse(s)}catch{return s}}return s}var MARKER=/^(\\*)(#|- \[|\d+\. |Answer:|Chosen:|To:|- (Area|Saved as|Replaces|Check with the client|Before|After|Decision|Where|More):)/;function escText(text3){return text3.split("\n").map(l=>MARKER.test(l)?`\\${l}`:l).join("\n")}function unescText(text3){return text3.split("\n").map(l=>/^\\+/.test(l)&&MARKER.test(l.slice(1))?l.slice(1):l).join("\n")}function clean(v){return JSON.parse(JSON.stringify(v))}function withHeader(header,body10){const head=yaml5.dump(clean(header),{lineWidth:110,noRefs:true}).trimEnd();return`---
149
149
  ${head}
150
150
  ---
151
151
  ${body10.trimEnd()}
@@ -470,7 +470,9 @@ ${askRule(t)}${openTicketsText(others,"architect")}
470
470
  ${PLAIN}${theirLanguage(t)}`}var DECISION_LINKS=` area: one of ${DECISION_AREAS.join(", ")}. replaces: only if it changes a saved decision, that decision's id
471
471
  (like "ADR-004"); the old one stops being a rule once this is approved.
472
472
  - follows: the ids of the saved decisions this ticket's design relies on (like ["ADR-003", "ADR-005"]). Only real
473
- ones from the list, only those that shape this ticket. Empty if none.`;var DEPENDS_RULE=`- dependsOn: the ids of other open tickets (from the list of open tickets, if there is one) whose code this ticket
473
+ ones from the list, only those that shape this ticket. Empty if none.`;var STEP_LINE=`Keep your to-do list as these steps: one item per step, in the same order, nothing more. Mark a step in progress
474
+ as you start it and completed when it is done. Also, as you start each step, write a line that says exactly ">> step N"
475
+ (N is the step number) in your message, before that step's work: one step at a time, never through a command such as echo.`;var DEPENDS_RULE=`- dependsOn: the ids of other open tickets (from the list of open tickets, if there is one) whose code this ticket
474
476
  needs and that are not merged yet, like ["RO-1"]. squad-kit then waits for them, or builds on their branch. Empty if
475
477
  none.`;var MOCKUP_RULE=`- mockup: when screens is 1 or more, one self-contained HTML page showing how the main screen will look after this
476
478
  ticket, using the project's own look where you can see it (colors, fonts, layout). Inline CSS and a little inline
@@ -490,7 +492,7 @@ Steps:
490
492
  ${steps}
491
493
 
492
494
  Rules:
493
- - Before you start each step, print a line that says exactly ">> step N" (N is the step number).
495
+ - ${STEP_LINE}
494
496
  - Follow the saved decisions and the project's existing style.
495
497
  - Write tests that check every "done when", and run the tests before you finish. A browser cannot start in your shell:
496
498
  run tests that open a browser with squad-kit's run_checks tool, never yourself.
@@ -844,7 +846,7 @@ The Architect updated the design for it${c.design?.summary?`: ${c.design.summary
844
846
  ${steps.map((s,i)=>`${i+1}. ${s.title}${s.status==="done"?" (done, keep it)":" (do it now)"}`).join("\n")}
845
847
 
846
848
  Redo only ${redo.length===1?"step":"steps"} ${redo.join(", ")}. The other steps are done: leave their code as it is unless the change needs it.
847
- Before you start each step, print a line that says exactly ">> step N" (N is the step number). Then run the tests.`}function chatNext(agent,hasSpec2,specApproved){if(agent==="analyst")return`When the person is happy, they press "${hasSpec2?"Update":"Write"} the spec" and you write it from this talk.`;if(agent==="architect")return specApproved?`When the person is happy, they press "Write the design" and you write it from this talk.`:"The design comes after the person approves the spec.";return"When the person presses Done, you carry on with your work as you agreed."}function chatCarryOnPrompt(agent){if(agent==="builder")return"You and the person talked it through above, and they pressed Done. Carry on with the work as you agreed, then run the tests.";if(agent==="reviewer")return"You and the person talked it through above, and they pressed Done. Finish your review with what you agreed in mind.";return"You and the person talked it through above, and they pressed Done. Carry on as you agreed."}import fs78 from"fs";import path71 from"path";import{inflateRawSync}from"zlib";var PART_WORDS={min:3e3,max:4e3};var MAX_UNPACKED=200*1024*1024;var MAX_ENTRIES2=5e3;var MAX_XML=20*1024*1024;var DocumentError=class extends Error{};var UNREADABLE_WORD="squad-kit cannot open this Word file. Save it again as .docx, or paste the text.";var EMPTY_WORD="This Word file has no text squad-kit can read. Save it again as .docx or paste the text.";var BIG_WORD="This Word file has more text than squad-kit reads in one go. Split it into smaller documents, or paste the text.";var BAD_PDF="This PDF cannot be read. Save it again as a PDF, or paste the text.";var LOCKED_PDF="This PDF has a password or is locked, so the Analyst cannot read it. Save a copy with no password and add that.";var EMPTY_TEXT="This file has no text. Add a file with text in it, or paste the text.";function kindOf(name){const ext=path71.extname(name).toLowerCase();if(ext===".pdf")return"pdf";if(ext===".docx")return"docx";if(ext===".md"||ext===".markdown")return"markdown";if(ext===".txt"||ext===".text")return"text";return null}function openZip(buf){const bad=()=>{throw new DocumentError(UNREADABLE_WORD)};let end=-1;for(let i=buf.length-22;i>=Math.max(0,buf.length-22-65535);i-=1){if(buf.readUInt32LE(i)===101010256){end=i;break}}if(end<0)bad();const count3=buf.readUInt16LE(end+10);if(count3>MAX_ENTRIES2)bad();let at3=buf.readUInt32LE(end+16);const entries=new Map;const spans=[];for(let n=0;n<count3;n+=1){if(at3+46>end||buf.readUInt32LE(at3)!==33639248)bad();const flags=buf.readUInt16LE(at3+8);const method=buf.readUInt16LE(at3+10);const packedSize=buf.readUInt32LE(at3+20);const size=buf.readUInt32LE(at3+24);const nameLength=buf.readUInt16LE(at3+28);const extraLength=buf.readUInt16LE(at3+30);const commentLength=buf.readUInt16LE(at3+32);const local=buf.readUInt32LE(at3+42);if(at3+46+nameLength>end)bad();const name=buf.subarray(at3+46,at3+46+nameLength).toString("utf8");at3+=46+nameLength+extraLength+commentLength;if(name.endsWith("/"))continue;if(flags&1||packedSize===4294967295||size===4294967295||entries.has(name))bad();if(local+30>buf.length||buf.readUInt32LE(local)!==67324752)bad();const start=local+30+buf.readUInt16LE(local+26)+buf.readUInt16LE(local+28);if(start+packedSize>buf.length||method===0&&size!==packedSize)bad();spans.push([local,start+packedSize]);entries.set(name,{method,packedSize,size,start})}spans.sort((a,b)=>a[0]-b[0]);for(let i=1;i<spans.length;i+=1)if(spans[i][0]<spans[i-1][1])bad();let unpacked=0;return{names:[...entries.keys()],read(name,max=MAX_UNPACKED){const e=entries.get(name);if(!e)return void 0;if(e.size>max||unpacked+e.size>MAX_UNPACKED)throw new DocumentError(max===MAX_XML?BIG_WORD:UNREADABLE_WORD);const packed=buf.subarray(e.start,e.start+e.packedSize);let out17;try{if(e.method===0)out17=Buffer.from(packed);else if(e.method===8)out17=inflateRawSync(packed,{maxOutputLength:Math.max(e.size,1)});else return bad()}catch{return bad()}if(out17.length!==e.size)bad();unpacked+=out17.length;return out17}}}var ENTITIES={lt:"<",gt:">",amp:"&",quot:'"',apos:"'"};function decode(s){if(!s.includes("&"))return s;return s.replace(/&(#x[0-9a-f]{1,8}|#\d{1,8}|\w{1,32});/gi,(all,e)=>{if(e[0]==="#"){const code=e[1]==="x"||e[1]==="X"?parseInt(e.slice(2),16):parseInt(e.slice(1),10);return Number.isFinite(code)&&code>0&&code<=1114111?String.fromCodePoint(code):""}return ENTITIES[e]??all})}var MAX_DEPTH=256;var SPACE2=/\s/;function attrsOf(s,from){const out17={};let i=from;while(i<s.length){const eq=s.indexOf("=",i);if(eq<0)break;const name=s.slice(i,eq).trim().split(/\s+/).pop()??"";let q=eq+1;while(q<s.length&&SPACE2.test(s[q]))q+=1;const quote=s[q];if(quote!=='"'&&quote!=="'"){i=q;continue}const close=s.indexOf(quote,q+1);if(close<0)break;if(name&&name!=="__proto__"&&name!=="constructor"&&name!=="prototype")out17[name]=decode(s.slice(q+1,close));i=close+1}return out17}function parseXml(xml){const root={name:"#root",attrs:{},kids:[]};const stack=[root];const open3=new Map;const top=()=>stack[stack.length-1];let i=0;while(i<xml.length){const lt=xml.indexOf("<",i);if(lt<0){top().kids.push(decode(xml.slice(i)));break}if(lt>i)top().kids.push(decode(xml.slice(i,lt)));if(xml.startsWith("<!--",lt)){const e=xml.indexOf("-->",lt+4);if(e<0)break;i=e+3;continue}if(xml.startsWith("<![CDATA[",lt)){const e=xml.indexOf("]]>",lt+9);if(e<0)break;top().kids.push(xml.slice(lt+9,e));i=e+3;continue}if(xml.startsWith("<?",lt)){const e=xml.indexOf("?>",lt+2);if(e<0)break;i=e+2;continue}const gt=xml.indexOf(">",lt+1);if(gt<0)break;i=gt+1;if(xml[lt+1]==="!")continue;const closing=xml[lt+1]==="/";const selfClosing=!closing&&xml[gt-1]==="/";const inner=xml.slice(lt+(closing?2:1),selfClosing?gt-1:gt);let nameEnd=0;while(nameEnd<inner.length&&!SPACE2.test(inner[nameEnd])&&inner[nameEnd]!=="/")nameEnd+=1;const name=inner.slice(0,nameEnd);if(!name)continue;if(closing){if(!open3.get(name))continue;for(;;){const n=stack.pop();open3.set(n.name,open3.get(n.name)-1);if(n.name===name)break}continue}const node={name,attrs:attrsOf(inner,nameEnd),kids:[]};top().kids.push(node);if(!selfClosing&&stack.length<=MAX_DEPTH){stack.push(node);open3.set(name,(open3.get(name)??0)+1)}}return root}function kidsNamed(n,name){return(n?.kids??[]).filter(k=>typeof k!=="string"&&k.name===name)}function kid(n,name){return kidsNamed(n,name)[0]}function findAll(n,name,out17=[]){for(const k of n.kids){if(typeof k==="string")continue;if(k.name===name)out17.push(k);findAll(k,name,out17)}return out17}function isOn(n){if(!n)return false;const v=n.attrs["w:val"];return v===void 0||!["0","false","off","none"].includes(v.toLowerCase())}function hides(rPr){const v=kid(rPr,"w:vanish")??kid(rPr,"w:webHidden");return v?isOn(v):void 0}function readStyles(xml){const heading=new Map;const numbering=new Map;const hidden=new Set;if(!xml)return{heading,numbering,hidden};for(const s of findAll(parseXml(xml),"w:style")){const id2=s.attrs["w:styleId"];if(!id2)continue;const name=(kid(s,"w:name")?.attrs["w:val"]??"").toLowerCase();const level=/^heading\s*(\d)$/.exec(name)?.[1]??/^heading(\d)$/i.exec(id2)?.[1];const outline=kid(kid(s,"w:pPr"),"w:outlineLvl")?.attrs["w:val"];if(name==="title"||id2.toLowerCase()==="title")heading.set(id2,1);else if(level)heading.set(id2,Number(level));else if(outline!==void 0&&Number(outline)<9)heading.set(id2,Number(outline)+1);const num4=kid(kid(s,"w:pPr"),"w:numPr");const numId=kid(num4,"w:numId")?.attrs["w:val"];if(numId&&numId!=="0")numbering.set(id2,{numId,ilvl:Number(kid(num4,"w:ilvl")?.attrs["w:val"]??0)});if(hides(kid(s,"w:rPr")))hidden.add(id2)}return{heading,numbering,hidden}}function readNumbering(xml){if(!xml)return()=>"bullet";const tree=parseXml(xml);const abstract=new Map;for(const a of findAll(tree,"w:abstractNum"))abstract.set(a.attrs["w:abstractNumId"]??"",a);const nums=new Map;for(const n of findAll(tree,"w:num"))nums.set(n.attrs["w:numId"]??"",kid(n,"w:abstractNumId")?.attrs["w:val"]??"");return(numId,ilvl)=>{const lvl=kidsNamed(abstract.get(nums.get(numId)??""),"w:lvl").find(l=>Number(l.attrs["w:ilvl"]??0)===ilvl);const fmt=kid(lvl,"w:numFmt")?.attrs["w:val"]??"bullet";return fmt==="bullet"||fmt==="none"?"bullet":"number"}}var BREAK="\0";function emphasised(p){const mark=p.bold&&p.italic?"***":p.bold?"**":p.italic?"*":"";const text3=p.text.trim();if(!mark||!text3)return p.text;const lead=p.text.slice(0,p.text.length-p.text.trimStart().length);const trail=p.text.slice(p.text.trimEnd().length);return`${lead}${mark}${text3}${mark}${trail}`}function escapeText(s){return s.replace(/([*`])/g,"\\$1")}function wordToMarkdown(buf){if(buf.length<4||buf.readUInt32LE(0)!==67324752)throw new DocumentError(UNREADABLE_WORD);const zip2=openZip(buf);const xml=name=>zip2.read(name,MAX_XML)?.toString("utf8");const doc=xml("word/document.xml");if(!doc)throw new DocumentError(UNREADABLE_WORD);const styles=readStyles(xml("word/styles.xml"));const listKind=readNumbering(xml("word/numbering.xml"));const rels=new Map;const relsXml=xml("word/_rels/document.xml.rels");if(relsXml){for(const r of findAll(parseXml(relsXml),"Relationship")){if(r.attrs.TargetMode==="External"||!r.attrs.Id||!r.attrs.Target)continue;rels.set(r.attrs.Id,path71.posix.normalize(path71.posix.join("word",r.attrs.Target.replace(/^\/word\//,"").replace(/^\//,""))))}}const media=[];const placed=new Map;const picture=(rid,alt)=>{const target=rid?rels.get(rid):void 0;if(!target)return"";let name=placed.get(target);if(!name){const data=zip2.read(target);if(!data)return"";name=path71.posix.basename(target).replace(/[^\w.-]+/g,"_")||`picture-${placed.size+1}`;while(media.some(m=>m.name===name))name=`${placed.size+1}-${name}`;placed.set(target,name);media.push({name,data})}const label2=(alt?.trim()||`Picture ${[...placed.keys()].indexOf(target)+1}`).replace(/[[\]\n]/g," ");return`![${label2}](../media/${name})`};let wroteText=false;const inline2=(n,lineBreak,hiddenStyle=false)=>{const pieces=[];const push=(text3,bold2=false,italic=false)=>{const last=pieces[pieces.length-1];if(last&&last.bold===bold2&&last.italic===italic)last.text+=text3;else pieces.push({text:text3,bold:bold2,italic})};const walk3=x=>{for(const k of x.kids){if(typeof k==="string")continue;if(k.name==="w:r"){const rPr=kid(k,"w:rPr");if(hides(rPr)??(styles.hidden.has(kid(rPr,"w:rStyle")?.attrs["w:val"]??"")||hiddenStyle))continue;const bold2=isOn(kid(rPr,"w:b"));const italic=isOn(kid(rPr,"w:i"));for(const c of k.kids){if(typeof c==="string")continue;if(c.name==="w:t"){const text3=c.kids.filter(s=>typeof s==="string").join("").replace(/\s+/g," ");if(text3.trim())wroteText=true;push(escapeText(text3),bold2,italic)}else if(c.name==="w:tab")push(" ",bold2,italic);else if(c.name==="w:br"||c.name==="w:cr")push(c.attrs["w:type"]==="page"?" ":BREAK);else if(c.name==="w:noBreakHyphen")push("-",bold2,italic);else if(c.name==="w:drawing"){const blip=findAll(c,"a:blip")[0];const alt=findAll(c,"wp:docPr")[0]?.attrs.descr;push(picture(blip?.attrs["r:embed"]??blip?.attrs["r:link"],alt))}else if(c.name==="w:pict"||c.name==="w:object"){const img=findAll(c,"v:imagedata")[0];push(picture(img?.attrs["r:id"],img?.attrs["o:title"]))}else if(c.name==="mc:AlternateContent"){const blip=findAll(c,"a:blip")[0];const img=findAll(c,"v:imagedata")[0];push(blip?picture(blip.attrs["r:embed"],findAll(c,"wp:docPr")[0]?.attrs.descr):picture(img?.attrs["r:id"],img?.attrs["o:title"]))}}}else if(["w:hyperlink","w:ins","w:smartTag","w:fldSimple","w:sdt","w:sdtContent","w:customXml","w:bdo","w:dir"].includes(k.name))walk3(k)}};walk3(n);return pieces.map(emphasised).join("").replace(/ +/g," ").replace(/ ?\u0000 ?/g,lineBreak)};const blocks2=[];const paragraph=p=>{const pPr=kid(p,"w:pPr");const style=kid(pPr,"w:pStyle")?.attrs["w:val"]??"";const text3=inline2(p," \n",styles.hidden.has(style)).trim();if(!text3)return;const outline=kid(pPr,"w:outlineLvl")?.attrs["w:val"];const level=styles.heading.get(style)??(outline!==void 0&&Number(outline)<9?Number(outline)+1:void 0);if(level){blocks2.push({text:`${"#".repeat(Math.min(6,level))} ${text3.replace(/\s*\n\s*/g," ")}`});return}const num4=kid(pPr,"w:numPr");const numId=kid(num4,"w:numId")?.attrs["w:val"]??styles.numbering.get(style)?.numId;if(numId&&numId!=="0"){const ilvl=Number(kid(num4,"w:ilvl")?.attrs["w:val"]??styles.numbering.get(style)?.ilvl??0);const mark=listKind(numId,ilvl)==="bullet"?"-":"1.";blocks2.push({text:`${" ".repeat(Math.min(ilvl,6))}${mark} ${text3.replace(/\n/g," ")}`,list:true});return}blocks2.push({text:text3})};const cellText=tc=>findAll(tc,"w:p").map(p=>inline2(p,"<br>",styles.hidden.has(kid(kid(p,"w:pPr"),"w:pStyle")?.attrs["w:val"]??"")).trim()).filter(Boolean).join("<br>").replace(/\|/g,"\\|");const table=tbl=>{const rows2=kidsNamed(tbl,"w:tr").map(tr=>kidsNamed(tr,"w:tc").flatMap(tc=>{const tcPr=kid(tc,"w:tcPr");const span=Math.max(1,Number(kid(tcPr,"w:gridSpan")?.attrs["w:val"]??1));const merged2=kid(tcPr,"w:vMerge");const text3=merged2&&merged2.attrs["w:val"]!=="restart"?"":cellText(tc);return[text3,...Array(span-1).fill("")]}));const width=Math.max(0,...rows2.map(r=>r.length));if(!width||rows2.every(r=>r.every(c=>!c)))return;const line3=cells=>`| ${[...cells,...Array(width-cells.length).fill("")].map(c=>c||" ").join(" | ")} |`;blocks2.push({text:[line3(rows2[0]),`|${" --- |".repeat(width)}`,...rows2.slice(1).map(line3)].join("\n")})};const body10=n=>{for(const k of n.kids){if(typeof k==="string")continue;if(k.name==="w:p")paragraph(k);else if(k.name==="w:tbl")table(k);else if(["w:sdt","w:sdtContent","w:customXml","w:ins"].includes(k.name))body10(k)}};const tree=parseXml(doc);const docBody=findAll(tree,"w:body")[0];if(!docBody)throw new DocumentError(UNREADABLE_WORD);body10(docBody);let markdown="";blocks2.forEach((b,i)=>{if(i>0)markdown+=b.list&&blocks2[i-1].list?"\n":"\n\n";markdown+=b.text});if(!wroteText||!markdown.trim())throw new DocumentError(EMPTY_WORD);return{markdown:`${markdown}
849
+ ${STEP_LINE} Then run the tests.`}function chatNext(agent,hasSpec2,specApproved){if(agent==="analyst")return`When the person is happy, they press "${hasSpec2?"Update":"Write"} the spec" and you write it from this talk.`;if(agent==="architect")return specApproved?`When the person is happy, they press "Write the design" and you write it from this talk.`:"The design comes after the person approves the spec.";return"When the person presses Done, you carry on with your work as you agreed."}function chatCarryOnPrompt(agent){if(agent==="builder")return"You and the person talked it through above, and they pressed Done. Carry on with the work as you agreed, then run the tests.";if(agent==="reviewer")return"You and the person talked it through above, and they pressed Done. Finish your review with what you agreed in mind.";return"You and the person talked it through above, and they pressed Done. Carry on as you agreed."}import fs78 from"fs";import path71 from"path";import{inflateRawSync}from"zlib";var PART_WORDS={min:3e3,max:4e3};var MAX_UNPACKED=200*1024*1024;var MAX_ENTRIES2=5e3;var MAX_XML=20*1024*1024;var DocumentError=class extends Error{};var UNREADABLE_WORD="squad-kit cannot open this Word file. Save it again as .docx, or paste the text.";var EMPTY_WORD="This Word file has no text squad-kit can read. Save it again as .docx or paste the text.";var BIG_WORD="This Word file has more text than squad-kit reads in one go. Split it into smaller documents, or paste the text.";var BAD_PDF="This PDF cannot be read. Save it again as a PDF, or paste the text.";var LOCKED_PDF="This PDF has a password or is locked, so the Analyst cannot read it. Save a copy with no password and add that.";var EMPTY_TEXT="This file has no text. Add a file with text in it, or paste the text.";function kindOf(name){const ext=path71.extname(name).toLowerCase();if(ext===".pdf")return"pdf";if(ext===".docx")return"docx";if(ext===".md"||ext===".markdown")return"markdown";if(ext===".txt"||ext===".text")return"text";return null}function openZip(buf){const bad=()=>{throw new DocumentError(UNREADABLE_WORD)};let end=-1;for(let i=buf.length-22;i>=Math.max(0,buf.length-22-65535);i-=1){if(buf.readUInt32LE(i)===101010256){end=i;break}}if(end<0)bad();const count3=buf.readUInt16LE(end+10);if(count3>MAX_ENTRIES2)bad();let at3=buf.readUInt32LE(end+16);const entries=new Map;const spans=[];for(let n=0;n<count3;n+=1){if(at3+46>end||buf.readUInt32LE(at3)!==33639248)bad();const flags=buf.readUInt16LE(at3+8);const method=buf.readUInt16LE(at3+10);const packedSize=buf.readUInt32LE(at3+20);const size=buf.readUInt32LE(at3+24);const nameLength=buf.readUInt16LE(at3+28);const extraLength=buf.readUInt16LE(at3+30);const commentLength=buf.readUInt16LE(at3+32);const local=buf.readUInt32LE(at3+42);if(at3+46+nameLength>end)bad();const name=buf.subarray(at3+46,at3+46+nameLength).toString("utf8");at3+=46+nameLength+extraLength+commentLength;if(name.endsWith("/"))continue;if(flags&1||packedSize===4294967295||size===4294967295||entries.has(name))bad();if(local+30>buf.length||buf.readUInt32LE(local)!==67324752)bad();const start=local+30+buf.readUInt16LE(local+26)+buf.readUInt16LE(local+28);if(start+packedSize>buf.length||method===0&&size!==packedSize)bad();spans.push([local,start+packedSize]);entries.set(name,{method,packedSize,size,start})}spans.sort((a,b)=>a[0]-b[0]);for(let i=1;i<spans.length;i+=1)if(spans[i][0]<spans[i-1][1])bad();let unpacked=0;return{names:[...entries.keys()],read(name,max=MAX_UNPACKED){const e=entries.get(name);if(!e)return void 0;if(e.size>max||unpacked+e.size>MAX_UNPACKED)throw new DocumentError(max===MAX_XML?BIG_WORD:UNREADABLE_WORD);const packed=buf.subarray(e.start,e.start+e.packedSize);let out17;try{if(e.method===0)out17=Buffer.from(packed);else if(e.method===8)out17=inflateRawSync(packed,{maxOutputLength:Math.max(e.size,1)});else return bad()}catch{return bad()}if(out17.length!==e.size)bad();unpacked+=out17.length;return out17}}}var ENTITIES={lt:"<",gt:">",amp:"&",quot:'"',apos:"'"};function decode(s){if(!s.includes("&"))return s;return s.replace(/&(#x[0-9a-f]{1,8}|#\d{1,8}|\w{1,32});/gi,(all,e)=>{if(e[0]==="#"){const code=e[1]==="x"||e[1]==="X"?parseInt(e.slice(2),16):parseInt(e.slice(1),10);return Number.isFinite(code)&&code>0&&code<=1114111?String.fromCodePoint(code):""}return ENTITIES[e]??all})}var MAX_DEPTH=256;var SPACE2=/\s/;function attrsOf(s,from){const out17={};let i=from;while(i<s.length){const eq=s.indexOf("=",i);if(eq<0)break;const name=s.slice(i,eq).trim().split(/\s+/).pop()??"";let q=eq+1;while(q<s.length&&SPACE2.test(s[q]))q+=1;const quote=s[q];if(quote!=='"'&&quote!=="'"){i=q;continue}const close=s.indexOf(quote,q+1);if(close<0)break;if(name&&name!=="__proto__"&&name!=="constructor"&&name!=="prototype")out17[name]=decode(s.slice(q+1,close));i=close+1}return out17}function parseXml(xml){const root={name:"#root",attrs:{},kids:[]};const stack=[root];const open3=new Map;const top=()=>stack[stack.length-1];let i=0;while(i<xml.length){const lt=xml.indexOf("<",i);if(lt<0){top().kids.push(decode(xml.slice(i)));break}if(lt>i)top().kids.push(decode(xml.slice(i,lt)));if(xml.startsWith("<!--",lt)){const e=xml.indexOf("-->",lt+4);if(e<0)break;i=e+3;continue}if(xml.startsWith("<![CDATA[",lt)){const e=xml.indexOf("]]>",lt+9);if(e<0)break;top().kids.push(xml.slice(lt+9,e));i=e+3;continue}if(xml.startsWith("<?",lt)){const e=xml.indexOf("?>",lt+2);if(e<0)break;i=e+2;continue}const gt=xml.indexOf(">",lt+1);if(gt<0)break;i=gt+1;if(xml[lt+1]==="!")continue;const closing=xml[lt+1]==="/";const selfClosing=!closing&&xml[gt-1]==="/";const inner=xml.slice(lt+(closing?2:1),selfClosing?gt-1:gt);let nameEnd=0;while(nameEnd<inner.length&&!SPACE2.test(inner[nameEnd])&&inner[nameEnd]!=="/")nameEnd+=1;const name=inner.slice(0,nameEnd);if(!name)continue;if(closing){if(!open3.get(name))continue;for(;;){const n=stack.pop();open3.set(n.name,open3.get(n.name)-1);if(n.name===name)break}continue}const node={name,attrs:attrsOf(inner,nameEnd),kids:[]};top().kids.push(node);if(!selfClosing&&stack.length<=MAX_DEPTH){stack.push(node);open3.set(name,(open3.get(name)??0)+1)}}return root}function kidsNamed(n,name){return(n?.kids??[]).filter(k=>typeof k!=="string"&&k.name===name)}function kid(n,name){return kidsNamed(n,name)[0]}function findAll(n,name,out17=[]){for(const k of n.kids){if(typeof k==="string")continue;if(k.name===name)out17.push(k);findAll(k,name,out17)}return out17}function isOn(n){if(!n)return false;const v=n.attrs["w:val"];return v===void 0||!["0","false","off","none"].includes(v.toLowerCase())}function hides(rPr){const v=kid(rPr,"w:vanish")??kid(rPr,"w:webHidden");return v?isOn(v):void 0}function readStyles(xml){const heading=new Map;const numbering=new Map;const hidden=new Set;if(!xml)return{heading,numbering,hidden};for(const s of findAll(parseXml(xml),"w:style")){const id2=s.attrs["w:styleId"];if(!id2)continue;const name=(kid(s,"w:name")?.attrs["w:val"]??"").toLowerCase();const level=/^heading\s*(\d)$/.exec(name)?.[1]??/^heading(\d)$/i.exec(id2)?.[1];const outline=kid(kid(s,"w:pPr"),"w:outlineLvl")?.attrs["w:val"];if(name==="title"||id2.toLowerCase()==="title")heading.set(id2,1);else if(level)heading.set(id2,Number(level));else if(outline!==void 0&&Number(outline)<9)heading.set(id2,Number(outline)+1);const num4=kid(kid(s,"w:pPr"),"w:numPr");const numId=kid(num4,"w:numId")?.attrs["w:val"];if(numId&&numId!=="0")numbering.set(id2,{numId,ilvl:Number(kid(num4,"w:ilvl")?.attrs["w:val"]??0)});if(hides(kid(s,"w:rPr")))hidden.add(id2)}return{heading,numbering,hidden}}function readNumbering(xml){if(!xml)return()=>"bullet";const tree=parseXml(xml);const abstract=new Map;for(const a of findAll(tree,"w:abstractNum"))abstract.set(a.attrs["w:abstractNumId"]??"",a);const nums=new Map;for(const n of findAll(tree,"w:num"))nums.set(n.attrs["w:numId"]??"",kid(n,"w:abstractNumId")?.attrs["w:val"]??"");return(numId,ilvl)=>{const lvl=kidsNamed(abstract.get(nums.get(numId)??""),"w:lvl").find(l=>Number(l.attrs["w:ilvl"]??0)===ilvl);const fmt=kid(lvl,"w:numFmt")?.attrs["w:val"]??"bullet";return fmt==="bullet"||fmt==="none"?"bullet":"number"}}var BREAK="\0";function emphasised(p){const mark=p.bold&&p.italic?"***":p.bold?"**":p.italic?"*":"";const text3=p.text.trim();if(!mark||!text3)return p.text;const lead=p.text.slice(0,p.text.length-p.text.trimStart().length);const trail=p.text.slice(p.text.trimEnd().length);return`${lead}${mark}${text3}${mark}${trail}`}function escapeText(s){return s.replace(/([*`])/g,"\\$1")}function wordToMarkdown(buf){if(buf.length<4||buf.readUInt32LE(0)!==67324752)throw new DocumentError(UNREADABLE_WORD);const zip2=openZip(buf);const xml=name=>zip2.read(name,MAX_XML)?.toString("utf8");const doc=xml("word/document.xml");if(!doc)throw new DocumentError(UNREADABLE_WORD);const styles=readStyles(xml("word/styles.xml"));const listKind=readNumbering(xml("word/numbering.xml"));const rels=new Map;const relsXml=xml("word/_rels/document.xml.rels");if(relsXml){for(const r of findAll(parseXml(relsXml),"Relationship")){if(r.attrs.TargetMode==="External"||!r.attrs.Id||!r.attrs.Target)continue;rels.set(r.attrs.Id,path71.posix.normalize(path71.posix.join("word",r.attrs.Target.replace(/^\/word\//,"").replace(/^\//,""))))}}const media=[];const placed=new Map;const picture=(rid,alt)=>{const target=rid?rels.get(rid):void 0;if(!target)return"";let name=placed.get(target);if(!name){const data=zip2.read(target);if(!data)return"";name=path71.posix.basename(target).replace(/[^\w.-]+/g,"_")||`picture-${placed.size+1}`;while(media.some(m=>m.name===name))name=`${placed.size+1}-${name}`;placed.set(target,name);media.push({name,data})}const label2=(alt?.trim()||`Picture ${[...placed.keys()].indexOf(target)+1}`).replace(/[[\]\n]/g," ");return`![${label2}](../media/${name})`};let wroteText=false;const inline2=(n,lineBreak,hiddenStyle=false)=>{const pieces=[];const push=(text3,bold2=false,italic=false)=>{const last=pieces[pieces.length-1];if(last&&last.bold===bold2&&last.italic===italic)last.text+=text3;else pieces.push({text:text3,bold:bold2,italic})};const walk3=x=>{for(const k of x.kids){if(typeof k==="string")continue;if(k.name==="w:r"){const rPr=kid(k,"w:rPr");if(hides(rPr)??(styles.hidden.has(kid(rPr,"w:rStyle")?.attrs["w:val"]??"")||hiddenStyle))continue;const bold2=isOn(kid(rPr,"w:b"));const italic=isOn(kid(rPr,"w:i"));for(const c of k.kids){if(typeof c==="string")continue;if(c.name==="w:t"){const text3=c.kids.filter(s=>typeof s==="string").join("").replace(/\s+/g," ");if(text3.trim())wroteText=true;push(escapeText(text3),bold2,italic)}else if(c.name==="w:tab")push(" ",bold2,italic);else if(c.name==="w:br"||c.name==="w:cr")push(c.attrs["w:type"]==="page"?" ":BREAK);else if(c.name==="w:noBreakHyphen")push("-",bold2,italic);else if(c.name==="w:drawing"){const blip=findAll(c,"a:blip")[0];const alt=findAll(c,"wp:docPr")[0]?.attrs.descr;push(picture(blip?.attrs["r:embed"]??blip?.attrs["r:link"],alt))}else if(c.name==="w:pict"||c.name==="w:object"){const img=findAll(c,"v:imagedata")[0];push(picture(img?.attrs["r:id"],img?.attrs["o:title"]))}else if(c.name==="mc:AlternateContent"){const blip=findAll(c,"a:blip")[0];const img=findAll(c,"v:imagedata")[0];push(blip?picture(blip.attrs["r:embed"],findAll(c,"wp:docPr")[0]?.attrs.descr):picture(img?.attrs["r:id"],img?.attrs["o:title"]))}}}else if(["w:hyperlink","w:ins","w:smartTag","w:fldSimple","w:sdt","w:sdtContent","w:customXml","w:bdo","w:dir"].includes(k.name))walk3(k)}};walk3(n);return pieces.map(emphasised).join("").replace(/ +/g," ").replace(/ ?\u0000 ?/g,lineBreak)};const blocks2=[];const paragraph=p=>{const pPr=kid(p,"w:pPr");const style=kid(pPr,"w:pStyle")?.attrs["w:val"]??"";const text3=inline2(p," \n",styles.hidden.has(style)).trim();if(!text3)return;const outline=kid(pPr,"w:outlineLvl")?.attrs["w:val"];const level=styles.heading.get(style)??(outline!==void 0&&Number(outline)<9?Number(outline)+1:void 0);if(level){blocks2.push({text:`${"#".repeat(Math.min(6,level))} ${text3.replace(/\s*\n\s*/g," ")}`});return}const num4=kid(pPr,"w:numPr");const numId=kid(num4,"w:numId")?.attrs["w:val"]??styles.numbering.get(style)?.numId;if(numId&&numId!=="0"){const ilvl=Number(kid(num4,"w:ilvl")?.attrs["w:val"]??styles.numbering.get(style)?.ilvl??0);const mark=listKind(numId,ilvl)==="bullet"?"-":"1.";blocks2.push({text:`${" ".repeat(Math.min(ilvl,6))}${mark} ${text3.replace(/\n/g," ")}`,list:true});return}blocks2.push({text:text3})};const cellText=tc=>findAll(tc,"w:p").map(p=>inline2(p,"<br>",styles.hidden.has(kid(kid(p,"w:pPr"),"w:pStyle")?.attrs["w:val"]??"")).trim()).filter(Boolean).join("<br>").replace(/\|/g,"\\|");const table=tbl=>{const rows2=kidsNamed(tbl,"w:tr").map(tr=>kidsNamed(tr,"w:tc").flatMap(tc=>{const tcPr=kid(tc,"w:tcPr");const span=Math.max(1,Number(kid(tcPr,"w:gridSpan")?.attrs["w:val"]??1));const merged2=kid(tcPr,"w:vMerge");const text3=merged2&&merged2.attrs["w:val"]!=="restart"?"":cellText(tc);return[text3,...Array(span-1).fill("")]}));const width=Math.max(0,...rows2.map(r=>r.length));if(!width||rows2.every(r=>r.every(c=>!c)))return;const line3=cells=>`| ${[...cells,...Array(width-cells.length).fill("")].map(c=>c||" ").join(" | ")} |`;blocks2.push({text:[line3(rows2[0]),`|${" --- |".repeat(width)}`,...rows2.slice(1).map(line3)].join("\n")})};const body10=n=>{for(const k of n.kids){if(typeof k==="string")continue;if(k.name==="w:p")paragraph(k);else if(k.name==="w:tbl")table(k);else if(["w:sdt","w:sdtContent","w:customXml","w:ins"].includes(k.name))body10(k)}};const tree=parseXml(doc);const docBody=findAll(tree,"w:body")[0];if(!docBody)throw new DocumentError(UNREADABLE_WORD);body10(docBody);let markdown="";blocks2.forEach((b,i)=>{if(i>0)markdown+=b.list&&blocks2[i-1].list?"\n":"\n\n";markdown+=b.text});if(!wroteText||!markdown.trim())throw new DocumentError(EMPTY_WORD);return{markdown:`${markdown}
848
850
  `,media,pictures:media.length}}function pdfPages(buf){if(buf.subarray(0,1024).toString("latin1").indexOf("%PDF")<0)throw new DocumentError(BAD_PDF);const text3=buf.toString("latin1");const pages=(text3.match(/\/Type\s*\/Page(?![a-zA-Z])/g)??[]).length;if(pages)return pages;let most=0;for(const m of text3.matchAll(/\/Type\s*\/Pages\b[^>]{0,2000}?\/Count\s+(\d+)|\/Count\s+(\d+)[^>]{0,2000}?\/Type\s*\/Pages\b/g))most=Math.max(most,Number(m[1]??m[2]));return most}function pdfLocked(buf){return/\/Encrypt\s*(\d+\s+\d+\s+R|<<)/.test(buf.toString("latin1"))}function plainText(buf){if(buf.includes(0))throw new DocumentError("This file is not plain text. Save it as PDF, Word (.docx), Markdown or text, or paste the text.");const text3=buf.toString("utf8").replace(/^/,"").replace(/\r\n?/g,"\n");if(!text3.trim())throw new DocumentError(EMPTY_TEXT);return text3.endsWith("\n")?text3:`${text3}
849
851
  `}function wordCount(text3){return(text3.match(/\S+/g)??[]).length}function sections3(markdown){const out17=[];let cur=[];let fence=false;for(const line3 of markdown.split("\n")){if(/^\s*(```|~~~)/.test(line3))fence=!fence;if(!fence&&/^#{1,6}\s/.test(line3)&&cur.some(l=>l.trim())){out17.push(cur.join("\n"));cur=[]}cur.push(line3)}if(cur.some(l=>l.trim()))out17.push(cur.join("\n"));return out17}function cutLong(section,max){const out17=[];let cur=[];let words=0;for(const para2 of section.split(/\n{2,}/)){const n=wordCount(para2);if(words&&words+n>max){out17.push(cur.join("\n\n"));cur=[];words=0}cur.push(para2);words+=n}const last=cur.join("\n\n");if(last.trim())out17.push(last);return out17}function trimLines(s){let a=0;let b=s.length;while(a<b&&s[a]==="\n")a+=1;while(b>a&&s[b-1]==="\n")b-=1;return s.slice(a,b)}function splitParts(markdown,size=PART_WORDS){const parts2=[];let cur=[];let words=0;for(const s of sections3(markdown).flatMap(x=>wordCount(x)>size.max?cutLong(x,size.max):[x])){const n=wordCount(s);if(cur.length&&(words>=size.min||words+n>size.max)){parts2.push(cur.join("\n\n"));cur=[];words=0}cur.push(trimLines(s));words+=n}if(cur.length)parts2.push(cur.join("\n\n"));return parts2.filter(p=>p.trim()).map(p=>`${p.trim()}
850
852
  `)}function prepareText(dir,source){const buf=fs78.readFileSync(path71.join(dir,source.file));const textDir=path71.join(dir,"text");const mediaDir=path71.join(dir,"media");if(source.kind==="pdf"){const pages=pdfPages(buf);if(pdfLocked(buf))throw new DocumentError(LOCKED_PDF);if(!pages)throw new DocumentError(BAD_PDF);return{parts:0,pages}}let markdown;let pictures=0;if(source.kind==="docx"){const w=wordToMarkdown(buf);markdown=w.markdown;pictures=w.pictures;fs78.rmSync(mediaDir,{recursive:true,force:true});if(w.media.length){fs78.mkdirSync(mediaDir,{recursive:true});for(const m of w.media)fs78.writeFileSync(path71.join(mediaDir,m.name),m.data)}}else markdown=plainText(buf);const parts2=splitParts(markdown);fs78.rmSync(textDir,{recursive:true,force:true});fs78.mkdirSync(textDir,{recursive:true});parts2.forEach((p,i)=>fs78.writeFileSync(path71.join(textDir,partName(i+1)),p));return{parts:parts2.length,...source.kind==="docx"?{pictures}:{},words:wordCount(markdown)}}function partName(n){return`part-${String(n).padStart(2,"0")}.md`}function textParts(dir){const textDir=path71.join(dir,"text");if(!fs78.existsSync(textDir))return[];return fs78.readdirSync(textDir).filter(f=>/^part-\d+\.md$/.test(f)).sort().map(f=>`text/${f}`)}var SUGGESTED="(suggested)";function autoAskRule(){return`- This story builds by itself: its story and "done when" were approved with the breakdown, and nobody approves
@@ -985,7 +987,7 @@ The last lines it printed:
985
987
  ${tail(r.output,30)}`}function short4(s,n=MAX_NAME){return s.length>n?`${s.slice(0,n)}\u2026`:s}import http from"http";import path92 from"path";import{timingSafeEqual}from"crypto";import{fileURLToPath as fileURLToPath2}from"url";var WINDOW_NAME="squad";var PROTOCOLS=["2025-11-25","2025-06-18","2025-03-26","2024-11-05"];var MAX_BODY=1024*1024;var KEEP_IDLE_MS=12e4;var windows=new Map;var board=null;var own=null;var ownServer=null;function windowAt(base2){board=base2?base2.replace(/\/$/,""):null}function openWindow(w){windows.set(w.key,w)}async function closeWindow(w){windows.delete(w.key);await w.close()}function cliEntry(){const here=fileURLToPath2(import.meta.url);if(/[\\/]cli\.js$/.test(here))return here;return path92.resolve(path92.dirname(here),"../../dist/cli.js")}async function windowAccess(w){const base2=board??await ownBase();return{name:WINDOW_NAME,url:`${base2}/mcp`,key:w.key,tools:[...w.tools],bridge:{command:process.execPath,args:[cliEntry(),"mcp-bridge"]}}}function ownBase(){own??=new Promise((resolve2,reject)=>{const srv=http.createServer((req,res)=>void serveNode(req,res));srv.keepAliveTimeout=KEEP_IDLE_MS;srv.once("error",reject);srv.listen(0,"127.0.0.1",()=>{const a=srv.address();srv.unref();ownServer=srv;resolve2(`http://127.0.0.1:${typeof a==="object"&&a?a.port:0}`)})});return own}function closeServer(s,done){const sweep=setInterval(()=>s.closeIdleConnections(),100);s.close(err2=>{clearInterval(sweep);done(err2)})}async function serveNode(req,res){const chunks=[];let size=0;for await(const c of req){size+=c.length;if(size>MAX_BODY)break;chunks.push(c)}const headers=new Headers;for(const[k,v]of Object.entries(req.headers))if(typeof v==="string")headers.set(k,v);const url=`http://${req.headers.host??"127.0.0.1"}${req.url??"/"}`;const r=await answerWindow(new Request(url,{method:req.method??"GET",headers,...req.method==="POST"?{body:Buffer.concat(chunks)}:{}}));res.writeHead(r.status,Object.fromEntries(r.headers.entries()));res.end(Buffer.from(await r.arrayBuffer()))}var json=(body10,status=200)=>new Response(JSON.stringify(body10),{status,headers:{"content-type":"application/json","cache-control":"no-store"}});var rpcError=(id2,code,message)=>({jsonrpc:"2.0",id:id2??null,error:{code,message}});function windowOf(req){const auth2=req.headers.get("authorization")??"";const key=auth2.toLowerCase().startsWith("bearer ")?auth2.slice(7).trim():"";if(!key)return null;const given=Buffer.from(key);for(const[k,w]of windows){const known=Buffer.from(k);if(given.length===known.length&&timingSafeEqual(given,known))return w}return null}async function answerWindow(req){if(req.method!=="POST")return new Response(null,{status:405,headers:{allow:"POST"}});if(req.headers.get("origin"))return json(rpcError(null,-32e3,"squad-kit takes these calls only from programs on this computer."),403);const host=(req.headers.get("host")??"").replace(/:\d+$/,"").toLowerCase();if(!["127.0.0.1","localhost","[::1]"].includes(host))return json(rpcError(null,-32e3,"squad-kit takes these calls only on this computer."),403);const w=windowOf(req);if(!w||w.isClosed)return json(rpcError(null,-32001,"squad-kit does not know this key, or its run has ended."),401);const length=Number(req.headers.get("content-length")??0);if(length>MAX_BODY)return json(rpcError(null,-32600,"The request is too large."),413);let body10;try{const raw=await req.text();if(raw.length>MAX_BODY)return json(rpcError(null,-32600,"The request is too large."),413);body10=JSON.parse(raw)}catch{return json(rpcError(null,-32700,"The request is not JSON."),400)}const batch=Array.isArray(body10);const messages=batch?body10:[body10];if(!messages.length||messages.length>20)return json(rpcError(null,-32600,"Send one message, or a few."),400);const answers2=[];for(const m of messages){if(!m||typeof m!=="object"){answers2.push(rpcError(null,-32600,"Not a JSON-RPC message."));continue}if(m.id===void 0||m.id===null||!m.method)continue;answers2.push(await dispatch(w,m))}if(!answers2.length)return new Response(null,{status:202});return json(batch?answers2:answers2[0])}async function dispatch(w,m){const p=m.params??{};switch(m.method){case"initialize":{const asked=typeof p.protocolVersion==="string"?p.protocolVersion:"";return{jsonrpc:"2.0",id:m.id,result:{protocolVersion:PROTOCOLS.includes(asked)?asked:PROTOCOLS[1],capabilities:{tools:{listChanged:false}},serverInfo:{name:"squad-kit",version:readInstalledPackage().version},instructions:"squad-kit's own tools for this step. squad-kit checks every field and does the work itself, on this story's own app on this computer."}}}case"ping":return{jsonrpc:"2.0",id:m.id,result:{}};case"tools/list":return{jsonrpc:"2.0",id:m.id,result:{tools:w.listTools()}};case"tools/call":{const name=typeof p.name==="string"?p.name:"";const result=await w.call(name,p.arguments??{});return{jsonrpc:"2.0",id:m.id,result}}default:return rpcError(m.id,-32601,`squad-kit does not offer ${String(m.method).slice(0,60)}.`)}}var TECHNICAL_BACK="This is technical. Decide it yourself, or call i_am_stuck.";var TECHNICAL_TRIES=2;function refusedAskLine(who4,what){if(what==="settings")return`The ${who4} asked to change squad-kit's settings. squad-kit refused, and did not bother you with it.`;if(what==="secret")return`The ${who4} asked for a password or a key. squad-kit refused, and did not bother you with it.`;return`The ${who4} asked to change the history of the code. squad-kit refused, and did not bother you with it.`}function refusedAskBack(what){const why=what==="settings"?"Nobody may ask the person to change squad-kit's settings, its .squad folder or the files agents load as instructions.":what==="secret"?"Nobody may ask the person for a password, a key, a token or any other secret.":"Nobody may ask the person to change git history: squad-kit does all the git.";return`squad-kit refused your question. ${why} Decide it yourself, or call i_am_stuck if something technical is in your way.`}var LOOK_ALIKES={\u0430:"a",\u0432:"b",\u0435:"e",\u0451:"e",\u0437:"3",\u043A:"k",\u043C:"m",\u043D:"h",\u043E:"o",\u0440:"p",\u0441:"c",\u0442:"t",\u0443:"y",\u0445:"x",\u0455:"s",\u0456:"i",\u0457:"i",\u0458:"j","\u0501":"d","\u051B":"q","\u051D":"w",\u04BB:"h","\u04CF":"l",\u0261:"g",\u04AF:"y",\u0410:"A",\u0412:"B",\u0415:"E",\u041A:"K",\u041C:"M",\u041D:"H",\u041E:"O",\u0420:"P",\u0421:"C",\u0422:"T",\u0423:"Y",\u0425:"X",\u0405:"S",\u0406:"I",\u0408:"J","\u0500":"D",\u04AE:"Y",\u03B1:"a",\u03B2:"b",\u03B5:"e",\u03B9:"i",\u03BA:"k",\u03BD:"v",\u03BF:"o",\u03C1:"p",\u03C4:"t",\u03C5:"u",\u03C7:"x",\u03B3:"y",\u03C9:"w",\u0391:"A",\u0392:"B",\u0395:"E",\u0396:"Z",\u0397:"H",\u0399:"I",\u039A:"K",\u039C:"M",\u039D:"N",\u039F:"O",\u03A1:"P",\u03A4:"T",\u03A5:"Y",\u03A7:"X","\u2018":"'","\u2019":"'","\u02BC":"'","\u201C":'"',"\u201D":'"',"\u2010":"-","\u2011":"-","\u2012":"-","\u2013":"-","\u2212":"-"};var LOOK_ALIKE=new RegExp(`[${Object.keys(LOOK_ALIKES).join("")}]`,"g");var INVISIBLE=/[\p{Cf}\p{Mn}\p{Me}ᅟᅠㅤᅠ⠀]/gu;function readable(text3){return text3.normalize("NFKC").replace(INVISIBLE,"").replace(LOOK_ALIKE,c=>LOOK_ALIKES[c]??c).replace(/[^\S\n]+/g," ").trim()}function sentences(text3){return text3.split(/(?<=[.!?؟])\s+|\n+/).map(s=>s.trim()).filter(Boolean)}var CHANGE=/\b(edit(s|ed|ing)?|chang(e|es|ed|ing)|set|sets|updat(e|es|ed|ing)|modif(y|ies|ied|ying)|add(s|ed|ing)?|remov(e|es|ed|ing)|delet(e|es|ed|ing)|replac(e|es|ed|ing)|turn(s|ed|ing)? (it |them |this |that )?(on|off)|switch(es|ed|ing)? (it |them |this |that )?(on|off)|enabl(e|es|ed|ing)|disabl(e|es|ed|ing)|adjust(s|ed|ing)?|(re)?configur(e|es|ed|ing)|tweak(s|ed|ing)?|allow(s|ed|ing)?|white-?list|allow-?list|loosen|relax|lift|bypass|skip|unlock|open up|grant|raise|increase|extend|widen|put|write|rewrite)\b/i;var SQUAD_THINGS=[/(^|[\s"'`(/\\])\.squad\b/i,/\b(CLAUDE(\.local)?|AGENTS|GEMINI)\.md\b|(^|[\s"'`(/\\])\.(claude|cursor|codex|gemini)\b|\.cursorrules\b|\bcopilot-instructions\b|\bsecrets\.ya?ml\b/i,/\bsquad-?kit'?s?\s+(own\s+)?(settings?|config(uration)?|rules|checks?|guard|sandbox|hosts?|permissions?|commands?|files?|folder|limits?|options?)\b/i,/\b(allowed|permitted|allow-?listed|white-?listed)\s+(hosts?|sites?|websites?|domains?)\b|\b(hosts?|domains?)\s+list\b|\b(hosts?|sites?|websites?|domains?|addresses)\s+(that\s+)?(the\s+)?(squad|agents?)\s+(may|can|is allowed to|are allowed to)\s+(reach|use|open|visit|access|call)\b/i,/\b(the|squad-?kit'?s|its|this|that|your|my)\s+(guard|sandbox)\b(?!\s+(role|roles|desk|station|shifts?|team|users?|staff|booth|post|rota|schedule|on duty))|\b(sandboxed|seatbelt|bubblewrap)\b/i,/\b(start|test|tests|check|checks|build|setup|lint)\s+commands?\b/i,/\b(allow|let|permit)\s+(the\s+)?(squad|agents?|me|us)\s+(to\s+)?(reach|open|run|use|access|write|read|install|call|launch|start)\b/i];var SETTINGS_PLACE=/\b(in|into|from|under|via|through|on|open|opens|opening|to)\s+(the\s+)?(squad-?kit'?s?\s+)?Settings\b/;var ASKED=/\b(please|kindly|can you|could you|would you|will you|you (can|could|should|need to|must|have to|'ll need to|will need to|may want to|might want to)|i need you to|we need you to|(do|would) you mind)\b|^(please,?\s+)?(edit|change|set|update|modify|add|remove|delete|replace|turn|switch|enable|disable|adjust|configure|tweak|allow|grant|open|go|run|type|paste|force|rebase|squash|amend|revert|reset|push|drop|cherry-pick|rewrite|undo|give|send|share|tell|enter|provide)\b/i;var HISTORY2=[/\bforce[- ]?push(es|ed|ing)?\b|\bpush\s+(--force(-with-lease)?|-f)\b|\bgit\s+(push|reset|rebase|commit|merge|checkout|switch|stash|clean|tag|branch|revert|cherry-pick|filter-branch|filter-repo|reflog|gc|prune)\b/i,/\brebas(e|es|ed|ing)\b[^.?!\n]{0,40}\b(onto|branch(es)?|commits?|main|master|origin|upstream|history|git|interactive)\b|\b(branch(es)?|commits?)\b[^.?!\n]{0,40}\brebas(e|ed|ing)\b/i,/\breset\s+--hard\b|\bhard reset\b[^.?!\n]{0,30}\b(branch|commits?|main|master|repo)\b/i,/\b(amend|squash|drop|revert|cherry-?pick|remove|delete|rewrite|undo|edit|reorder|split)\s+((the|that|this|those|these|my|your|its|last|latest|previous|old|a|some|all|two|three|\d+)\s+)*commits?\b/i,/\b(rewrite|rewriting|change|changing|edit|alter|clean( up)?|squash)\s+((the|its|this|our|your)\s+)?(git|commit|code'?s?|branch'?s?|repo(sitory)?'?s?)\s+history\b|\bhistory of (the|this|its) (code|branch|commits|repo(sitory)?)\b/i,/\b(delete|remove|drop|rename|reset|force|overwrite)\s+((the|this|that|a|my|your|its)\s+)?((git|remote|feature|ticket'?s?|story'?s?|work|main|master|base)\s+)?branch(es)?\b(?!\s+(offices?|managers?|staff|locations?|names?|list|address(es)?|codes?|levels?|of|in|at|for|from|near|that|which|where|hours|opening|accounts?|network|teams?|directors?|employees?))/i];var SECRET_WORD=/^(passwords?|passcodes?|passphrases?|pins?|keys?|api-?keys?|apikeys?|tokens?|secrets?|credentials?|creds|logins?|log-ins?|sign-ins?|signins?)$/i;var SIGN_IN_WORD=/^(logins?|log-ins?|sign-ins?|signins?|account|access|admin|credential|password)$/i;var ENDS_THING=new Set("for of about on in at with to from by that which who whose and or so if when because here there now please first again than as into is are was were be via over under using after before while where how why then but".split(" "));var GIVE=/(?:^|\b(?:please|kindly|just)\s*,?\s+|\b(?:can|could|would|will) you\s+(?:please\s+|also\s+|just\s+|kindly\s+)?|\byou (?:can|could|should|need to|must|have to)\s+(?:also\s+|just\s+)?|\b(?:i|we) need you to\s+)(?:paste|send|share|give|provide|tell|enter|type|supply|post|reveal|hand over|copy|put|drop|forward|email|dm|text|add|input|fill in)\s+/gi;var HAVE=/\b(?:do|can|could|would) you\s+(?:please\s+)?(?:still\s+)?(?:have|know|remember)\s+|\b(?:i|we)(?:'d| would| will|'ll)?\s+(?:need|want|require)\s+|\b(?:can|could|may) (?:i|we)\s+(?:please\s+)?(?:get|have|borrow|use|see)\s+/gi;var SECRET_TO_HAND=/\b(passwords?|pins?|keys?|tokens?|logins?|credentials?|secrets?)\s+(?:(?:for (?:me|us)\s+)?to (?:paste|send|share|copy)|(?:that\s+)?(?:i|we) (?:can|could|should|may) (?:paste|use|copy|try))\b/i;var WHAT=/\b(?:what(?:'s|s| is| are| was| were)|where(?:'s| is| are| can i find| do i find| can i get| do i get)|which is)\s+/gi;var SECRET_PLACE=/(^|[\s"'`(/])\.env\b|\bsecrets?\.ya?ml\b|\bkeychain\b|\bpassword manager\b/i;function thingAsked(rest){const words=rest.split(/\s+/).filter(Boolean);let i=0;if(/^(me|us|it|over|in)$/i.test(words[i]??""))i+=1;if(/^(to|with)$/i.test(words[i]??"")&&/^(me|us)$/i.test(words[i+1]??""))i+=2;if(/^what$/i.test(words[i]??""))i+=1;const thing=[];for(;i<words.length&&thing.length<6;i+=1){const raw=words[i];const word=raw.replace(/^["'`(]+|["'`).,;:!?]+$/g,"");if(!word||ENDS_THING.has(word.toLowerCase()))break;thing.push(word);if(/[.,;:!?)]$/.test(raw))break}return thing}function isSecretThing(thing){const last=thing[thing.length-1]??"";const before=thing[thing.length-2]??"";if(SECRET_WORD.test(last))return true;if(/^(details|info|information)$/i.test(last)&&SIGN_IN_WORD.test(before))return true;return/^connection$/i.test(before)&&/^strings?$/i.test(last)}function asksForSecret(s){if(SECRET_TO_HAND.test(s)&&(ASKED.test(s)||/\b(i|we|me|us)\b/i.test(s)))return true;for(const re of[GIVE,HAVE,WHAT]){re.lastIndex=0;for(let m=re.exec(s);m;m=re.exec(s)){if(isSecretThing(thingAsked(s.slice(m.index+m[0].length))))return true;if(re===GIVE&&SECRET_PLACE.test(s))return true;if(m[0].length===0)re.lastIndex+=1}}return false}var AR_SECRET="(?:\u0627\u0644)?(?:\u0643\u0644\u0645(?:\u0629|\u0627\u062A) (?:\u0627\u0644)?(?:\u0645\u0631\u0648\u0631|\u0633\u0631)|\u0631\u0642\u0645 (?:\u0627\u0644)?\u0633\u0631\u064A|\u0631\u0645\u0632 (?:\u0627\u0644)?(?:\u062F\u062E\u0648\u0644|\u0633\u0631\u064A|\u0648\u0635\u0648\u0644)|\u0645\u0641\u062A\u0627\u062D (?:\u0627\u0644)?(?:API|\u0648\u0627\u062C\u0647\u0629|\u0648\u0635\u0648\u0644|\u062F\u062E\u0648\u0644|\u0633\u0631\u064A)|\u062A\u0648\u0643\u0646|\u0628\u064A\u0627\u0646\u0627\u062A (?:\u0627\u0644)?(?:\u062F\u062E\u0648\u0644|\u0627\u0639\u062A\u0645\u0627\u062F)|API[- ]?key|token|password)";var AR_GIVE="(?:^|\u0645\u0646 \u0641\u0636\u0644\u0643\\s+|\u0631\u062C\u0627\u0621\\s+|\u064A\u0631\u062C\u0649\\s+|\u0647\u0644 \u064A\u0645\u0643\u0646\u0643\\s+|\u0647\u0644 \u062A\u0633\u062A\u0637\u064A\u0639\\s+|\u0623\u0631\u062C\u0648\\s+)(?:\u0623\u0639\u0637\u0646\u064A|\u0623\u0639\u0637\u0646\u0627|\u0623\u0631\u0633\u0644|\u0632\u0648\u062F\u0646\u064A|\u0632\u0648\u062F\u0646\u0627|\u0634\u0627\u0631\u0643|\u0627\u0644\u0635\u0642|\u0623\u062F\u062E\u0644|\u0627\u0643\u062A\u0628|\u0642\u062F\u0645|\u0627\u0646\u0633\u062E|\u0623\u062E\u0628\u0631\u0646\u064A|\u0625\u0639\u0637\u0627\u0626\u064A|\u0625\u0631\u0633\u0627\u0644|\u062A\u0632\u0648\u064A\u062F\u064A|\u062A\u0632\u0648\u064A\u062F\u0646\u0627|\u0645\u0634\u0627\u0631\u0643\u0629)(?:\\s+(?:\u0644\u064A|\u0644\u0646\u0627|\u0625\u0644\u064A|\u0625\u0644\u064A\u0646\u0627|\u0645\u0639\u064A|\u0645\u0639\u0646\u0627|\u0628))?\\s*";var AR_HAVE="(?:\u0647\u0644 \u0644\u062F\u064A\u0643|\u0647\u0644 \u0639\u0646\u062F\u0643|\u0647\u0644 \u062A\u0639\u0631\u0641|\u0647\u0644 \u062A\u0645\u0644\u0643|\u0623\u062D\u062A\u0627\u062C(?:\\s+\u0625\u0644\u0649)?|\u0646\u062D\u062A\u0627\u062C(?:\\s+\u0625\u0644\u0649)?|\u0623\u0631\u064A\u062F|\u0646\u0631\u064A\u062F)\\s+";var AR_WHAT="(?:\u0645\u0627 \u0647\u064A|\u0645\u0627 \u0647\u0648|\u0645\u0627|\u0623\u064A\u0646 \u0647\u064A|\u0623\u064A\u0646 \u0647\u0648|\u0623\u064A\u0646)\\s+";var AR_ASKS_SECRET=new RegExp(`(?:${AR_GIVE}|${AR_HAVE}|${AR_WHAT})${AR_SECRET}`);var AR_CHANGE=/(?:غير|عدل|حدث|أضف|احذف|أزل|بدل|فعل|عطل|اسمح|ارفع|افتح|اكتب في|تغيير|تعديل|تحديث|إضافة|حذف|تفعيل|تعطيل)/;var AR_SQUAD_THINGS=/(?:إعدادات|ملفات|مجلد|قواعد|فحوصات|حدود|صلاحيات|حارس)\s+squad-?kit/i;function notAllowedIn(text3,reply2){for(const s of sentences(text3)){const asked=ASKED.test(s);if(asksForSecret(s)||AR_ASKS_SECRET.test(s))return{what:"secret",why:"asks the person for a secret"};if(AR_CHANGE.test(s)&&AR_SQUAD_THINGS.test(s))return{what:"settings",why:"asks the person to change squad-kit's settings or protected files"};const changes=CHANGE.test(s)&&(!reply2||asked);if(changes&&SQUAD_THINGS.some(re=>re.test(s)))return{what:"settings",why:"asks the person to change squad-kit's settings or protected files"};if(changes&&asked&&SETTINGS_PLACE.test(s))return{what:"settings",why:"asks the person to change squad-kit's settings"};if((!reply2||asked)&&HISTORY2.some(re=>re.test(s)))return{what:"history",why:"asks the person to change git history"}}return null}var TECHNICAL=[/\b(config(uration)? files?|settings files?|package\.json|tsconfig|dockerfile|docker-compose|makefile|\.env\b|\.ya?ml\b|\.toml\b|\.ini\b|\.json\b)/i,...SQUAD_THINGS,/\bports? \d{2,5}\b|\bport (number|is (in use|taken|busy|blocked)|in use)\b|\b(busy|free|open|another|a different) port\b|\bon port\b|\blisten(s|ing)? on\b|\blocalhost\b|\b127\.0\.0\.1\b|\b0\.0\.0\.0\b|:\d{4,5}\b/i,/\b(pids?|process id|kill( the)? (process|server|app)|running process(es)?|background process(es)?|dev server|web server)\b/i,/\b(permission denied|file permissions?|eacces|eperm|sudo|chmod|chown|root access|admin rights)\b/i,/\bpermissions? (to|for) (run|write|read|execute|install|access|use)\b[^.?!\n]{0,30}\b(files?|folders?|director(y|ies)|commands?|terminal|shell|scripts?|browser|tools?|ports?|network|internet|disk)\b/i,/\b(sandbox(ed)?|seatbelt|bubblewrap|mach port|bootstrap_check_in|sigtrap)\b/i,/\b(run|type|enter|paste|execute|start|launch|use)\b[^.?!\n]{0,40}\b(in|into|from|inside) (a |the |your |my )?terminal\b|\b(open|launch) (a |the |your |my )?(new )?terminal\b|\bterminal (window|app|command|session|tab|emulator)s?\b/i,/\b(command line|command prompt|shell (command|script)s?|bash|zsh|powershell|cli)\b/i,/\b(run|execute|type|enter|paste) ((the|this|a|that|these|those|following|my|your) )?((shell|terminal|cli) )?(commands?|scripts?)\b|\b(shell|terminal|cli|bash|git|npm|pnpm|yarn) commands?\b|\b(npm|pnpm|yarn|npx|pip|pip3|brew|apt|apt-get|dotnet|mvn|gradle|cargo|go get|composer)\b/i,/\b(environment variables?|env vars?)\b/i,/\b[A-Z][A-Z0-9]*_[A-Z0-9_]{2,}\b/,/\b(install(ed|ing|s)?|reinstall|upgrade|downgrade) (the |a |an )?(browser|chromium|chrome|playwright|package|packages|dependenc(y|ies)|node|python|library|libraries|module|sdk|driver|plugin|tool)\b/i,/\b(playwright|chromium|headless|webdriver|puppeteer|selenium)\b/i,/(^|[\s"'`(])(~\/|\.\.?\/|\/(Users|home|private|tmp|var|etc|opt|usr)\/|[A-Za-z]:\\)/,/\b[\w.-]+\/[\w./-]*\.(m?[jt]sx?|json|py|ya?ml|toml|sh|md|lock|cfg|ini|cs|java|rb|go|php|html|css)\b/i,/\bmcp\b|\bi_am_stuck\b/i,/\bgit\b|\bpull request\b|\bgit stash\b|\bstash(ed|ing)? ((the|my|your|these|those|its|all|local|uncommitted) )?(changes|work|edits)\b|\bmerge conflict\b|\bcommit (the|these|my|this) changes\b/i,/مل(?:ف|فات) (?:ال)?(?:إعدادات|تكوين)|متغير(?:ات)? (?:ال)?بيئة/,/(?:رقم )?(?:ال)?منفذ \d{2,5}|رقم (?:ال)?منفذ|خادم (?:ال)?(?:تطوير|ويب)|(?:ال)?عملية الجارية/,/صلاحيات (?:ال)?مل(?:ف|فات)|صلاحية (?:ال)?ملف|تم رفض الإذن/,/(?:نافذة|سطر|موجه) (?:ال)?أوامر|(?:في |إلى |من )(?:ال)?طرفية/,/(?:شغل|نفذ|اكتب|الصق|تشغيل|تنفيذ)\s+(?:هذا\s+|هذه\s+)?(?:ال)?(?:أمر|أوامر|سكربت|برنامج النصي)/,/(?:ثبت|تثبيت|حدث|تحديث)\s+(?:ال)?(?:متصفح|حزم(?:ة)?|مكتب(?:ة|ات)|إضافة|أداة|حزمة)/,/طلب(?:ات)? (?:ال)?سحب|تعارض (?:ال)?دمج|مستودع (?:ال)?كود|(?:ال)?فرع (?:الرئيسي )?في (?:ال)?مستودع/];function askText(q){const named=x=>Array.isArray(x)?x.flatMap(s=>s&&typeof s==="object"?[s.text,s.title,s.section]:[s]):[];const parts2=[q.text,...Array.isArray(q.options)?q.options:[],q.why,...named(q.sources),...named(q.refs),q.impact];return parts2.filter(x=>typeof x==="string"&&x.trim()!=="").join("\n")}function judgeQuestion(q){const text3=readable(askText(q));if(!text3)return{pass:true};const no=notAllowedIn(text3,false);if(no)return{pass:false,kind:"not-allowed",...no};const hit=TECHNICAL.find(re=>re.test(text3));if(hit)return{pass:false,kind:"technical",why:`technical: ${(hit.exec(text3)?.[0]??"").trim().slice(0,60)}`};return{pass:true}}function judgeReply(text3){const no=notAllowedIn(readable(text3??""),true);return no?{pass:false,kind:"not-allowed",...no}:{pass:true}}function refusedReplyLine(who4,what){const asked=what==="settings"?"to change squad-kit's settings":what==="secret"?"for a password or a key":"to change the history of the code";return`The ${who4}'s answer asked you ${asked}, so squad-kit left it out. Ask again, or ask in other words.`}function questionsIn(answer2){const a=answer2;if(!a||typeof a!=="object")return[];const one2=(q,at3)=>{const x=q;if(!x||typeof x.text!=="string"||!x.text.trim())return[];return[{at:at3,text:x.text,...Array.isArray(x.options)?{options:x.options.filter(o=>typeof o==="string")}:{},...typeof x.why==="string"?{why:x.why}:{},...Array.isArray(x.sources)?{sources:x.sources}:{},...Array.isArray(x.refs)?{refs:x.refs}:{},...typeof x.impact==="string"?{impact:x.impact}:{}}]};return[...one2(a.question,"question"),...Array.isArray(a.questions)?a.questions.flatMap((q,i)=>one2(q,`questions[${i}]`)):[]]}function stoppedQuestion(answer2,settles){for(const q of questionsIn(answer2)){const verdict=judgeQuestion(q);if(!verdict.pass&&verdict.kind==="technical"&&settles?.(q))continue;if(!verdict.pass)return{q,verdict}}return null}function questionBackPrompt(found2){const said=`Your ${found2.q.at==="question"?"question":`question (${found2.q.at})`}: "${found2.q.text.slice(0,300)}"`;const back=found2.verdict.kind==="technical"?TECHNICAL_BACK:refusedAskBack(found2.verdict.what);return`${said}
986
988
  ${back}
987
989
  Then carry on and hand in your answer again${found2.q.at==="question"?" without that question":" without that question in it"}. Ask the person only about the business: a gap (something missing that changes what gets built) or a clash (two requirements that disagree).`}var STOPPED7="You stopped the squad.";var TECHNICAL_STOP="The step stopped on something technical, so squad-kit could not finish it. Press Try again.";var RUN_WORDS=[/^You stopped the squad\.$/,/^The step reached the most it may do in one run\.$/,/^It used the \d+ turns this step may take\.$/,/^It reached the \$[\d.]+ this step may spend\.$/,/^Stopped after \d+ minutes without finishing\.$/,/^It took longer than \d+ minutes, so squad-kit stopped it\.$/,/^The answer was cut off because it got too long\.$/,/^The agent declined to do this step\.$/,/^The step was cancelled\.$/,/^The agent finished without giving squad-kit its answer\.$/,/^squad-kit could not save what the agent was doing, so it stopped the run\. The work so far is kept\.$/,/^[\w .-]{1,40} did not (start|open its session|open a session) in \d+ seconds?\. Check that it opens in a terminal and is logged in, then press Try again\.$/];function ownRunWords(said,tool5){const s=said.trim();return RUN_WORDS.some(re=>re.test(s))||Boolean(tool5?.ownWords?.(s))}function ownWords2(err2){if(err2 instanceof StepError||err2 instanceof NoRoomForCopy)return err2.message;if(err2 instanceof Error&&err2.message===STOPPED7)return STOPPED7;return null}import fs100 from"fs";import os14 from"os";import path93 from"path";var CONVERSATIONS=[{key:"intake",label:"Analyst and Architect"},{key:"build",label:"Builder"},{key:"review",label:"Reviewer"},{key:"tester",label:"Tester"}];function blockedMeta(output){const found2=blockedIn(output);return found2.length?{squadKit:{blocked:found2.map(b=>blockedLine(b))}}:null}function withBlocked(updates){return updates.map(e=>{const u=e.update;const meta=u.sessionUpdate==="tool_call_update"?blockedMeta(outputOf(u)):null;return meta?{...e,update:{...u,_meta:{...u._meta??{},...meta}}}:e})}function sessionFile2(id2,base2=path93.join(os14.homedir(),".claude","projects")){if(!/^[\w-]{8,64}$/.test(id2))return null;try{for(const d of fs100.readdirSync(base2)){const file3=path93.join(base2,d,`${id2}.jsonl`);if(fs100.existsSync(file3))return file3}}catch{}return null}var SYSTEM_PROMPT=/^\s*(<task-notification|<system-reminder|\[structured-output-enforce\]|<command-|Caveat:)/;var MAX_TEXT=2e4;var MAX_RESULT=4e3;var MAX_IMAGE=3e6;var MEDIA={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",webm:"video/webm",mov:"video/quicktime",mp3:"audio/mpeg",wav:"audio/wav",ogg:"audio/ogg",weba:"audio/webm",opus:"audio/ogg",oga:"audio/ogg",m4a:"audio/mp4",aac:"audio/aac",pdf:"application/pdf",csv:"text/csv",json:"application/json",md:"text/markdown",txt:"text/plain",html:"text/html",zip:"application/zip",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",har:"application/json",log:"text/plain"};function dispositionOf(kind,file3){const name=path93.basename(file3);return/^[\x20-\x7e]+$/.test(name)?`${kind}; filename="${name.replace(/"/g,"")}"`:`${kind}; filename*=UTF-8''${encodeURIComponent(name)}`}function mimeOf(file3){return MEDIA[path93.extname(file3).slice(1).toLowerCase()]??"application/octet-stream"}var SHOWN=/\.(png|jpe?g|gif|webp|svg|mp4|webm|mov|mp3|wav|ogg|pdf|csv|xlsx|docx|zip|har)$/i;function clip(s,max){const t=s.trim();return t.length>max?`${t.slice(0,max)}
988
- \u2026 (${t.length-max} more characters)`:t}function text2(t,meta){return{type:"text",text:t,...meta?{_meta:meta}:{}}}function kindOf2(name){switch(name){case"Read":return"read";case"Edit":case"MultiEdit":case"Write":case"NotebookEdit":return"edit";case"Glob":case"Grep":case"LS":return"search";case"Bash":return"execute";case"WebFetch":case"WebSearch":return"fetch";case"TodoWrite":return"think";default:return"other"}}function diffsOf(name,input7){const file3=typeof input7.file_path==="string"?input7.file_path:"";if(!file3)return[];const str3=v=>typeof v==="string"?clip(v,MAX_TEXT):"";if(name==="Edit")return[{type:"diff",path:file3,oldText:str3(input7.old_string),newText:str3(input7.new_string)}];if(name==="Write")return[{type:"diff",path:file3,oldText:null,newText:str3(input7.content)}];if(name==="MultiEdit"&&Array.isArray(input7.edits)){return input7.edits.map(e=>({type:"diff",path:file3,oldText:str3(e.old_string),newText:str3(e.new_string)}))}return[]}function filesIn(textToScan,cwd){const found2=new Set;for(const m of textToScan.matchAll(/(?:^|[\s"'=(:])((?:\/|\.{0,2}\/)?[\w@./-]+\.(?:png|jpe?g|gif|webp|svg|mp4|webm|mov|mp3|wav|ogg|pdf|csv|xlsx|docx|zip|har))\b/gi)){const p=m[1];const abs=path93.isAbsolute(p)?p:cwd?path93.resolve(cwd,p):"";if(abs&&SHOWN.test(abs)&&fs100.existsSync(abs))found2.add(abs);if(found2.size>=12)break}return[...found2].map(abs=>({type:"content",content:{type:"resource_link",uri:`file://${abs}`,name:path93.basename(abs),mimeType:mimeOf(abs),size:fs100.statSync(abs).size}}))}function resultContent(content){if(typeof content==="string")return content.trim()?[{type:"content",content:text2(clip(content,MAX_RESULT))}]:[];if(!Array.isArray(content))return[];const out17=[];for(const c of content){if(c?.type==="text"&&c.text?.trim())out17.push({type:"content",content:text2(clip(c.text,MAX_RESULT))});if(c?.type==="image"&&c.source?.data){out17.push({type:"content",content:c.source.data.length<=MAX_IMAGE?{type:"image",data:c.source.data,mimeType:c.source.media_type??"image/png"}:text2("(a large image was left out)")})}}return out17}function planOf(input7){if(!Array.isArray(input7.todos))return[];return input7.todos.map(t=>({content:String(t.content??""),priority:"medium",status:t.status==="completed"?"completed":t.status==="in_progress"?"in_progress":"pending"}))}function readConversation(id2,limit=400,base2,nativeDir){const native=nativeDir&&/^[\w-]{8,64}$/.test(id2)?path93.join(nativeDir,`${id2}.jsonl`):null;if(native&&fs100.existsSync(native)){const updates2=[];for(const raw of fs100.readFileSync(native,"utf8").split("\n")){try{if(raw.trim())updates2.push(JSON.parse(raw))}catch{}}const cwd2=cwdOf(id2,base2);return{sessionId:id2,...cwd2?{cwd:cwd2}:{},updates:withBlocked(updates2.slice(-limit)),total:updates2.length}}const file3=sessionFile2(id2,base2);if(!file3)return null;const updates=[];const toolFiles=new Map;const toolContent=new Map;const answers2=new Set;let cwd;for(const raw of fs100.readFileSync(file3,"utf8").split("\n")){if(!raw.trim())continue;let line3;try{line3=JSON.parse(raw)}catch{continue}if(line3.isSidechain||line3.type!=="user"&&line3.type!=="assistant")continue;cwd??=line3.cwd;const at3=line3.timestamp;const push=update=>updates.push({...at3?{at:at3}:{},update});const content=line3.message?.content;const blocks2=typeof content==="string"?[{type:"text",text:content}]:content??[];for(const b of blocks2){if(b.type==="text"&&b.text?.trim()){if(line3.type==="user"){const from=SYSTEM_PROMPT.test(b.text)?"claude-code":"squad-kit";push({sessionUpdate:"user_message_chunk",content:text2(clip(b.text,MAX_TEXT),{squadKit:{from}})})}else{push({sessionUpdate:"agent_message_chunk",content:text2(clip(b.text,MAX_TEXT))})}}else if(b.type==="thinking"&&b.thinking?.trim()){push({sessionUpdate:"agent_thought_chunk",content:text2(clip(b.thinking,MAX_RESULT))})}else if(b.type==="tool_use"&&b.name&&b.id){const input7=b.input??{};if(b.name==="TodoWrite"){const entries=planOf(input7);if(entries.length)push({sessionUpdate:"plan",entries});continue}if(b.name==="StructuredOutput")answers2.add(b.id);const title2=b.name==="StructuredOutput"?"Handed in its answer":describeToolEvent({name:b.name,input:input7},cwd??"/")??`Used ${b.name}`;const filePath=typeof input7.file_path==="string"?input7.file_path:typeof input7.path==="string"?input7.path:"";const command=typeof input7.command==="string"?input7.command:"";if(command)toolFiles.set(b.id,command);const callContent=[...diffsOf(b.name,input7),...b.name!=="Read"&&filePath&&SHOWN.test(filePath)?filesIn(filePath,cwd):[]];toolContent.set(b.id,callContent);push({sessionUpdate:"tool_call",toolCallId:b.id,title:title2,kind:kindOf2(b.name),status:"in_progress",content:callContent,...filePath?{locations:[{path:filePath}]}:{},rawInput:command?{command}:filePath?{path:filePath}:{}})}else if(b.type==="tool_result"&&b.tool_use_id){const resultText=typeof b.content==="string"?b.content:Array.isArray(b.content)?b.content.map(c=>c.text??"").join("\n"):"";const blocked=blockedMeta(resultText);push({sessionUpdate:"tool_call_update",toolCallId:b.tool_use_id,...blocked?{_meta:blocked}:{},status:b.is_error?"failed":"completed",content:answers2.has(b.tool_use_id)?[]:[...toolContent.get(b.tool_use_id)??[],...resultContent(b.content),...filesIn(`${toolFiles.get(b.tool_use_id)??""}
990
+ \u2026 (${t.length-max} more characters)`:t}function text2(t,meta){return{type:"text",text:t,...meta?{_meta:meta}:{}}}function kindOf2(name){switch(name){case"Read":return"read";case"Edit":case"MultiEdit":case"Write":case"NotebookEdit":return"edit";case"Glob":case"Grep":case"LS":return"search";case"Bash":return"execute";case"WebFetch":case"WebSearch":return"fetch";case"TodoWrite":return"think";default:return"other"}}function diffsOf(name,input7){const file3=typeof input7.file_path==="string"?input7.file_path:"";if(!file3)return[];const str3=v=>typeof v==="string"?clip(v,MAX_TEXT):"";if(name==="Edit")return[{type:"diff",path:file3,oldText:str3(input7.old_string),newText:str3(input7.new_string)}];if(name==="Write")return[{type:"diff",path:file3,oldText:null,newText:str3(input7.content)}];if(name==="MultiEdit"&&Array.isArray(input7.edits)){return input7.edits.map(e=>({type:"diff",path:file3,oldText:str3(e.old_string),newText:str3(e.new_string)}))}return[]}function filesIn(textToScan,cwd){const found2=new Set;for(const m of textToScan.matchAll(/(?:^|[\s"'=(:])((?:\/|\.{0,2}\/)?[\w@./-]+\.(?:png|jpe?g|gif|webp|svg|mp4|webm|mov|mp3|wav|ogg|pdf|csv|xlsx|docx|zip|har))\b/gi)){const p=m[1];const abs=path93.isAbsolute(p)?p:cwd?path93.resolve(cwd,p):"";if(abs&&SHOWN.test(abs)&&fs100.existsSync(abs))found2.add(abs);if(found2.size>=12)break}return[...found2].map(abs=>({type:"content",content:{type:"resource_link",uri:`file://${abs}`,name:path93.basename(abs),mimeType:mimeOf(abs),size:fs100.statSync(abs).size}}))}function resultContent(content){if(typeof content==="string")return content.trim()?[{type:"content",content:text2(clip(content,MAX_RESULT))}]:[];if(!Array.isArray(content))return[];const out17=[];for(const c of content){if(c?.type==="text"&&c.text?.trim())out17.push({type:"content",content:text2(clip(c.text,MAX_RESULT))});if(c?.type==="image"&&c.source?.data){out17.push({type:"content",content:c.source.data.length<=MAX_IMAGE?{type:"image",data:c.source.data,mimeType:c.source.media_type??"image/png"}:text2("(a large image was left out)")})}}return out17}function planOf2(input7){if(!Array.isArray(input7.todos))return[];return input7.todos.map(t=>({content:String(t.content??""),priority:"medium",status:t.status==="completed"?"completed":t.status==="in_progress"?"in_progress":"pending"}))}function readConversation(id2,limit=400,base2,nativeDir){const native=nativeDir&&/^[\w-]{8,64}$/.test(id2)?path93.join(nativeDir,`${id2}.jsonl`):null;if(native&&fs100.existsSync(native)){const updates2=[];for(const raw of fs100.readFileSync(native,"utf8").split("\n")){try{if(raw.trim())updates2.push(JSON.parse(raw))}catch{}}const cwd2=cwdOf(id2,base2);return{sessionId:id2,...cwd2?{cwd:cwd2}:{},updates:withBlocked(updates2.slice(-limit)),total:updates2.length}}const file3=sessionFile2(id2,base2);if(!file3)return null;const updates=[];const toolFiles=new Map;const toolContent=new Map;const answers2=new Set;let cwd;for(const raw of fs100.readFileSync(file3,"utf8").split("\n")){if(!raw.trim())continue;let line3;try{line3=JSON.parse(raw)}catch{continue}if(line3.isSidechain||line3.type!=="user"&&line3.type!=="assistant")continue;cwd??=line3.cwd;const at3=line3.timestamp;const push=update=>updates.push({...at3?{at:at3}:{},update});const content=line3.message?.content;const blocks2=typeof content==="string"?[{type:"text",text:content}]:content??[];for(const b of blocks2){if(b.type==="text"&&b.text?.trim()){if(line3.type==="user"){const from=SYSTEM_PROMPT.test(b.text)?"claude-code":"squad-kit";push({sessionUpdate:"user_message_chunk",content:text2(clip(b.text,MAX_TEXT),{squadKit:{from}})})}else{push({sessionUpdate:"agent_message_chunk",content:text2(clip(b.text,MAX_TEXT))})}}else if(b.type==="thinking"&&b.thinking?.trim()){push({sessionUpdate:"agent_thought_chunk",content:text2(clip(b.thinking,MAX_RESULT))})}else if(b.type==="tool_use"&&b.name&&b.id){const input7=b.input??{};if(b.name==="TodoWrite"){const entries=planOf2(input7);if(entries.length)push({sessionUpdate:"plan",entries});continue}if(b.name==="StructuredOutput")answers2.add(b.id);const title2=b.name==="StructuredOutput"?"Handed in its answer":describeToolEvent({name:b.name,input:input7},cwd??"/")??`Used ${b.name}`;const filePath=typeof input7.file_path==="string"?input7.file_path:typeof input7.path==="string"?input7.path:"";const command=typeof input7.command==="string"?input7.command:"";if(command)toolFiles.set(b.id,command);const callContent=[...diffsOf(b.name,input7),...b.name!=="Read"&&filePath&&SHOWN.test(filePath)?filesIn(filePath,cwd):[]];toolContent.set(b.id,callContent);push({sessionUpdate:"tool_call",toolCallId:b.id,title:title2,kind:kindOf2(b.name),status:"in_progress",content:callContent,...filePath?{locations:[{path:filePath}]}:{},rawInput:command?{command}:filePath?{path:filePath}:{}})}else if(b.type==="tool_result"&&b.tool_use_id){const resultText=typeof b.content==="string"?b.content:Array.isArray(b.content)?b.content.map(c=>c.text??"").join("\n"):"";const blocked=blockedMeta(resultText);push({sessionUpdate:"tool_call_update",toolCallId:b.tool_use_id,...blocked?{_meta:blocked}:{},status:b.is_error?"failed":"completed",content:answers2.has(b.tool_use_id)?[]:[...toolContent.get(b.tool_use_id)??[],...resultContent(b.content),...filesIn(`${toolFiles.get(b.tool_use_id)??""}
989
991
  ${resultText}`,cwd)]})}}}return{sessionId:id2,...cwd?{cwd}:{},updates:updates.slice(-limit),total:updates.length}}function cwdOf(id2,base2){const file3=sessionFile2(id2,base2);if(!file3)return void 0;const head=fs100.readFileSync(file3,"utf8").slice(0,2e4);return head.match(/"cwd":"((?:[^"\\]|\\.)*)"/)?.[1]?.replace(/\\(.)/g,"$1")}function ticketConversations(t,base2,nativeDir){return CONVERSATIONS.flatMap(c=>{const id2=t.sessions?.[c.key];if(!id2)return[];const started=Boolean(sessionFile2(id2,base2))||Boolean(nativeDir&&fs100.existsSync(path93.join(nativeDir,`${id2}.jsonl`)));return[{key:c.key,label:c.label,sessionId:id2,started}]})}function ticketFolders(t,bp){return[t.storyDir??t.worktree,bp.proofDir,ticketDir(bp,t.id),ticketMediaDir(bp,t.id)].filter(d=>Boolean(d))}function allowedFile(file3,folders){try{const real2=fs100.realpathSync(file3);const ok2=folders.some(f=>{try{const root=fs100.realpathSync(f);return real2===root||real2.startsWith(root+path93.sep)}catch{return false}});return ok2&&fs100.statSync(real2).isFile()?real2:null}catch{return null}}function chatSummaries(tickets,q=""){const needle=q.trim().toLowerCase();const out17=[];for(const t of tickets){const c=t.chat;if(!c||!c.open&&!c.messages.length)continue;const last=c.messages.at(-1);const mine2=c.messages.map(m=>m.who).lastIndexOf("you");const fresh=c.messages.slice(mine2+1).filter(m=>m.who!=="you"&&m.who!=="squad").length;let match;if(needle&&!`${t.id} ${t.title}`.toLowerCase().includes(needle)){const hit=c.messages.find(m=>m.text.toLowerCase().includes(needle));if(!hit)continue;const i=hit.text.toLowerCase().indexOf(needle);match=`${i>40?"\u2026":""}${hit.text.slice(Math.max(0,i-40),i+needle.length+60).replace(/\s+/g," ").trim()}`}out17.push({id:t.id,title:t.title,stage:t.stage,...t.release?{release:t.release}:{},with:c.with,open:c.open,thinking:Boolean(c.thinking),beside:Boolean(c.beside),yourTurn:c.open&&!c.thinking&&Boolean(last)&&last.who!=="you",fresh,count:c.messages.length,at:last?.at??t.activity[0]?.at??t.createdAt,...last?{last:{who:last.who,text:last.text.slice(0,240),at:last.at}}:{},...match?{match}:{}})}return out17.sort((a,b)=>Number(b.open)-Number(a.open)||b.at.localeCompare(a.at))}var QUALITY_NAME={best:"Best",balanced:"Balanced",thrifty:"Thrifty"};var CapReached=class extends Error{constructor(role,step2,by2,caps,quality,who4,run5){super(capWords(who4,by2,caps,quality));this.role=role;this.step=step2;this.by=by2;this.caps=caps;this.quality=quality;this.who=who4;this.run=run5;this.name="CapReached"}role;step;by;caps;quality;who;run};function capWords(who4,by2,caps,quality){const what=by2==="turns"?`used the ${caps.turns} turns one step may take`:`reached the $${caps.spendUsd} one step may spend`;return`The ${who4} ${what} on ${QUALITY_NAME[quality]}, so squad-kit paused it. Its work so far is kept.`}function capCard(cap4){return{gate:"cap",question:cap4.message,options:[{do:"more",name:"Give it more",note:"It carries on where it stopped, with twice the turns and spend for this step."},{do:"stop",name:"Stop",note:"Leave it here. Nothing is lost: you can carry on later, fix it yourself or send it back to the plan."}]}}import fs101 from"fs";import path94 from"path";var ProductError=class extends Error{constructor(message,detail){super(message);this.detail=detail}detail};function mainCopyDir(bp,repo){return path94.join(productCopiesDir(bp),repo.replace(/[^\w.-]+/g,"-"))}var productCopiesDir=bp=>path94.join(bp.localDir,"product");function changedParts(meta,t){if(t.parts?.length)return t.parts;const repo=repoFor(meta,t).path;return partsOf(meta).filter(p=>p.repo===repo).map(p=>p.id)}function productPlan(bp,meta,t){const copies=copiesOf(meta,t);const parts2=partsToRun(meta,changedParts(meta,t));const places=[];for(const part of parts2){const repo=reposOf(meta).find(r=>r.path===part.repo);if(!repo||places.some(p=>p.repo.path===repo.path))continue;const inRepo=parts2.filter(p=>p.repo===repo.path);const front=inRepo.find(p=>!inRepo.some(x=>x.needs.includes(p.id)))??part;const start=repo.commands.start?.trim();const url=(repo.commands.url??front.url)?.trim();if(!start)return{places:[],why:`squad-kit does not know how to start ${front.name} yet. An engineer can set it in Settings.`};if(!url)return{places:[],why:`squad-kit does not know the address of ${front.name} yet. An engineer can set it in Settings.`};const copy2=copies.find(c=>c.repo.path===repo.path);places.push({part:front,repo,dir:copy2?.dir??mainCopyDir(bp,repo.path),start,url,fromStory:Boolean(copy2)})}const needed=new Set(places.flatMap(p=>p.part.needs));const tops=places.filter(p=>!needed.has(p.part.id));const changed=changedParts(meta,t);return{places,...places.length?{front:tops.find(p=>changed.includes(p.part.id))??tops[0]??places.at(-1)}:{}}}var guardOf2=(bp,t,dir)=>{if(!guardSupport().on)return void 0;const allow=readMeta(bp).guard;return ticketGuard(bp,{cwd:dir,id:t.id,...allow?{allow}:{}})};async function readyMainCopy(bp,t,place,signal2){const{dir,repo}=place;const{commit,fresh}=await updateCopy(bp.root,repo,dir,signal2?{signal:signal2}:{});await fillOwnCopy(bp,repo.path,dir);const setup=repo.commands.setup?.trim();if(!setup)return;const print=await dependencyPrint(dir,commit);const mark=`${dir}.setup`;if(!fresh&&fs101.existsSync(mark)&&fs101.readFileSync(mark,"utf8")===print)return;const guard=guardOf2(bp,t,dir);const r=await runCommand(dir,setup,SETUP_MS,{silentMs:CHECK_SILENT_MS,...signal2?{signal:signal2}:{},...guard?{guard}:{}});if(!r.ok)throw new ProductError(dockerBlockedWords(r.output,guard)??`squad-kit could not set up ${place.part.name}.`,`${setup}
990
992
  ${tail(r.output,30)}`);await fillOwnCopy(bp,repo.path,dir);fs101.writeFileSync(mark,print)}var running2=new Map;var keyOf2=(bp,holder)=>`${bp.root}
991
993
  ${holder}`;function stopProduct(bp,holder){const key=keyOf2(bp,holder);for(const{app}of running2.get(key)??[])app.stop();running2.delete(key);leaveProduct(bp,holder)}async function startProduct(bp,t,holder,kind,o={}){const plan=productPlan(bp,readMeta(bp),t);if(plan.why)throw new ProductError(plan.why);await waitForProduct(bp,holder,kind,{...o.signal?{signal:o.signal}:{},...o.onWait?{onWait:o.onWait}:{},...o.limitMs?{limitMs:o.limitMs}:{},...o.lookMs?{lookMs:o.lookMs}:{}});const key=keyOf2(bp,holder);const list2=running2.get(key)??[];running2.set(key,list2);try{let front;for(const place of plan.places){if(!place.fromStory)await readyMainCopy(bp,t,place,o.signal);const was=list2.find(r=>r.repo===place.repo.path);if(was&&was.app.ended?.())list2.splice(list2.indexOf(was),1);let app=was&&!was.app.ended?.()?was.app:void 0;if(!app){o.log?.(`Starting ${place.part.name} (${place.fromStory?"the story's copy":"main"}) at ${place.url}: ${place.start}`);app=await startPart(bp,t,place,o);list2.push({app,repo:place.repo.path})}if(place===plan.front)front=app}return front}catch(err2){stopProduct(bp,holder);throw err2}}var composeStops=new Map;async function startPart(bp,t,place,o){await composeStops.get(place.url);const guard=guardOf2(bp,t,place.dir);const compose=composeRun(readMeta(bp),place.repo.path,place.start,place.dir,bp.root);if(compose&&"why"in compose)throw new ProductError(compose.why);const own2=compose&&guard?dockerGuard(guard,compose):guard;try{const app=await startApp(place.dir,compose?.command??place.start,place.url,o.waitMs??(compose?DOCKER_WAIT_MS:void 0),o.signal,own2,{fixed:true,...compose?{env:compose.env}:{}});if(!compose)return app;return{...app,stop:()=>{app.stop();const stopped=runCommand(place.dir,compose.stop,DOCKER_STOP_MS,{...own2?{guard:own2}:{},env:compose.env}).then(()=>void 0,()=>void 0);composeStops.set(place.url,stopped);void stopped.then(()=>composeStops.delete(place.url))}}}catch(err2){throw productFailure(err2,place,own2)}}function productFailure(err2,place,guard){const message=err2.message;if(err2 instanceof ProductError)return err2;if(err2 instanceof AddressTaken)return new ProductError(`Another program on this computer is using the address of ${place.part.name}, so squad-kit could not run the product. Close that program, then press Try again.`,message);return new ProductError(dockerBlockedWords(message,guard)??`squad-kit could not start ${place.part.name}.`,message)}function waitWords2(hold){if(hold.kind==="apps")return"Waiting for Your apps: they use the same addresses. Stop them, and this goes on.";const who4=hold.holder.replace(/^try:/,"");return hold.kind==="try"?`Waiting for you to finish trying ${who4}: it uses the same addresses.`:`Waiting for ${who4}: only one story at a time runs the product.`}function answerProblem(schema,value){if(value===void 0||value===null)return"there is no answer";return problem(schema,value,"")}function problem(shape2,value,at3){const name=at3||"the answer";if(shape2.type&&!fits(shape2.type,value))return`${name} is not ${KIND[shape2.type]??shape2.type}`;if(shape2.enum&&!shape2.enum.includes(value))return`${name} is not one of ${shape2.enum.join(", ")}`;if(Array.isArray(value)){if(shape2.minItems!==void 0&&value.length<shape2.minItems)return`${name} has fewer than ${shape2.minItems} items`;if(shape2.maxItems!==void 0&&value.length>shape2.maxItems)return`${name} has more than ${shape2.maxItems} items`;if(shape2.items){for(const[i,item]of value.entries()){const p=problem(shape2.items,item,`${at3||"item"}[${i}]`);if(p)return p}}}else if(typeof value==="object"&&value!==null){const o=value;for(const key of shape2.required??[])if(o[key]===void 0||o[key]===null)return`${at3?`${at3}.`:""}${key} is missing`;for(const[key,sub]of Object.entries(shape2.properties??{})){if(o[key]===void 0||o[key]===null)continue;const p=problem(sub,o[key],at3?`${at3}.${key}`:key);if(p)return p}}return null}var KIND={object:"an object",array:"a list",string:"text",boolean:"true or false",integer:"a whole number",number:"a number",null:"empty"};function fits(type,v){switch(type){case"object":return typeof v==="object"&&v!==null&&!Array.isArray(v);case"array":return Array.isArray(v);case"string":return typeof v==="string";case"boolean":return typeof v==="boolean";case"integer":return Number.isInteger(v);case"number":return typeof v==="number"&&Number.isFinite(v);case"null":return v===null;default:return true}}function dependsProblem(value,open3,self){const list2=value?.dependsOn;if(list2===void 0||list2===null)return null;if(!Array.isArray(list2))return"dependsOn is not a list";const known=new Set(open3.map(x=>x.toUpperCase()));for(const item of list2){if(typeof item!=="string")return"dependsOn has an item that is not text";const name=item.trim().toUpperCase();if(name===self.toUpperCase())return`dependsOn names ${self}, this ticket itself`;if(!known.has(name))return`dependsOn names ${item.trim()||"an empty name"}, which is not an open ticket on this board${open3.length?` (open: ${open3.join(", ")})`:" (there are none)"}`}return null}var OVERVIEW_PARTS=["users","can","worth","ideas","parts","links"];function scanProblem(value){if(!value||typeof value!=="object")return null;const v=value;if(v.empty!==true){for(const key of OVERVIEW_PARTS)if(v[key]===void 0||v[key]===null)return`${key} is missing`}return partsProblem(value)}function partsProblem(value){if(!value||typeof value!=="object")return null;const v=value;const ids=(Array.isArray(v.parts)?v.parts:[]).map(p=>p&&typeof p==="object"?p.id:void 0);const twice=ids.find((id2,i)=>ids.indexOf(id2)!==i);if(twice!==void 0)return`two parts have the id "${String(twice)}"`;const known=new Set(ids);for(const[i,l]of(Array.isArray(v.links)?v.links:[]).entries()){const link=l&&typeof l==="object"?l:{};for(const end of["from","to"]){if(!known.has(link[end]))return`links[${i}].${end} names no part ("${String(link[end])}")`}}return null}function portHolder(p){return p.app?`your app ${p.app}`:"a program squad-kit started earlier"}function portWords(p){if(p.ours)return`${upperFirst(portHolder(p))} is in the way, so the app does not answer.`;return"Another program on this computer is in the way, so the app does not answer. squad-kit leaves it alone. Close that program, then press Stop and try again."}function portDetail(p){return`${p.program||"A program"}${p.pid?` (process ${p.pid})`:""} listens on port ${p.port}, the port in the app's address${p.app?`: your app ${p.app}, which squad-kit runs from Your apps`:p.ours?", and squad-kit started it":""}.`}function portParts(q){if(!q.port)return{};return{fact:{label:"What is in the way",value:portWords(q.port)},...q.port.ours?{freePort:{port:q.port.port,what:portHolder(q.port)}}:{}}}function slowDetail(q){const lines3=[q.command?`Waiting on: ${q.command}${q.pid?` (process ${q.pid})`:""}`:"",q.port?portDetail(q.port):""].filter(Boolean);return lines3.length?lines3.join("\n"):void 0}var RESUMED="resumed after a restart";var QUEUED2="Next in line";function queuedLine(busy,limit){return`${QUEUED2}: starts when a slot is free (${busy} of ${limit} busy)`}var TRACK=["tickets","specs","implement","test","review","release"];var YOUR_PART={questions:"answer the questions",spec:"approve the spec",plan:"approve the design",proof:"check the proof",stuck:"pick a way out",release:"release it",ask:"answer the question",yours:"give it back",chat:"finish the talk",change:"decide on the design update",depends:"choose how to build it"};function lowerFirst2(s){return/^[A-Z][A-Z0-9]+\b/.test(s)?s:s.charAt(0).toLowerCase()+s.slice(1)}function upperFirst(s){return s.charAt(0).toUpperCase()+s.slice(1)}function shortDoing(line3){const s=line3.replace(/(?:[\w.@~-]*\/)+([\w.@-]+)/g,"$1").replace(/\s+/g," ").trim();return lowerFirst2(s.length>56?`${s.slice(0,55)}\u2026`:s)}function stateOf(t){if(t.stage==="done")return"done";if(t.waiting||t.takenOver)return"you";if(t.stopRequested)return"stopping";if(t.status.startsWith(QUEUED2))return"queued";if(t.stage==="release")return"ready";if(t.squad||t.live)return"working";return"idle"}function guessClock(t){const s=t.status;const doing=t.steps?.findIndex(x=>x.status==="doing")??-1;switch(t.stage){case"tickets":case"bugs":return{who:"analyst",step:"questions"};case"specs":return/^Analyst/.test(s)||t.track!=="express"&&!t.specApproved?{who:"analyst",step:"spec"}:{who:"architect",step:"design"};case"implement":return doing>=0?{who:"builder",step:"build",part:doing+1}:{who:"builder",step:/fix/i.test(s)?"fix":"build"};case"test":return/Tester|screen|screenshot/i.test(s)?{who:"tester",step:"proof"}:/Builder/.test(s)?{who:"builder",step:"fix"}:{who:"squad",step:"checks"};default:return/Builder/.test(s)?{who:"builder",step:"fix"}:/Tester|proof|screen/i.test(s)?{who:"tester",step:"proof"}:{who:"reviewer",step:"review"}}}var SENTENCE={questions:()=>"The Analyst is reading the ticket to see what it needs to ask you.",spec:t=>t.questions?.some(q=>q.answer)?"The Analyst is writing the spec from your answers.":'The Analyst is writing the spec: the story and the "done when" list.',design:t=>t.track==="express"?"The Architect is writing the plan.":t.track==="auto"?"The Architect is reading the part of the BRD and the code, and writing the design.":"The Architect is choosing how to build it and writing the design.",build:()=>"The Builder is writing the code.",fix:()=>"The Builder is fixing what the checks or the Reviewer found.",checks:()=>"squad-kit is running the project's own checks, so the squad never grades its own work.",review:()=>"The Reviewer, a fresh agent, is reading the changes.",proof:()=>'The Tester is opening the app and taking a screenshot for each "done when".'};function screens(t){return changesScreens(t)}var MERGES_BY_ITSELF="nothing: it is accepted and merged by itself";function ahead(t,step2,part){const after=["review",...screens(t)?["proof"]:[]];const proof={what:t.track==="auto"?MERGES_BY_ITSELF:"check the proof"};switch(step2){case"questions":return{steps:[],builds:0,what:"answer its questions, or approve the spec"};case"spec":return{steps:[],builds:0,what:"approve the spec"};case"design":if(t.track!=="express"&&t.track!=="auto")return{steps:[],builds:0,what:"approve the design"};return{steps:["checks",...after],builds:t.work?.steps??t.steps?.length??0,...proof};case"build":return{steps:["checks",...after],builds:part?part.of-part.n:0,...proof};case"fix":return{steps:["checks",...after],builds:0,...proof};case"checks":return{steps:after,builds:0,...proof};case"review":return{steps:screens(t)?["proof"]:[],builds:0,...proof};case"proof":return{steps:[],builds:0,...proof}}}function nextWords(t,step2,part){const shots=screens(t)?', then the Tester takes a screenshot for each "done when"':"";const ready=t.track==="auto"?"The squad accepts it by itself and merges it.":"The proof is ready for you to check.";switch(step2){case"questions":return"The Analyst asks you what it needs to know, or writes the spec straight away.";case"spec":return"You read the spec and approve it. Then the Architect writes the design.";case"design":return t.track==="express"?"Express track: the plan is approved on its own, then the Builder starts and the checks and the Reviewer follow.":t.track==="auto"?"Builds by itself: the plan is approved on its own, then the Builder starts and the checks and the Reviewer follow.":"You read the design and approve it. Then the Builder starts.";case"build":return part&&part.n<part.of?`The Builder goes on with step ${part.n+1} of ${part.of}. Then the checks run on their own, a fresh Reviewer reads the work${shots}.`:`The checks run on their own, a fresh Reviewer reads the work${shots}.`;case"fix":return`The checks run again on their own, then the Reviewer reads the work${shots}.`;case"checks":return`The Reviewer, a fresh agent, reads the changes${shots}.`;case"review":return screens(t)?'The Tester takes a screenshot for each "done when".':ready;case"proof":return ready}}function inAbout(seconds2){const m=Math.round(seconds2/60);return m<=1?"in about a minute":`in about ${m} minutes`}function barOf(t,step2,part){if(part)return Array.from({length:part.of},(_,i)=>i+1<part.n?"done":i+1===part.n?"now":"todo");const group=["questions","spec","design"].includes(step2)?t.track==="express"||t.track==="auto"?["design"]:["questions","spec","design"]:["checks","review",...screens(t)?["proof"]:[]];const at3=group.indexOf(step2==="fix"||step2==="build"?"checks":step2);return group.map((_,i)=>i<at3?"done":i===at3?"now":"todo")}function stageNotes(t,state,book,at3){const stage=t.stage==="bugs"?"tickets":t.stage;const cur=stage==="done"?TRACK.length:TRACK.indexOf(stage);const st=t.stageTimes;const usual=(who4,step2)=>middle(usualOf(book,who4,step2,t.track));const about=s=>s>0?`about ${Math.max(1,Math.round(s/60))} min`:"";return TRACK.map((s,i)=>{let note2="";if(i<cur){const spent=st?.spent[s];const yours=t.track!=="express"&&t.track!=="auto"&&t.specApproved;note2=spent!==void 0?`${durationWords(spent)}${s==="specs"&&yours?", you approved":""}`:""}else if(i===cur){const inStage=st?.stage===t.stage?(at3-Date.parse(st.since))/1e3:void 0;if(state==="you")note2="your turn";else if(state==="ready")note2=t.release?`in ${t.release}`:"accepted";else if(state==="queued")note2="next in line";else if(state==="working"||state==="stopping")note2=inStage!==void 0?`now \xB7 ${durationWords(inStage)}`:"now"}else if(s==="review")note2=t.track==="auto"?"accepted by itself":"you check proof";else if(s==="release")note2=t.track==="auto"?"merges by itself":"on Looks right";else if(s==="specs")note2=about(t.track==="express"||t.track==="auto"?usual("architect","design"):usual("analyst","spec")+usual("architect","design"));else if(s==="implement")note2=about((t.work?.steps??t.steps?.length??0)*usual("builder","build"));else if(s==="test")note2=about(usual("squad","checks"));return{stage:s,note:note2}})}function noteWhenWords(t,now2){switch(now2.state){case"working":{const left=now2.usual?Math.max(60,middle(now2.usual)-(now2.seconds??0)):60;if(now2.role==="squad")return`squad-kit is running the checks. The next agent reads it when its step starts, ${inAbout(left)}.`;return`The ${now2.who} reads it when its next step starts, ${inAbout(left)}.`}case"you":return`No agent is running now, so nobody reads it yet. The squad reads it when it carries on, after you ${t.waiting?YOUR_PART[t.waiting.kind]:"give it back"}.`;case"queued":return"No agent is running now, so nobody reads it yet. It is next in line: the squad reads it when a slot is free and its step starts.";case"stopping":return"The squad is stopping. It reads it when the work carries on.";case"ready":case"done":return"The work is accepted, so no agent works on it any more. The note stays on the ticket for the record.";default:return"No agent is running now, so nobody reads it yet. The squad reads it when work on the ticket starts."}}function slowCard(t,c,part,lastAt,at3){const q=t.quiet;if(!q)return void 0;const who4=WHO[c.who];const where=`${t.id} \xB7 ${part?`step ${part.n} of ${part.of}`:who4}`;const limit=c.limit?`If you do nothing, squad-kit stops this step at ${c.limit} minutes. All work is kept.`:void 0;const port=portParts(q);const detail=slowDetail(q);if(q.kind==="stuck"){const quiet=Math.max(0,(at3-Date.parse(q.since))/1e3);return{kind:"stuck",title:"Looks stuck",where,text:`No progress for ${minutesWords(quiet)}.${c.doing?` ${c.who==="squad"?"squad-kit":`The ${who4}`} was ${lowerFirst2(c.doing)}.`:""}`,facts:[{label:"What we checked",value:q.detail},...port.fact?[port.fact]:[],{label:"Your work",value:"Safe. Everything done so far is kept."}],...limit?{limit}:{},canEnd:Boolean(q.pid),...port.freePort?{freePort:port.freePort}:{},...detail?{detail}:{}}}const facts=[];if(c.doing)facts.push({label:"Doing now",value:upperFirst(c.doing)});if(lastAt){const ago3=Math.max(0,(at3-Date.parse(lastAt))/1e3);facts.push({label:"Last progress",value:ago3<180?`${minutesWords(ago3)} ago, so it is still working`:`${minutesWords(ago3)} ago`})}if((t.tries??0)>0)facts.push({label:"Why it may be slow",value:`The checks failed ${t.tries===1?"once":`${t.tries} times`}, so the Builder is fixing them (try ${(t.tries??0)+1} of 3).`});else if((t.reviewRound??0)>1)facts.push({label:"Why it may be slow",value:`The Reviewer asked for changes, so this is round ${t.reviewRound} of 2.`});if(port.fact)facts.push(port.fact);return{kind:"long",title:"Taking longer than usual",where,text:q.detail,facts,...limit?{limit}:{},canEnd:Boolean(q.pid),...port.freePort?{freePort:port.freePort}:{},...detail?{detail}:{}}}function liveNow(t,book,at3=Date.now()){const state=stateOf(t);const stages=stageNotes(t,state,book,at3);const base2={state,stages};if(state==="done")return{...base2,line:t.status||"Released",sentence:`It is released${t.release?` in ${t.release}`:""}.`,noteWhen:noteWhenWords(t,{state})};if(state==="ready")return{...base2,line:t.status,sentence:`It is accepted and goes out with ${t.release??"the next release"}. Nothing is needed from you until then.`,noteWhen:noteWhenWords(t,{state})};if(state==="you"){const summary=t.waiting?.summary??"You took it over. Give it back when you are done";return{...base2,line:t.status,sentence:`It waits for you: ${lowerFirst2(summary)}.`,next:`The squad carries on after you ${t.waiting?YOUR_PART[t.waiting.kind]:"give it back"}.`,noteWhen:noteWhenWords(t,{state})}}if(state==="queued"){const m=/\((\d+) of (\d+) busy\)/.exec(t.status);return{...base2,line:t.status,sentence:`It is next in line. It starts when a slot is free${m?` (${m[1]} of ${m[2]} busy)`:""}.`,next:"Nothing is needed from you now.",noteWhen:noteWhenWords(t,{state})}}if(state==="idle")return{...base2,line:t.status,sentence:t.stage==="bugs"?"It is a reported bug, waiting for the squad.":"Nothing runs on it now.",noteWhen:noteWhenWords(t,{state})};const c=t.clock??guessClock(t);const since=t.clock?.since;const who4=WHO[c.who];const steps=t.steps??[];const part=c.who==="builder"&&c.step==="build"&&c.part&&steps.length?{n:Math.min(c.part,steps.length),of:steps.length}:void 0;const usual=usualOf(book,c.who,c.step,t.track);const seconds2=since?stepSeconds({...c,since},at3):void 0;const fromStatus=t.status.startsWith(`${who4} is `)?t.status.slice(who4.length+4):/^Step \d+ of \d+/.test(t.status)||!t.status?"":t.status;const said=t.clock?.doing??(part?steps[part.n-1]?.live:void 0)??fromStatus;const raw=said.startsWith(`${who4} is `)?said.slice(who4.length+4):said;const resumed=t.clock?.resumed&&!(t.clock.doingAt&&Date.parse(t.clock.doingAt)>Date.parse(t.clock.resumed));const doing=resumed?RESUMED:raw?shortDoing(raw):void 0;const lastAt=t.clock?.doingAt??since;if(state==="stopping")return{...base2,who:who4,role:c.who,step:c.step,line:"Stopping\u2026",sentence:`The ${who4} is stopping. The work so far is kept.`,noteWhen:noteWhenWords(t,{state})};const line3=[who4,part?`step ${part.n} of ${part.of}`:"",doing].filter(Boolean).join(" \xB7 ");const time2=seconds2!==void 0?`${durationWords(seconds2)} \xB7 ${usualWords(usual)}`:usualWords(usual);const title2=part?steps[part.n-1]?.title:void 0;const sentence=part&&title2?`The ${who4} is on step ${part.n} of ${part.of}: ${lowerFirst2(title2.replace(/\.$/,""))}.`:t.status.startsWith(`${who4} is `)&&c.who!=="squad"?`The ${t.status.replace(/\.$/,"")}.`:SENTENCE[c.step](t);const detail=[part?`Step ${part.n} of ${part.of}`:"",seconds2!==void 0?`${durationWords(seconds2)} so far`:"",resumed?RESUMED:"",`this step usually takes ${rangeWords(usual)}`].filter(Boolean).join(" \xB7 ");const go=ahead(t,c.step,part);const left=Math.max(0,middle(usual)-(seconds2??0))+go.builds*middle(usualOf(book,"builder","build",t.track))+go.steps.reduce((n,s)=>n+middle(usualOf(book,s==="checks"?"squad":s==="review"?"reviewer":"tester",s,t.track)),0);const then=nextWords(t,c.step,part);const next=t.track==="auto"?`${then} It is accepted and merged by itself ${inAbout(left)}. Nothing is needed from you now.`:`${then} You will be asked to ${go.what} ${inAbout(left)}. Nothing is needed from you now.`;const now2={...base2,who:who4,role:c.who,step:c.step,...part?{part}:{},...doing?{doing}:{},line:line3,time:time2,bar:barOf(t,c.step,part),sentence,detail,next,then,...since?{since}:{},...seconds2!==void 0?{seconds:seconds2}:{},usual,...lastAt?{lastAt}:{},readyIn:{seconds:left,what:go.what},noteWhen:""};now2.noteWhen=noteWhenWords(t,now2);const slow=slowCard(t,t.clock??{...c,doing:raw||void 0},part,lastAt,at3);if(slow)now2.slow=slow;return now2}function portOf3(url){if(!url)return null;try{const port=Number(new URL(url).port);return Number.isInteger(port)&&port>0?port:null}catch{return null}}function needsApp(t){return t.clock?.who==="tester"||/^Starting the app/.test(t.status)}async function lookAtPort(bp,t){if(!needsApp(t))return null;const url=repoFor(readMeta(bp),t).commands.url;const port=portOf3(url);if(!url||!port||!await portTaken(port)||await isUp(url))return null;const ours=squadKitOn(bp,port);const holder=ours?.holder??whoListens(port);return{port,ours:Boolean(ours),...holder?{pid:holder.pid,...holder.program?{program:holder.program}:{}}:{},...ours?.app?{app:ours.app}:{}}}var LOOK_MS=2e4;var looked2=new Map;function watchPort(bp,id2,now2=Date.now()){const key=`${bp.root}:${id2}`;if(now2-(looked2.get(key)??0)<LOOK_MS)return;looked2.set(key,now2);const t=readTicket(bp,id2);if(!t?.quiet)return;void lookAtPort(bp,t).then(port=>{const same5=(a,b)=>JSON.stringify(a??null)===JSON.stringify(b??null);if(same5(readTicket(bp,id2)?.quiet?.port,port))return;updateTicket(bp,id2,x=>{if(!x.quiet)return;if(port)x.quiet.port=port;else delete x.quiet.port})}).catch(()=>void 0)}async function freePortAndRetry(bp,id2,opts={}){const t=requireTicket(bp,id2);const port=t.quiet?.port?.port;if(!port)throw new BoardActionError(`${t.id} is not held up by another program.`);const ours=squadKitOn(bp,port);if(!ours){throw new BoardActionError(whoListens(port)?"squad-kit did not start the program in the way, so it leaves it alone. Close it yourself, then press Stop and try again.":"Nothing squad-kit started is in the way now. Press Stop and try again.")}await endPid(ours.group);const until=Date.now()+(opts.waitMs??3e3);while(await portTaken(port)&&Date.now()<until)await new Promise(r=>setTimeout(r,100));const what=ours.app?`your app ${ours.app}`:"a program squad-kit started earlier";if(await portTaken(port))throw new BoardActionError(`squad-kit stopped ${what}, but something is still in the way. Press Stop and try again in a moment.`);writeLog({level:"info",project:readMeta(bp).project,ticket:t.id,text:`Freed port ${port}: stopped ${ours.holder.program||"a program"} (process ${ours.holder.pid}), which squad-kit started`});updateTicket(bp,t.id,x=>{addActivity(x,"you",`Stopped ${what}, which was in the way`);delete x.quiet});const now2=requireTicket(bp,t.id);return now2.live||now2.squad?restartStep(bp,t.id):now2}import fs102 from"fs";import path95 from"path";var troubles=new Map;var checked2=new Map;var SAVE_CHECK_MS=5e3;var TEST_WRITE=Buffer.alloc(64*1024,32);function canSave(bp){if(!fs102.existsSync(bp.squadDir))return false;for(const dir of[bp.squadDir,bp.localDir]){const file3=path95.join(dir,`.save-check-${process.pid}`);try{fs102.mkdirSync(dir,{recursive:true});fs102.writeFileSync(file3,TEST_WRITE);fs102.rmSync(file3,{force:true})}catch{try{fs102.rmSync(file3,{force:true})}catch{}return false}}return true}function isSaveTrouble(bp,err2){if(!fs102.existsSync(bp.squadDir))return true;if(!isFileTrouble(err2))return false;return!canSave(bp)}function noteSaveTrouble(bp,err2){if(troubles.has(bp.squadDir))return false;const e=asError(err2);troubles.set(bp.squadDir,{root:bp.root,since:new Date().toISOString(),text:isFileTrouble(e)?fileTroubleWords(e):fileTroubleWords({code:"ENOENT"}),detail:e.message});checked2.set(bp.squadDir,Date.now());return true}function saveTroubles(){return[...troubles.values(),...turnTroubles.values()]}var turnTroubles=new Map;var TURN_TROUBLE="squad-kit ran into a problem it did not expect, so a project's squad cannot move right now. Your work is safe. Restart squad-kit. If this stays, use Report a problem.";function noteTurnTrouble(bp,err2){turnTroubles.set(bp.squadDir,{root:bp.root,since:new Date().toISOString(),text:TURN_TROUBLE,detail:asError(err2).message})}function endTurnTrouble(bp){turnTroubles.delete(bp.squadDir)}function stillHeld(bp,at3=Date.now()){if(!troubles.has(bp.squadDir))return{held:false,fixed:false};if(at3-(checked2.get(bp.squadDir)??0)<SAVE_CHECK_MS)return{held:true,fixed:false};checked2.set(bp.squadDir,at3);if(!canSave(bp))return{held:true,fixed:false};troubles.delete(bp.squadDir);checked2.delete(bp.squadDir);return{held:false,fixed:true}}function forgetSaveTrouble(bp){troubles.delete(bp.squadDir);checked2.delete(bp.squadDir);turnTroubles.delete(bp.squadDir)}import fs105 from"fs";import path98 from"path";import fs103 from"fs";import path96 from"path";var SETUP_STEPS=["files","tool","read","commands","tests","app","overview"];function talkFile(bp){return path96.join(bp.localDir,"overview-talk.json")}function readTalk(bp){try{const t=JSON.parse(fs103.readFileSync(talkFile(bp),"utf8"));return Array.isArray(t.messages)?t:null}catch{return null}}function writeTalk(bp,t){fs103.mkdirSync(path96.dirname(talkFile(bp)),{recursive:true});const tmp=`${talkFile(bp)}.${process.pid}.tmp`;fs103.writeFileSync(tmp,`${JSON.stringify(t,null,1)}
@@ -1145,7 +1147,7 @@ ${tail(r.output,30)}`}:{command:meta.repos?.length?said:setup,output:tail(r.outp
1145
1147
 
1146
1148
  This work was built outside squad-kit, and it is in this folder as it is now. Do not build it again. Look at what is there, then do only this:
1147
1149
 
1148
- ${prompt}`}const cwd=workFolder(t);const started=Date.now();let current=-1;const lanes=await this.watchCopies(t);this.lanesOf.set(id2,lanes);const r=await this.runAgent({role:"builder",step:step2,ticket:id2,cwd,...resume?{resume:t.sessions.build}:{sessionId:t.sessions.build},prompt:change?[smallChangePrompt(change),prompt].filter(Boolean).join("\n\n"):prompt,permissions:["edit"],schema:BUILD_SCHEMA,timeoutMs:BUILD_STEP_MS,addDirs:this.extraDirs(t),box:{},onText:text3=>{const m=[...text3.matchAll(/>>\s*step\s+(\d+)/gi)].pop();if(!m)return;const n=Number(m[1])-1;if(n===current)return;current=n;this.update(id2,x=>{const steps=x.steps??[];steps.forEach((s2,i)=>{if(i<n&&s2.status!=="done"){s2.status="done";s2.live=void 0;s2.minutes=Math.max(1,Math.round((Date.now()-started)/6e4/Math.max(1,n)));addActivity(x,"builder",`Finished step ${i+1}: ${s2.title}`)}});const s=steps[n];if(s&&s.status!=="doing"){s.status="doing";addActivity(x,"builder",`Started step ${n+1}: ${s.title}`)}x.progress=Math.round(n/Math.max(1,steps.length)*100);x.status=`Step ${n+1} of ${steps.length}`;nextPart(this.bp,x,n+1)})},onTool:this.watcher(id2,"builder",cwd,(line3,x)=>{const doing=x.steps?.find(s=>s.status==="doing");if(doing)doing.live=line3})}).finally(()=>{this.lanesOf.delete(id2);return this.keepLanes(id2,"builder",lanes)}).catch(err2=>this.builderFailed(id2,err2));if(change){this.update(id2,x=>{const c=x.changes?.find(y=>y.id===change.id);if(c?.status==="building"){c.status="done";delete c.paused;delete c.specBefore}addActivity(x,"builder",`Made change ${change.id}: ${change.summary??change.ask}`)})}const asked=r.structured?.question;const way=asked?.text?.trim()?await this.inTheWay(id2,asked,told):null;if(way?.answer&&settled>=2)throw new StepError("The Builder kept asking a technical question, which squad-kit does not pass on to you, so it stopped this step. Press Try again.",asked.text);if(way?.answer)return this.runBuilder(id2,way.answer,true,step2,settled+1,told);this.askIfNeeded(id2,"builder",way?.ask??asked);this.saveDecided(id2,"builder",r.structured);const late2=openChange(readTicket(this.bp,id2));if(late2&&late2.size!=="big"&&late2.status==="approved")await this.runBuilder(id2,"",true,step2)}builderFailed(id2,err2){if(err2 instanceof PassingTrouble)throw err2;if(err2 instanceof WaitingForYou||err2 instanceof LimitReached||err2 instanceof CapReached)throw err2;const ended=this.closing||Boolean(this.stops.get(id2)?.signal.aborted)||Boolean(readTicket(this.bp,id2)?.stopRequested);if(ended)throw err2;if(sleptSince(this.marks.get(id2)))throw new CutOff("sleep");this.update(id2,x=>x.fails=(x.fails??0)+1);throw err2}handChange(id2){const t=readTicket(this.bp,id2);const c=t?openChange(t):void 0;if(!t||!c||c.size==="big"||changeWork(t)!=="build")return void 0;this.update(id2,x=>{const y=openChange(x);if(y)y.status="building";addActivity(x,"builder",`Took change ${c.id} into this step`)});return c}async inTheWay(id2,q,told){const hit=inTheWay([q.text,q.why,...q.options??[]].filter(Boolean).join("\n"));if(!hit)return null;const copies=projectCopies(this.bp);const busy=this.busyCopies(id2);const outside=[];const settled=[];const holders=hit.ports.length?hit.ports.map(port=>({port,holder:listenerOn(port)})):hit.pids.map(pid=>({port:void 0,holder:processes().find(r=>r.pid===pid)?{pid,program:""}:null}));for(const{port,holder}of holders){if(!holder){if(port&&await portTaken(port))outside.push({port,pid:0,asked:q.text.trim()});else settled.push(port?`Port ${port} is free now; carry on. Run what you needed it for again, then go on with the work.`:"That program has ended; carry on with the work.");continue}if((await this.clearCopies(copies,{busy,only:[holder.pid]},id2)).length){settled.push(settledAnswer({...port?{port}:{}}));continue}const app=port?squadKitOn(this.bp,port)?.app:void 0;const cwd=workingFolders([holder.pid]).get(holder.pid);if(!app&&(ownedBySquadKit(holder.pid)||cwd&&folderOf(cwd,copies))){settled.push(ownWorkAnswer(await this.aroundIt(id2,port)));continue}outside.push({...port?{port}:{},pid:holder.pid,...holder.program?{program:holder.program}:{},...app?{app}:{},asked:q.text.trim()})}const key=p=>p.port?`port ${p.port}`:`process ${p.pid}`;const again2=outside.find(p=>told?.has(key(p)));if(again2){const card=portCard(again2);this.log("info",`The Builder asked again about ${key(again2)}, held by ${again2.program||"a program"}${again2.pid?` (process ${again2.pid})`:""}: it asks the person`,id2);return{ask:{text:card.text,options:card.options,why:card.why,port:again2}}}for(const p of outside){told?.add(key(p));settled.push(goAroundAnswer(await this.aroundIt(id2,p.port)))}this.log("info",`Asked about ${[...hit.ports.map(p=>`port ${p}`),...hit.pids.map(p=>`process ${p}`)].join(", ")}: ${settled.join(" ")}`,id2);this.update(id2,x=>addActivity(x,"squad",outside.length?"Another program on this computer was in the way, so squad-kit showed how to go around it. The work carries on":"Something squad-kit left running was in the way. squad-kit dealt with it, and the work carries on"));return{answer:settled.join("\n")}}async aroundIt(id2,port){if(!port)return{};const free=await nextFreePort(port);const cwd=readTicket(this.bp,id2)?.worktree;const vars=cwd&&fs112.existsSync(cwd)?appPortVariables(cwd,port):[];return{port,...free?{free}:{},...vars.length?{vars}:{}}}async portAnswer(id2,p,choice){if(choice!==STOP_IT)return cardAnswer(p,choice,null);const now2=p.port?listenerOn(p.port):processes().find(r=>r.pid===p.pid)??null;let stopped=!now2&&!(p.port&&await portTaken(p.port));const app=p.port?squadKitOn(this.bp,p.port):null;if(p.pid>0&&now2?.pid===p.pid&&(app?.app||!ownedBySquadKit(p.pid))){if(app?.app)await endPid(app.group);else await endProcess(p.pid);stopped=p.port?!await portTaken(p.port):!processes().some(r=>r.pid===p.pid)}this.log("info",`${stopped?"Stopped":"Could not stop"} ${p.port?`the program on port ${p.port}`:"the program in the way"} (process ${p.pid}), as the person asked`,id2);this.update(id2,x=>addActivity(x,"squad",stopped?"Stopped the other program, as you asked":"Could not stop the other program, so the Builder goes on without it"));return cardAnswer(p,choice,stopped)}async builderAnswered(id2){const t=await this.workspace(id2);this.update(id2,x=>{x.stage="implement";x.live=true});const q=t.agentQuestion;await this.runBuilder(id2,q?.port?await this.portAnswer(id2,q.port,q.answer??""):answerPrompt(q??{text:""},"builder"),true,"build");this.update(id2,x=>{for(const s of x.steps??[]){s.status="done";s.live=void 0}x.progress=100;x.agentQuestion=void 0;addActivity(x,"builder","Carried on with your answer and finished");x.stage="test";x.status="Running the project checks";scheduleSquad(x,"test",.2,1)})}askIfNeeded(id2,from,q){if(!q?.text?.trim())return;const who4={architect:"Architect",builder:"Builder",reviewer:"Reviewer"}[from];this.update(id2,x=>{const folders=ticketFolders(x,this.bp);const files=(q.files??[]).map(f=>allowedFile(f.replace(/^file:\/\//,""),folders)).filter(f=>Boolean(f)).slice(0,6);const kind=x.track==="auto"&&(q.kind==="gap"||q.kind==="clash")?q.kind:void 0;const sources=kind?cleanSources(q.sources):[];x.agentQuestion={from,text:q.text.trim(),options:(q.options??[]).filter(o=>o.trim()).slice(0,4),...q.why?{why:q.why}:{},...files.length?{files}:{},...q.port?{port:q.port}:{},...kind?{kind}:{},...sources.length?{sources}:{},at:now()};const words=askWords(who4,kind);x.live=false;x.squad=null;x.status=words.status;x.waiting={kind:"ask",summary:words.summary};addActivity(x,from,`Asked you: ${q.text.trim()}${files.length?` (with ${files.length} ${files.length===1?"file":"files"})`:""}`)});throw new WaitingForYou}saveDecided(id2,by2,out17){const decided=out17?.decided;if(!Array.isArray(decided)||!decided.length||readTicket(this.bp,id2)?.track!=="auto")return;this.update(id2,x=>addDecided(x,by2,decided))}saveMockup(id2,html){const dir=ticketDir(this.bp,id2);fs112.mkdirSync(dir,{recursive:true});const file3=path104.join(dir,"mockup.html");fs112.writeFileSync(file3,html);return file3}async reviewFix(id2){const t=await this.workspace(id2);this.update(id2,x=>{x.stage="implement";x.live=true;x.status="Builder is fixing the review notes";x.tries=0;x.reviewRound=0});await this.runBuilder(id2,reviewFixPrompt(t.reviewNotes??[]),Boolean(t.sessions?.build));this.update(id2,x=>{addActivity(x,"builder","Fixed the review notes");x.reviewNotes=void 0;x.stage="test";x.status="Running the project checks";scheduleSquad(x,"test",.2,1)})}async bringInBase(t,base2,remote,dir=t.worktree){const unmerged=async()=>(await gitLater(dir,["diff","--name-only","--diff-filter=U"],{allowFail:true})).split("\n").filter(Boolean);const merging=()=>git(dir,["rev-parse","-q","--verify","MERGE_HEAD"],{allowFail:true})!=="";const latest=latestBase(dir,base2,remote);const left=merging()?await unmerged():[];if(left.length)return{baseSha:git(dir,["rev-parse","MERGE_HEAD"]),clashes:left,from:latest.ref};const baseSha=latest.sha;leaveProjectFiles(this.bp,t,base2);const guard=this.guardFor(dir,t.id);const signal2=this.stops.get(t.id)?.signal;const saved=await commitAll(dir,`Work so far on ${t.title} (${t.id})`,conventionalWords("chore",`work so far on ${t.title}`,t.id),{...guard?{guard}:{},...signal2?{signal:signal2}:{}});if(saved.hookSaid!==void 0){this.log("warn",`The project's own check said no to the work so far: ${saved.hookSaid}`,t.id);this.update(t.id,x=>{x.hookOwed=true;addActivity(x,"squad","Saved the work so far without the project's own check, as it is not finished. The check runs on the whole change before it goes in")})}let why="git did not merge it";try{await gitLater(dir,["merge","--no-commit","--no-ff","-m",`Merge ${latest.ref} into ${t.branch??"this ticket"}`,baseSha])}catch(err2){why=err2.message}const clashes=await unmerged();if(!merging()&&!isAncestor(dir,baseSha,"HEAD"))throw new StepError("squad-kit could not bring the newest code into this ticket. Press Try again.",why);return{baseSha,clashes,from:latest.ref}}async bringInEveryBase(t){const copies=copiesOf(this.meta(),t);const clashes=[];const bases=new Map;let first2;for(const c of copies){const r=await this.bringInBase(t,c.repo.base,await fetchBase(this.bp,{...t,repo:c.repo.path}),c.dir);first2??=r;clashes.push(...r.clashes.map(f=>t.storyDir?`${c.repo.path}/${f}`:f));bases.set(c.repo.path,r.baseSha)}if(t.copies?.length)this.update(t.id,x=>x.copies=x.copies?.map(c=>({...c,baseCommit:bases.get(c.repo)??c.baseCommit})));return{...first2,clashes}}async anyBaseMoved(t){for(const c of copiesOf(this.meta(),t)){if(this.baseMoved({...t,worktree:c.dir,baseCommit:c.baseCommit},c.repo.base,await fetchBase(this.bp,{...t,repo:c.repo.path})))return true}return false}async rebuild(id2,adr){const t=readTicket(this.bp,id2);const base2=repoFor(this.meta(),t).base;const cut=t.builtPlan===planKey(t)&&t.sessions?.build?carriesOn(t,"build"):void 0;const moved=!cut||await this.anyBaseMoved(t);const{baseSha,clashes,from}=moved?await this.bringInEveryBase(t):{baseSha:t.baseCommit,clashes:[],from:base2};const t1=this.update(id2,x=>{x.baseCommit=baseSha;x.stage="implement";x.live=true;x.tries=0;x.reviewRound=0;x.proof=void 0;x.gates={checks:"pending",review:"pending",proof:"pending"};x.sessions={...x.sessions,build:x.sessions?.build??randomUUID7()};x.status=cut?`Step ${openStep(x)} of ${x.steps?.length??1}`:`Redoing the work to follow ${adr}`;if(moved)addActivity(x,"squad",`Brought the newest code into this ticket${clashes.length?`. Clashes in ${clashes.join(", ")}`:""}`);addActivity(x,"builder",cut?carryOnLine(x,cut.by):`Started redoing the work to follow ${adr}`)});const meta=this.meta();this.update(id2,x=>x.builtPlan=planKey(t1));const intro=`The plan changed to follow ${adr}, a decision approved after you started. The latest ${base2} is now in this copy${clashes.length?`, with clashes in ${clashes.join(", ")} to fix first`:""}. Stay on this branch: it already has the newest code, so a plan that says to start from ${base2} is met, and squad-kit does all the git. Redo the work to the new plan below: keep what still fits, change what does not.`;const newer2=moved&&cut?` squad-kit brought the newest code into this copy meanwhile${clashes.length?`, with clashes in ${clashes.join(", ")} to fix first`:""}.`:"";await this.runBuilder(id2,cut?`${carryOnPrompt(t1,cut.by)}${newer2}`:`${intro}
1150
+ ${prompt}`}const cwd=workFolder(t);const started=Date.now();let current=-1;const reached=n=>{if(n<=current||n>=(readTicket(this.bp,id2)?.steps?.length??0))return;current=n;this.update(id2,x=>{const steps=x.steps??[];steps.forEach((s2,i)=>{if(i<n&&s2.status!=="done"){s2.status="done";s2.live=void 0;s2.minutes=Math.max(1,Math.round((Date.now()-started)/6e4/Math.max(1,n)));addActivity(x,"builder",`Finished step ${i+1}: ${s2.title}`)}});const s=steps[n];if(s&&s.status!=="doing"){s.status="doing";addActivity(x,"builder",`Started step ${n+1}: ${s.title}`)}x.progress=Math.round(n/Math.max(1,steps.length)*100);x.status=`Step ${n+1} of ${steps.length}`;nextPart(this.bp,x,n+1)})};let heard="";const stepSaid=said=>{const m=[...said.matchAll(/>>\s*step\s+(\d+)/gi)].pop();if(m)reached(Number(m[1])-1)};const planSaid=entries=>{const steps=readTicket(this.bp,id2)?.steps??[];if(!steps.length||entries.length!==steps.length)return;const doing=entries.findIndex(e=>e.status==="in_progress");const open3=entries.findIndex(e=>e.status!=="completed");reached(doing>=0?doing:open3>=0?open3:steps.length-1)};const watch=this.watcher(id2,"builder",cwd,(line3,x)=>{const doing=x.steps?.find(s=>s.status==="doing");if(doing)doing.live=line3});const lanes=await this.watchCopies(t);this.lanesOf.set(id2,lanes);const r=await this.runAgent({role:"builder",step:step2,ticket:id2,cwd,...resume?{resume:t.sessions.build}:{sessionId:t.sessions.build},prompt:change?[smallChangePrompt(change),prompt].filter(Boolean).join("\n\n"):prompt,permissions:["edit"],schema:BUILD_SCHEMA,timeoutMs:BUILD_STEP_MS,addDirs:this.extraDirs(t),box:{},onText:piece=>{heard=`${heard}${piece}`.slice(-200);stepSaid(heard);heard=heard.slice(-20)},onPlan:planSaid,onTool:e=>{if(typeof e.input.command==="string")stepSaid(e.input.command);watch(e)}}).finally(()=>{this.lanesOf.delete(id2);return this.keepLanes(id2,"builder",lanes)}).catch(err2=>this.builderFailed(id2,err2));if(change){this.update(id2,x=>{const c=x.changes?.find(y=>y.id===change.id);if(c?.status==="building"){c.status="done";delete c.paused;delete c.specBefore}addActivity(x,"builder",`Made change ${change.id}: ${change.summary??change.ask}`)})}const asked=r.structured?.question;const way=asked?.text?.trim()?await this.inTheWay(id2,asked,told):null;if(way?.answer&&settled>=2)throw new StepError("The Builder kept asking a technical question, which squad-kit does not pass on to you, so it stopped this step. Press Try again.",asked.text);if(way?.answer)return this.runBuilder(id2,way.answer,true,step2,settled+1,told);this.askIfNeeded(id2,"builder",way?.ask??asked);this.saveDecided(id2,"builder",r.structured);const late2=openChange(readTicket(this.bp,id2));if(late2&&late2.size!=="big"&&late2.status==="approved")await this.runBuilder(id2,"",true,step2)}builderFailed(id2,err2){if(err2 instanceof PassingTrouble)throw err2;if(err2 instanceof WaitingForYou||err2 instanceof LimitReached||err2 instanceof CapReached)throw err2;const ended=this.closing||Boolean(this.stops.get(id2)?.signal.aborted)||Boolean(readTicket(this.bp,id2)?.stopRequested);if(ended)throw err2;if(sleptSince(this.marks.get(id2)))throw new CutOff("sleep");this.update(id2,x=>x.fails=(x.fails??0)+1);throw err2}handChange(id2){const t=readTicket(this.bp,id2);const c=t?openChange(t):void 0;if(!t||!c||c.size==="big"||changeWork(t)!=="build")return void 0;this.update(id2,x=>{const y=openChange(x);if(y)y.status="building";addActivity(x,"builder",`Took change ${c.id} into this step`)});return c}async inTheWay(id2,q,told){const hit=inTheWay([q.text,q.why,...q.options??[]].filter(Boolean).join("\n"));if(!hit)return null;const copies=projectCopies(this.bp);const busy=this.busyCopies(id2);const outside=[];const settled=[];const holders=hit.ports.length?hit.ports.map(port=>({port,holder:listenerOn(port)})):hit.pids.map(pid=>({port:void 0,holder:processes().find(r=>r.pid===pid)?{pid,program:""}:null}));for(const{port,holder}of holders){if(!holder){if(port&&await portTaken(port))outside.push({port,pid:0,asked:q.text.trim()});else settled.push(port?`Port ${port} is free now; carry on. Run what you needed it for again, then go on with the work.`:"That program has ended; carry on with the work.");continue}if((await this.clearCopies(copies,{busy,only:[holder.pid]},id2)).length){settled.push(settledAnswer({...port?{port}:{}}));continue}const app=port?squadKitOn(this.bp,port)?.app:void 0;const cwd=workingFolders([holder.pid]).get(holder.pid);if(!app&&(ownedBySquadKit(holder.pid)||cwd&&folderOf(cwd,copies))){settled.push(ownWorkAnswer(await this.aroundIt(id2,port)));continue}outside.push({...port?{port}:{},pid:holder.pid,...holder.program?{program:holder.program}:{},...app?{app}:{},asked:q.text.trim()})}const key=p=>p.port?`port ${p.port}`:`process ${p.pid}`;const again2=outside.find(p=>told?.has(key(p)));if(again2){const card=portCard(again2);this.log("info",`The Builder asked again about ${key(again2)}, held by ${again2.program||"a program"}${again2.pid?` (process ${again2.pid})`:""}: it asks the person`,id2);return{ask:{text:card.text,options:card.options,why:card.why,port:again2}}}for(const p of outside){told?.add(key(p));settled.push(goAroundAnswer(await this.aroundIt(id2,p.port)))}this.log("info",`Asked about ${[...hit.ports.map(p=>`port ${p}`),...hit.pids.map(p=>`process ${p}`)].join(", ")}: ${settled.join(" ")}`,id2);this.update(id2,x=>addActivity(x,"squad",outside.length?"Another program on this computer was in the way, so squad-kit showed how to go around it. The work carries on":"Something squad-kit left running was in the way. squad-kit dealt with it, and the work carries on"));return{answer:settled.join("\n")}}async aroundIt(id2,port){if(!port)return{};const free=await nextFreePort(port);const cwd=readTicket(this.bp,id2)?.worktree;const vars=cwd&&fs112.existsSync(cwd)?appPortVariables(cwd,port):[];return{port,...free?{free}:{},...vars.length?{vars}:{}}}async portAnswer(id2,p,choice){if(choice!==STOP_IT)return cardAnswer(p,choice,null);const now2=p.port?listenerOn(p.port):processes().find(r=>r.pid===p.pid)??null;let stopped=!now2&&!(p.port&&await portTaken(p.port));const app=p.port?squadKitOn(this.bp,p.port):null;if(p.pid>0&&now2?.pid===p.pid&&(app?.app||!ownedBySquadKit(p.pid))){if(app?.app)await endPid(app.group);else await endProcess(p.pid);stopped=p.port?!await portTaken(p.port):!processes().some(r=>r.pid===p.pid)}this.log("info",`${stopped?"Stopped":"Could not stop"} ${p.port?`the program on port ${p.port}`:"the program in the way"} (process ${p.pid}), as the person asked`,id2);this.update(id2,x=>addActivity(x,"squad",stopped?"Stopped the other program, as you asked":"Could not stop the other program, so the Builder goes on without it"));return cardAnswer(p,choice,stopped)}async builderAnswered(id2){const t=await this.workspace(id2);this.update(id2,x=>{x.stage="implement";x.live=true});const q=t.agentQuestion;await this.runBuilder(id2,q?.port?await this.portAnswer(id2,q.port,q.answer??""):answerPrompt(q??{text:""},"builder"),true,"build");this.update(id2,x=>{for(const s of x.steps??[]){s.status="done";s.live=void 0}x.progress=100;x.agentQuestion=void 0;addActivity(x,"builder","Carried on with your answer and finished");x.stage="test";x.status="Running the project checks";scheduleSquad(x,"test",.2,1)})}askIfNeeded(id2,from,q){if(!q?.text?.trim())return;const who4={architect:"Architect",builder:"Builder",reviewer:"Reviewer"}[from];this.update(id2,x=>{const folders=ticketFolders(x,this.bp);const files=(q.files??[]).map(f=>allowedFile(f.replace(/^file:\/\//,""),folders)).filter(f=>Boolean(f)).slice(0,6);const kind=x.track==="auto"&&(q.kind==="gap"||q.kind==="clash")?q.kind:void 0;const sources=kind?cleanSources(q.sources):[];x.agentQuestion={from,text:q.text.trim(),options:(q.options??[]).filter(o=>o.trim()).slice(0,4),...q.why?{why:q.why}:{},...files.length?{files}:{},...q.port?{port:q.port}:{},...kind?{kind}:{},...sources.length?{sources}:{},at:now()};const words=askWords(who4,kind);x.live=false;x.squad=null;x.status=words.status;x.waiting={kind:"ask",summary:words.summary};addActivity(x,from,`Asked you: ${q.text.trim()}${files.length?` (with ${files.length} ${files.length===1?"file":"files"})`:""}`)});throw new WaitingForYou}saveDecided(id2,by2,out17){const decided=out17?.decided;if(!Array.isArray(decided)||!decided.length||readTicket(this.bp,id2)?.track!=="auto")return;this.update(id2,x=>addDecided(x,by2,decided))}saveMockup(id2,html){const dir=ticketDir(this.bp,id2);fs112.mkdirSync(dir,{recursive:true});const file3=path104.join(dir,"mockup.html");fs112.writeFileSync(file3,html);return file3}async reviewFix(id2){const t=await this.workspace(id2);this.update(id2,x=>{x.stage="implement";x.live=true;x.status="Builder is fixing the review notes";x.tries=0;x.reviewRound=0});await this.runBuilder(id2,reviewFixPrompt(t.reviewNotes??[]),Boolean(t.sessions?.build));this.update(id2,x=>{addActivity(x,"builder","Fixed the review notes");x.reviewNotes=void 0;x.stage="test";x.status="Running the project checks";scheduleSquad(x,"test",.2,1)})}async bringInBase(t,base2,remote,dir=t.worktree){const unmerged=async()=>(await gitLater(dir,["diff","--name-only","--diff-filter=U"],{allowFail:true})).split("\n").filter(Boolean);const merging=()=>git(dir,["rev-parse","-q","--verify","MERGE_HEAD"],{allowFail:true})!=="";const latest=latestBase(dir,base2,remote);const left=merging()?await unmerged():[];if(left.length)return{baseSha:git(dir,["rev-parse","MERGE_HEAD"]),clashes:left,from:latest.ref};const baseSha=latest.sha;leaveProjectFiles(this.bp,t,base2);const guard=this.guardFor(dir,t.id);const signal2=this.stops.get(t.id)?.signal;const saved=await commitAll(dir,`Work so far on ${t.title} (${t.id})`,conventionalWords("chore",`work so far on ${t.title}`,t.id),{...guard?{guard}:{},...signal2?{signal:signal2}:{}});if(saved.hookSaid!==void 0){this.log("warn",`The project's own check said no to the work so far: ${saved.hookSaid}`,t.id);this.update(t.id,x=>{x.hookOwed=true;addActivity(x,"squad","Saved the work so far without the project's own check, as it is not finished. The check runs on the whole change before it goes in")})}let why="git did not merge it";try{await gitLater(dir,["merge","--no-commit","--no-ff","-m",`Merge ${latest.ref} into ${t.branch??"this ticket"}`,baseSha])}catch(err2){why=err2.message}const clashes=await unmerged();if(!merging()&&!isAncestor(dir,baseSha,"HEAD"))throw new StepError("squad-kit could not bring the newest code into this ticket. Press Try again.",why);return{baseSha,clashes,from:latest.ref}}async bringInEveryBase(t){const copies=copiesOf(this.meta(),t);const clashes=[];const bases=new Map;let first2;for(const c of copies){const r=await this.bringInBase(t,c.repo.base,await fetchBase(this.bp,{...t,repo:c.repo.path}),c.dir);first2??=r;clashes.push(...r.clashes.map(f=>t.storyDir?`${c.repo.path}/${f}`:f));bases.set(c.repo.path,r.baseSha)}if(t.copies?.length)this.update(t.id,x=>x.copies=x.copies?.map(c=>({...c,baseCommit:bases.get(c.repo)??c.baseCommit})));return{...first2,clashes}}async anyBaseMoved(t){for(const c of copiesOf(this.meta(),t)){if(this.baseMoved({...t,worktree:c.dir,baseCommit:c.baseCommit},c.repo.base,await fetchBase(this.bp,{...t,repo:c.repo.path})))return true}return false}async rebuild(id2,adr){const t=readTicket(this.bp,id2);const base2=repoFor(this.meta(),t).base;const cut=t.builtPlan===planKey(t)&&t.sessions?.build?carriesOn(t,"build"):void 0;const moved=!cut||await this.anyBaseMoved(t);const{baseSha,clashes,from}=moved?await this.bringInEveryBase(t):{baseSha:t.baseCommit,clashes:[],from:base2};const t1=this.update(id2,x=>{x.baseCommit=baseSha;x.stage="implement";x.live=true;x.tries=0;x.reviewRound=0;x.proof=void 0;x.gates={checks:"pending",review:"pending",proof:"pending"};x.sessions={...x.sessions,build:x.sessions?.build??randomUUID7()};x.status=cut?`Step ${openStep(x)} of ${x.steps?.length??1}`:`Redoing the work to follow ${adr}`;if(moved)addActivity(x,"squad",`Brought the newest code into this ticket${clashes.length?`. Clashes in ${clashes.join(", ")}`:""}`);addActivity(x,"builder",cut?carryOnLine(x,cut.by):`Started redoing the work to follow ${adr}`)});const meta=this.meta();this.update(id2,x=>x.builtPlan=planKey(t1));const intro=`The plan changed to follow ${adr}, a decision approved after you started. The latest ${base2} is now in this copy${clashes.length?`, with clashes in ${clashes.join(", ")} to fix first`:""}. Stay on this branch: it already has the newest code, so a plan that says to start from ${base2} is met, and squad-kit does all the git. Redo the work to the new plan below: keep what still fits, change what does not.`;const newer2=moved&&cut?` squad-kit brought the newest code into this copy meanwhile${clashes.length?`, with clashes in ${clashes.join(", ")} to fix first`:""}.`:"";await this.runBuilder(id2,cut?`${carryOnPrompt(t1,cut.by)}${newer2}`:`${intro}
1149
1151
 
1150
1152
  ${buildPrompt(meta,listDecisions(this.bp),t1,this.ctx(t1))}`,Boolean(t.sessions?.build),"build");this.update(id2,x=>{for(const s of x.steps??[]){s.status="done";s.live=void 0}x.replanFor=void 0;x.progress=100;addActivity(x,"builder","Finished all steps");x.stage="test";x.status="Running the project checks";scheduleSquad(x,"test",.2,1)})}baseMoved(t,base2,remote){try{const latest=latestBase(t.worktree,base2,remote).sha;return latest!==t.baseCommit&&!isAncestor(t.worktree,latest,"HEAD")}catch{return true}}async updateBase(id2){const t=await this.workspace(id2);const base2=repoFor(this.meta(),t).base;const{baseSha,clashes,from}=await this.bringInEveryBase(t);this.update(id2,x=>{x.baseCommit=baseSha;x.stage="implement";x.live=true;x.tries=0;x.reviewRound=0;x.proof=void 0;x.gates={checks:"pending",review:"pending",proof:"pending"};x.status=clashes.length?`Builder is fixing ${clashes.length} clashing ${clashes.length===1?"file":"files"}`:"Running the project checks";addActivity(x,"squad",`Brought the newest code into this ticket${clashes.length?`. Clashes in ${clashes.join(", ")}`:""}`)});if(clashes.length){await this.runBuilder(id2,clashPrompt(base2,clashes),Boolean(t.sessions?.build));this.update(id2,x=>addActivity(x,"builder",`Fixed the clash with ${base2}`))}this.update(id2,x=>{x.stage="test";x.status="Running the project checks";scheduleSquad(x,"test",.2,1)})}async fixFromPerson(id2){const t=await this.workspace(id2);this.update(id2,x=>{x.stage="implement";x.live=true;x.status="Builder is fixing what you found";x.tries=0;x.fails=0;x.reviewRound=0;x.steps=[...x.steps??[],{title:`Fix: ${x.changeRequest?.split("\n")[0]??"what you found"}`,status:"doing"}]});await this.runBuilder(id2,changeFixPrompt(t.changeRequest??""),Boolean(t.sessions?.build));this.update(id2,x=>{const last=x.steps?.[x.steps.length-1];if(last){last.status="done";last.live=void 0}addActivity(x,"builder","Fixed what you found");x.changeRequest=void 0;x.stage="test";x.status="Running the project checks";x.proof=void 0;scheduleSquad(x,"test",.2,1)})}saveFoundCommands(id2,cwd,repoPath){const meta=this.meta();const t=readTicket(this.bp,id2);const repo=(repoPath?reposOf(meta).find(r=>r.path===repoPath):void 0)??repoFor(meta,t);const has3=k=>Boolean(repo.commands[k]?.trim());const fresh=Boolean(meta.scan?.empty)||!["test","lint","build","start","url"].some(has3);const wanted=(fresh?["setup","test","lint","build","start","url"]:["test"]).filter(k=>!has3(k)&&!(k==="test"&&meta.noTests));if(!wanted.length||!fs112.existsSync(cwd))return repo.commands;const found2=findCommands(cwd);const add2=Object.fromEntries(wanted.filter(k=>found2[k]?.trim()).map(k=>[k,found2[k].trim()]));if(!Object.keys(add2).length)return repo.commands;if(meta.repos?.length)meta.repos=meta.repos.map(r=>r.path===repo.path?{...r,commands:{...r.commands,...add2}}:r);else meta.commands={...meta.commands??{},...add2};writeMeta(this.bp,meta);this.log("info",`Found and saved: ${Object.entries(add2).map(([k,v])=>`${k}: ${v}`).join(", ")}`,id2);this.update(id2,x=>{if(add2.test)delete x.testSuggestion;addActivity(x,"squad",add2.test&&Object.keys(add2).length===1?"squad-kit found the tests in the project, and runs them from now on":"squad-kit found how to test and run the project, and saved it")});return{...repo.commands,...add2}}async builderPassed(id2,t,copies){const notes=this.builderRuns.get(id2);this.builderRuns.delete(id2);if(!notes?.size)return null;const out17=new Map;let why=t.testChange&&!t.testChange.ask?"a new test command is still to be tried":"";for(const c of why?[]:copies){const note2=notes.get(c.dir);const want=checksToRun(c.dir,c.repo.commands);if(!note2)why=`it has no passing run of every check in ${c.label}`;else if(want.length!==note2.ran.length||want.some((w,i)=>w.name!==note2.ran[i].name||w.command!==note2.ran[i].command))why=`the checks now are not the ones it ran (${want.map(w=>`${w.name}: ${w.command}`).join("; ")})`;else if(startsToo(c.repo.commands,note2.ran))why="no tests ran, so the checks also start the app";else if(await treeOf(c.dir)!==note2.tree)why=`the code in ${c.label} changed since that run started`;if(why||!note2)break;out17.set(c,note2.ran)}if(why){this.log("info",`squad-kit runs the checks itself, not using the Builder's last run_checks: ${why}`,id2);return null}if(!out17.size)return null;for(const[c,ran2]of out17){const note2=notes.get(c.dir);this.log("info",`squad-kit uses the Builder's run_checks from ${new Date(note2.at).toISOString()} in ${c.label} instead of running the same commands again: they all passed in the same copy, on the same code (tree ${note2.tree.slice(0,12)}): ${ran2.map(r=>`${r.name}: ${r.command}`).join("; ")}`,id2)}return out17}async checks(id2){const t=readTicket(this.bp,id2);for(const c of copiesOf(this.meta(),t))this.saveFoundCommands(id2,c.dir,c.repo.path);const meta=this.meta();const copies=copiesOf(meta,t);const several=copies.length>1;const weak=[];for(const c of copies){if(c.baseCommit)weak.push(...await weakenedChecks(c.dir,c.baseCommit,{skip:[`${path104.basename(this.bp.squadDir)}/`],testCommand:c.repo.commands.test}),...await gatesChanged(this.bp,c.dir,c.repo.path,c.baseCommit))}const fresh=weak.filter(w=>!(t.weakenedContinued??[]).includes(w.key));if(fresh.length)return this.weakenedStop(id2,fresh,repoFor(meta,t).base);await this.clearBeforeApp(id2);this.goOn(id2);const changed=[];for(const c of copies)if(!several||c.baseCommit&&(await workSoFar(c.dir,c.baseCommit)).length)changed.push(c);const passed=await this.builderPassed(id2,t,changed.length?changed:copies.slice(0,1));this.update(id2,x=>{x.stage="test";x.live=true;x.gates={checks:"pending",review:x.gates?.review??"pending",proof:"pending"};addActivity(x,"tester",passed?CHECKS_PASSED_FOR_BUILDER:"squad-kit is running the project checks");startStep(x,{who:"squad",step:"checks",limit:15})});const mark=sleepMark();const results=[];const ranIn=new Map;for(const c of changed.length?changed:copies.slice(0,1)){let commands=c.repo.commands;const onStart=(name,command)=>{this.log("info",`Running ${name}: ${command??commands[name]}`,id2);this.update(id2,x=>{x.status=CHECK_DOING[name]??DOING.check;stepDoing(x,x.status)})};const guard=this.guardFor(c.dir,id2);const hookLane=await watchLane(c.dir,hookFiles(c.dir));if(!t.testChange?.repo||t.testChange.repo===c.repo.path)commands=await this.tryTestChange(id2,c.dir,commands,guard,mark);const ran3=passed?.get(c)??await runChecks2(c.dir,commands,onStart,{...this.commandWatch(id2),...guard?{guard}:{}});const hooksBack=(await checkLane(hookLane)).restored;if(hooksBack.length)this.update(id2,x=>addActivity(x,"squad",`The project's checks changed the check that runs on every save (${hooksBack.slice(0,6).join(", ")}); squad-kit put it back. Changing that check stays with you or an engineer`));if(guard&&!this.stops.get(id2)?.signal.aborted){const hit=ran3.find(r=>!r.ok&&guardBlockedPath(r.output,guard));if(hit)return this.guardStop(id2,"test",guardBlockedPath(hit.output,guard),hit.command,hit.output,guard)}for(const r of ran3.map(x=>several?{...x,part:c.label}:x)){results.push(r);ranIn.set(r,c)}this.goOn(id2)}const gates=await runGatesAt2(this.bp,"checks",(changed.length?changed:copies.slice(0,1)).map(c=>({dir:c.dir,repo:c.repo.path,...several?{label:c.label}:{}})),t.addedSkipped??[],dir=>{const guard=this.guardFor(dir,id2);return{...this.commandWatch(id2),...guard?{guard}:{}}},name=>this.update(id2,x=>{x.status=`Running your team's gate: ${name}`;stepDoing(x,x.status)}));this.goOn(id2);this.update(id2,x=>noteGates(x,gates,text3=>addActivity(x,"tester",text3)));results.push(...gates.filter(g=>g.stops&&g.result!=="cant-run").map(g=>({name:PROJECT_CHECKS.includes(g.name.toLowerCase())?`${g.name} (your team's gate)`:g.name,command:g.command,ok:g.result==="passed",output:g.output,seconds:g.seconds,...g.failedFirst!==void 0?{failedFirst:g.failedFirst}:{},...g.label?{part:g.label}:{}})));const shown2=r=>r.part?`${r.part}: ${r.name}`:r.name;this.goOn(id2);const onMain=new Map;for(const r of results){if(r.ok||r.ended||r.name!=="test"||(t.checksContinued??[]).includes(shown2(r))||!ranIn.has(r))continue;const found2=await this.testsOnMain(id2,ranIn.get(r),r);this.goOn(id2);if(found2)onMain.set(r,found2)}if(sleptSince(mark)){this.update(id2,x=>{delete x.clock;addActivity(x,"squad","The computer slept while the checks ran, so squad-kit runs them again. Not a failed try");scheduleSquad(x,"test",.2,1)});return}this.update(id2,x=>endStep(this.bp,x));for(const r of results.filter(x=>x.failedFirst!==void 0)){this.log("warn",`${shown2(r)} failed once and passed when run again (${r.command}):
1151
1153
  ${r.failedFirst}`,id2);this.update(id2,x=>addActivity(x,"tester",failedOnceLine(r.name)))}const test=results.find(r=>r.name==="test");if(test?.ok&&test.failedFirst===void 0)forgetKnownFailure(this.bp,t,test.command);const oldOnly=new Set([...onMain].filter(([,m])=>m.old.length&&!m.fine.length).map(([r])=>r));const failed2=results.filter(r=>!r.ok&&!oldOnly.has(r));const cant=gates.filter(g=>g.stops&&g.result==="cant-run");const old=[];for(const[r,m]of onMain){if(!m.old.length)continue;const fixes=storiesForOld(this.bp,readTicket(this.bp,id2),m.old,m.printed,{command:r.command,...meta.repos?.length?{repo:ranIn.get(r).repo.path}:{},...r.part?{part:r.part}:{}});this.log("info",`${shown2(r)} failed on main's code too, without ${id2}'s change (${r.command}): ${fixes.map(f=>`${f.file} \u203A ${f.title}${f.story?` (${f.story})`:""}`).join("; ")}`,id2);const waiting2=fixes.flatMap(f=>f.story&&readTicket(this.bp,f.story)?.waitsToStart?[f.story]:[]);const line3=oldLine(fixes,!failed2.length&&!cant.length,waiting2);this.update(id2,x=>{if(!x.activity.some(a=>a.text===line3))addActivity(x,"tester",line3)});if(oldOnly.has(r))old.push(...fixes)}if(cant.length&&!failed2.length)return this.gateCantRun(id2,cant);const ran2=ranOf(results);const around=weak.length>0;if(failed2.length===0&&noTestsRan(results))return this.notChecked(id2,results,around);if(failed2.length===0){const tests=test?testCount(test.output):null;const parts2=[...new Set(results.map(r=>r.part).filter(Boolean))];const names2=parts2.length?parts2.map(p=>`${p} \u2713`).join(", "):results.map(r=>checkWords(r.name)).join(", ");const passed2=parts2.length?names2:tests?`${tests} ${tests===1?"test passes":"tests pass"}, ${names2} OK`:`${names2} pass`;const text3=old.length?`${passed2}; ${oldGateText(old.length)}`:passed2;this.update(id2,x=>{x.gates={...x.gates,checks:around?"skipped":"pass",checksText:around?`${text3}; tests weakened, you continued`:text3,ran:ran2,...old.length?{oldFailures:old}:{}};x.tries=0;clearStaleStop(x);x.checksContinued=void 0;addActivity(x,"tester",old.length?`All checks pass, apart from ${old.length===1?"the old test that fails":"the old tests that fail"} on main too: ${names2}`:`All checks pass: ${names2}`);x.stage="review";x.status="Reviewer is reading the changes";scheduleSquad(x,"review",.2,1,(x.reviewRound??0)+1)});for(const d of listDecisions(this.bp)){if(d.from===id2&&d.checkedBy&&!d.checkedBy.passing)writeDecision(this.bp,{...d,checkedBy:{...d.checkedBy,passing:true}})}return}const past=t.checksContinued;if(past?.length&&failed2.every(f=>past.includes(shown2(f)))){const names2=failed2.map(shown2).join(", ");this.update(id2,x=>{x.tries=0;x.gates={...x.gates,checks:"skipped",checksText:`${names2} still failed, continued anyway`,ran:ran2};clearStaleStop(x);addActivity(x,"tester",`Checks still fail: ${names2}. You continued anyway past them, so the work goes on`);x.stage="review";x.status="Reviewer is reading the changes";scheduleSquad(x,"review",.2,1,(x.reviewRound??0)+1)});return}const tries=(t.tries??0)+1;this.update(id2,x=>{x.tries=tries;x.fails=(x.fails??0)+1;x.gates={...x.gates,checks:"fail",checksText:`${failed2.map(shown2).join(", ")} failed`,ran:ran2};addActivity(x,"tester",`Checks failed: ${failed2.map(shown2).join(", ")} (try ${tries} of ${MAX_CHECK_TRIES})`)});if(tries>=MAX_CHECK_TRIES){this.update(id2,x=>{x.live=false;x.squad=null;x.retryPhase="test";x.status=`Stuck after ${tries} check runs`;x.stuck={gate:"checks",checks:failed2.map(shown2),question:`The checks still fail after ${tries} tries: ${checkNames(failed2.map(shown2))} did not pass.`,detail:failed2.map(f=>`${shown2(f)} (${f.command}):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squad-kit",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "author": "Momen Yasser <momenysr@gmail.com>",
5
5
  "description": "Your AI Engineering Squad transforms ideas into production-ready software.",
6
6
  "keywords": [