@automagik/genie 4.260324.12 → 4.260324.14

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.
@@ -10,7 +10,7 @@
10
10
  "plugins": [
11
11
  {
12
12
  "name": "genie",
13
- "version": "4.260324.12",
13
+ "version": "4.260324.14",
14
14
  "source": "./plugins/genie",
15
15
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, wish them into plans, make with parallel agents, ship as one team. A coding genie that grows with your project."
16
16
  }
package/dist/genie.js CHANGED
@@ -1029,18 +1029,18 @@ Wish: ${state2.wish}`),console.log("\u2500".repeat(60));let entries=Object.entri
1029
1029
  `);if(combined.length>50000)return`${combined.slice(0,50000)}
1030
1030
 
1031
1031
  ... (diff truncated at 50KB)`;return combined}catch{return""}}function parseWishGroups(content){let groups=[],groupPattern=/^### Group ([A-Za-z0-9]+):/gim,match=groupPattern.exec(content);while(match!==null){let name=match[1],start=match.index,rest=content.slice(start+match[0].length),nextGroupIdx=rest.search(/^### Group [A-Za-z0-9]+:/m),depsMatch=(nextGroupIdx!==-1?rest.slice(0,nextGroupIdx):rest).match(/\*\*depends-on:\*\*\s*(.+)/i),dependsOn=[];if(depsMatch){let depsStr=depsMatch[1].trim();if(depsStr.replace(/\s*\([^)]*\)/g,"").trim().toLowerCase()!=="none")dependsOn=depsStr.split(",").map((d)=>d.trim().replace(/^group\s*/i,"").replace(/\s*\(.*\)\s*$/,"").trim()).filter(Boolean)}groups.push({name,dependsOn}),match=groupPattern.exec(content)}return groups}function parseExecutionStrategy(content){let strategyMatch=content.match(/^## Execution Strategy\s*$/m);if(!strategyMatch||strategyMatch.index===void 0)return buildFallbackWaves(content);let strategyStart=strategyMatch.index+strategyMatch[0].length,nextSectionMatch=content.slice(strategyStart).match(/^## /m),strategyEnd=nextSectionMatch?.index!==void 0?strategyStart+nextSectionMatch.index:content.length,strategyContent=content.slice(strategyStart,strategyEnd),waves=[],wavePattern=/^### (Wave \d+[^\n]*)/gm,waveMatch=wavePattern.exec(strategyContent);while(waveMatch!==null){let waveName=waveMatch[1].trim(),waveStart=waveMatch.index+waveMatch[0].length,nextWaveIdx=strategyContent.slice(waveStart).search(/^### /m),waveEnd=nextWaveIdx!==-1?waveStart+nextWaveIdx:strategyContent.length,waveContent=strategyContent.slice(waveStart,waveEnd),waveGroups=[],tableRowPattern=/^\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*[^|]*\s*\|$/gm,rowMatch=tableRowPattern.exec(waveContent);while(rowMatch!==null){let groupVal=rowMatch[1].trim(),agentVal=rowMatch[2].trim();if(groupVal!=="Group"&&!groupVal.startsWith("-"))waveGroups.push({group:groupVal,agent:agentVal});rowMatch=tableRowPattern.exec(waveContent)}if(waveGroups.length>0)waves.push({name:waveName,groups:waveGroups});waveMatch=wavePattern.exec(strategyContent)}if(waves.length===0)return buildFallbackWaves(content);return waves}function buildFallbackWaves(content){let groups=parseWishGroups(content);if(groups.length===0)return[];return[{name:"Wave 1 (sequential fallback)",groups:groups.map((g)=>({group:g.name,agent:"engineer"}))}]}function detectWorkMode(ref,agent){if(!agent){if(ref.includes("#"))throw Error("Manual dispatch requires an agent: genie work <slug>#<group> <agent>");return{mode:"auto",slug:ref}}if(ref.includes("#"))return{mode:"manual",ref,agent};if(agent.includes("#"))return{mode:"manual",ref:agent,agent:ref};throw Error('Invalid: ref must contain "#" \u2014 use "genie work <slug>" or "genie work <agent> <slug>#<group>"')}async function autoOrchestrateCommand(slug){let wishPath=join24(process.cwd(),".genie","wishes",slug,"WISH.md");if(!existsSync19(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);let content=await readFile9(wishPath,"utf-8"),groups=parseWishGroups(content),waves=parseExecutionStrategy(content);if(waves.length===0)console.error("\u274C No execution groups found in wish"),process.exit(1);let state2=await getOrCreateState(slug,groups),nextWave=waves.find((wave)=>wave.groups.some((g)=>{let gs=state2?.groups[g.group];return!gs||gs.status==="ready"}));if(!nextWave){console.log(`\u2705 All waves already dispatched for wish "${slug}"`);return}console.log(`\uD83D\uDE80 Dispatching ${nextWave.name} for wish "${slug}" \u2014 ${nextWave.groups.length} group(s)`),await Promise.all(nextWave.groups.map(({group,agent})=>{let ref=`${slug}#${group}`;return workDispatchCommand(agent,ref)}));let groupList=nextWave.groups.map((g)=>g.group).join(", ");console.log(`
1032
- \u2705 Agents dispatched for ${nextWave.name} (groups: ${groupList})`),console.log(` Monitor: genie status ${slug}`),console.log(" Logs: genie read <agent>")}async function brainstormCommand(agentName,slug){let draftPath=join24(process.cwd(),".genie","brainstorms",slug,"DRAFT.md");if(!existsSync19(draftPath))console.error(`\u274C Draft not found: ${draftPath}`),console.error(` Create it first: mkdir -p .genie/brainstorms/${slug} && touch .genie/brainstorms/${slug}/DRAFT.md`),process.exit(1);let content=await readFile9(draftPath,"utf-8"),context=buildContextPrompt({filePath:draftPath,sectionContent:content,command:"brainstorm",skill:"brainstorm"}),contextFile=await writeContextFile(context);console.log(`\uD83D\uDCDD Dispatching brainstorm to ${agentName} for "${slug}"`),console.log(` Draft: ${draftPath}`),await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",extraArgs:["--append-system-prompt-file",contextFile]});let repoPath=process.cwd();await sendMessage(repoPath,"cli",agentName,`Brainstorm "${slug}". Your context is in the system prompt. Explore the idea, ask clarifying questions, and build toward a design.`)}async function wishCommand(agentName,slug){let designPath=join24(process.cwd(),".genie","brainstorms",slug,"DESIGN.md");if(!existsSync19(designPath))console.error(`\u274C Design not found: ${designPath}`),console.error(` Run brainstorm first: genie brainstorm <agent> ${slug}`),process.exit(1);let content=await readFile9(designPath,"utf-8"),context=buildContextPrompt({filePath:designPath,sectionContent:content,command:"wish",skill:"wish"}),contextFile=await writeContextFile(context);console.log(`\uD83D\uDCDD Dispatching wish to ${agentName} for "${slug}"`),console.log(` Design: ${designPath}`),await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",extraArgs:["--append-system-prompt-file",contextFile]});let repoPath=process.cwd();await sendMessage(repoPath,"cli",agentName,`Create a wish from the design for "${slug}". Your context is in the system prompt. Write the WISH.md with execution groups, acceptance criteria, and validation commands.`)}async function workDispatchCommand(agentName,ref){let{slug,group}=parseRef(ref),wishPath=join24(process.cwd(),".genie","wishes",slug,"WISH.md");if(!existsSync19(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);let content=await readFile9(wishPath,"utf-8"),groupSection=extractGroup(content,group);if(!groupSection){console.error(`\u274C Group "${group}" not found in ${wishPath}`),console.error(" Available groups:");let groups2=content.match(/^### Group [A-Za-z0-9]+:.*$/gm);if(groups2)for(let g of groups2)console.error(` ${g}`);process.exit(1)}let groups=parseWishGroups(content);await getOrCreateState(slug,groups);try{await startGroup(slug,group,agentName),console.log(`\u2705 Group "${group}" set to in_progress (assigned to ${agentName})`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}let wishContext=extractWishContext(content),context=buildContextPrompt({filePath:wishPath,sectionContent:groupSection,wishContext,command:`work ${ref}`,skill:"work"}),contextFile=await writeContextFile(context);console.log(`\uD83D\uDD27 Dispatching work to ${agentName} for "${ref}"`),console.log(` Wish: ${wishPath}`),console.log(` Group: ${group}`);let effectiveRole=`${agentName}-${group}`;await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",role:effectiveRole,extraArgs:["--append-system-prompt-file",contextFile]});let repoPath=process.cwd();await sendMessage(repoPath,"cli",effectiveRole,`Execute Group ${group} of wish "${slug}". Your full context is in the system prompt. Read the wish at ${wishPath} if needed. Implement all deliverables, run validation, and report completion.
1032
+ \u2705 Agents dispatched for ${nextWave.name} (groups: ${groupList})`),console.log(` Monitor: genie status ${slug}`),console.log(" Logs: genie read <agent>")}async function brainstormCommand(agentName,slug){let draftPath=join24(process.cwd(),".genie","brainstorms",slug,"DRAFT.md");if(!existsSync19(draftPath))console.error(`\u274C Draft not found: ${draftPath}`),console.error(` Create it first: mkdir -p .genie/brainstorms/${slug} && touch .genie/brainstorms/${slug}/DRAFT.md`),process.exit(1);let content=await readFile9(draftPath,"utf-8"),context=buildContextPrompt({filePath:draftPath,sectionContent:content,command:"brainstorm",skill:"brainstorm"}),contextFile=await writeContextFile(context);console.log(`\uD83D\uDCDD Dispatching brainstorm to ${agentName} for "${slug}"`),console.log(` Draft: ${draftPath}`);let brainstormPrompt=`Brainstorm "${slug}". Your context is in the system prompt. Explore the idea, ask clarifying questions, and build toward a design.`;await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:brainstormPrompt});let repoPath=process.cwd(),result=await sendMessage(repoPath,"cli",agentName,brainstormPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${agentName} failed: ${result.reason??"unknown"}`)}async function wishCommand(agentName,slug){let designPath=join24(process.cwd(),".genie","brainstorms",slug,"DESIGN.md");if(!existsSync19(designPath))console.error(`\u274C Design not found: ${designPath}`),console.error(` Run brainstorm first: genie brainstorm <agent> ${slug}`),process.exit(1);let content=await readFile9(designPath,"utf-8"),context=buildContextPrompt({filePath:designPath,sectionContent:content,command:"wish",skill:"wish"}),contextFile=await writeContextFile(context);console.log(`\uD83D\uDCDD Dispatching wish to ${agentName} for "${slug}"`),console.log(` Design: ${designPath}`);let wishPrompt=`Create a wish from the design for "${slug}". Your context is in the system prompt. Write the WISH.md with execution groups, acceptance criteria, and validation commands.`;await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:wishPrompt});let repoPath=process.cwd(),result=await sendMessage(repoPath,"cli",agentName,wishPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${agentName} failed: ${result.reason??"unknown"}`)}async function workDispatchCommand(agentName,ref){let{slug,group}=parseRef(ref),wishPath=join24(process.cwd(),".genie","wishes",slug,"WISH.md");if(!existsSync19(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);let content=await readFile9(wishPath,"utf-8"),groupSection=extractGroup(content,group);if(!groupSection){console.error(`\u274C Group "${group}" not found in ${wishPath}`),console.error(" Available groups:");let groups2=content.match(/^### Group [A-Za-z0-9]+:.*$/gm);if(groups2)for(let g of groups2)console.error(` ${g}`);process.exit(1)}let groups=parseWishGroups(content);await getOrCreateState(slug,groups);try{await startGroup(slug,group,agentName),console.log(`\u2705 Group "${group}" set to in_progress (assigned to ${agentName})`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}let wishContext=extractWishContext(content),context=buildContextPrompt({filePath:wishPath,sectionContent:groupSection,wishContext,command:`work ${ref}`,skill:"work"}),contextFile=await writeContextFile(context);console.log(`\uD83D\uDD27 Dispatching work to ${agentName} for "${ref}"`),console.log(` Wish: ${wishPath}`),console.log(` Group: ${group}`);let effectiveRole=`${agentName}-${group}`,workPrompt=`Execute Group ${group} of wish "${slug}". Your full context is in the system prompt. Read the wish at ${wishPath} if needed. Implement all deliverables, run validation, and report completion.
1033
1033
 
1034
1034
  When done:
1035
1035
  1. Run: genie done ${slug}#${group}
1036
- 2. Run: genie send 'Group ${group} complete. <summary>' --to team-lead`)}async function reviewCommand(agentName,ref){let{slug,group}=parseRef(ref),wishPath=join24(process.cwd(),".genie","wishes",slug,"WISH.md");if(!existsSync19(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),process.exit(1);let content=await readFile9(wishPath,"utf-8"),groupSection=extractGroup(content,group);if(!groupSection)console.error(`\u274C Group "${group}" not found in ${wishPath}`),process.exit(1);let diff=getGitDiff(),wishContext=extractWishContext(content),reviewContent=[groupSection,"","## Git Diff (changes to review)","",diff?`\`\`\`diff
1036
+ 2. Run: genie send 'Group ${group} complete. <summary>' --to team-lead`;await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",role:effectiveRole,extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:workPrompt});let repoPath=process.cwd(),result=await sendMessage(repoPath,"cli",effectiveRole,workPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${effectiveRole} failed: ${result.reason??"unknown"}`)}async function reviewCommand(agentName,ref){let{slug,group}=parseRef(ref),wishPath=join24(process.cwd(),".genie","wishes",slug,"WISH.md");if(!existsSync19(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),process.exit(1);let content=await readFile9(wishPath,"utf-8"),groupSection=extractGroup(content,group);if(!groupSection)console.error(`\u274C Group "${group}" not found in ${wishPath}`),process.exit(1);let diff=getGitDiff(),wishContext=extractWishContext(content),reviewContent=[groupSection,"","## Git Diff (changes to review)","",diff?`\`\`\`diff
1037
1037
  ${diff}
1038
1038
  \`\`\``:"(no uncommitted changes found \u2014 review committed changes)"].join(`
1039
1039
  `),context=buildContextPrompt({filePath:wishPath,sectionContent:reviewContent,wishContext,command:`review ${ref}`,skill:"review"}),contextFile=await writeContextFile(context);if(console.log(`\uD83D\uDD0D Dispatching review to ${agentName} for "${ref}"`),console.log(` Wish: ${wishPath}`),console.log(` Group: ${group}`),diff)console.log(` Diff: ${diff.split(`
1040
- `).length} lines`);await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",extraArgs:["--append-system-prompt-file",contextFile]});let repoPath=process.cwd();await sendMessage(repoPath,"cli",agentName,`Review "${ref}". Your context and diff are in the system prompt. Evaluate against acceptance criteria and return SHIP, FIX-FIRST, or BLOCKED with severity-tagged findings.
1040
+ `).length} lines`);let reviewPrompt=`Review "${ref}". Your context and diff are in the system prompt. Evaluate against acceptance criteria and return SHIP, FIX-FIRST, or BLOCKED with severity-tagged findings.
1041
1041
 
1042
1042
  When done, report your verdict:
1043
- Run: genie send '<SHIP|FIX-FIRST|BLOCKED> \u2014 <summary>' --to team-lead`)}function registerDispatchCommands(program2){program2.command("brainstorm <agent> <slug>").description("Spawn agent with brainstorm DRAFT.md context").action(async(agent,slug)=>{await brainstormCommand(agent,slug)}),program2.command("wish <agent> <slug>").description("Spawn agent with wish DESIGN.md context").action(async(agent,slug)=>{await wishCommand(agent,slug)}),program2.command("work <ref> [agent]").description("Auto-orchestrate a wish, or dispatch work on a specific group").action(async(ref,agent)=>{try{let work=detectWorkMode(ref,agent);if(work.mode==="auto")await autoOrchestrateCommand(work.slug);else await workDispatchCommand(work.agent,work.ref)}catch(error2){console.error(`\u274C ${error2 instanceof Error?error2.message:error2}`),process.exit(1)}}),program2.command("review <agent> <ref>").description("Spawn agent with review scope for a wish group (format: <slug>#<group>)").action(async(agent,ref)=>{await reviewCommand(agent,ref)})}init_agent_registry();function formatTime(timestamp2){try{return new Date(timestamp2).toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",hour12:!1})}catch{return"??:??"}}function truncate(str2,maxLen){if(str2.length<=maxLen)return str2;return`${str2.slice(0,maxLen-3)}...`}function formatPath(path3){let shortened=path3.replace(/^\/home\/\w+\/workspace\//,"~/").replace(/^\/home\/\w+\//,"~/");return truncate(shortened,50)}function flushPendingReads(ctx){if(ctx.pendingReads.length===0)return;ctx.events.push({timestamp:ctx.lastReadTime,type:"read",summary:ctx.pendingReads.length===1?`Read: ${formatPath(ctx.pendingReads[0])}`:`Read ${ctx.pendingReads.length} files`,details:ctx.pendingReads.length>1?ctx.pendingReads.map(formatPath):void 0}),ctx.pendingReads.length=0}function flushPendingEdits(ctx){if(ctx.pendingEdits.length===0)return;ctx.events.push({timestamp:ctx.lastEditTime,type:"edit",summary:ctx.pendingEdits.length===1?`Edit: ${formatPath(ctx.pendingEdits[0])}`:`Edit ${ctx.pendingEdits.length} files`,details:ctx.pendingEdits.length>1?ctx.pendingEdits.map(formatPath):void 0}),ctx.pendingEdits.length=0}function flushAll(ctx){flushPendingReads(ctx),flushPendingEdits(ctx)}function processToolCallEntry(entry,ctx){if(!entry.toolCall)return;let{name,input}=entry.toolCall,inputRecord=input,normalizedName=name==="shell"||name==="exec_command"?"Bash":name;switch(normalizedName){case"Read":if(ctx.lastReadTime=entry.timestamp,inputRecord.file_path)ctx.pendingReads.push(String(inputRecord.file_path));break;case"Edit":if(flushPendingReads(ctx),ctx.lastEditTime=entry.timestamp,inputRecord.file_path)ctx.pendingEdits.push(String(inputRecord.file_path));break;case"Write":flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"write",summary:`Write: ${formatPath(String(inputRecord.file_path||"unknown"))}`});break;case"Bash":{flushAll(ctx);let cmd=String(inputRecord.command||"").replace(/\n/g," ");ctx.events.push({timestamp:entry.timestamp,type:"bash",summary:`Bash: ${truncate(cmd,60)}`});break}case"AskUserQuestion":{flushAll(ctx);let questions=inputRecord.questions;ctx.events.push({timestamp:entry.timestamp,type:"question",summary:`Question: ${truncate(questions?.[0]?.question||"question",60)}`});break}default:flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"bash",summary:`${normalizedName}: ${truncate(entry.text.replace(/\n/g," "),60)}`})}}function processTranscriptEntry(entry,ctx){if(entry.role==="user"){flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"prompt",summary:truncate(entry.text.replace(/\n/g," "),80)});return}if(entry.role==="tool_call"){processToolCallEntry(entry,ctx);return}if(entry.role==="assistant"&&entry.text.length>100)flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"response",summary:truncate(entry.text.replace(/\n/g," "),80)})}function extractEvents(entries){let ctx={events:[],pendingReads:[],pendingEdits:[],lastReadTime:"",lastEditTime:""};for(let entry of entries)processTranscriptEntry(entry,ctx);return flushAll(ctx),ctx.events}function detectStatus(entries){if(entries.length===0)return"unknown";let lastEntries=entries.slice(-10);for(let entry of lastEntries.reverse())if(entry.role==="tool_call"&&entry.toolCall?.name==="AskUserQuestion")return"question";let last=entries[entries.length-1];if(last.role==="user")return"working";if(last.role==="assistant")return"idle";return"unknown"}function formatEventsForDisplay(events,stats){let lines=[];lines.push(""),lines.push(`Session: ${stats.workerId} [${stats.provider}]${stats.branch?` (${stats.branch})`:""} | ${stats.duration} | ${stats.totalEntries} entries \u2192 ${stats.compressedLines} lines`),lines.push("");for(let event of events){let time=formatTime(event.timestamp),icon=getEventIcon(event.type);if(lines.push(`[${time}] ${icon} ${event.summary}`),event.details&&event.details.length>0){for(let detail of event.details.slice(0,5))lines.push(` ${detail}`);if(event.details.length>5)lines.push(` ... and ${event.details.length-5} more`)}if(event.result)lines.push(` \u2192 ${event.result}`)}return lines.push(""),lines.push(`Status: ${stats.status.toUpperCase()} | ${stats.exchanges} exchanges | ${stats.toolCalls} tool calls | ${stats.compressionRatio.toFixed(0)}x compression`),lines.push(""),lines.join(`
1043
+ Run: genie send '<SHIP|FIX-FIRST|BLOCKED> \u2014 <summary>' --to team-lead`;await handleWorkerSpawn(agentName,{provider:"claude",team:process.env.GENIE_TEAM??"genie",extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:reviewPrompt});let repoPath=process.cwd(),result=await sendMessage(repoPath,"cli",agentName,reviewPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to ${agentName} failed: ${result.reason??"unknown"}`)}function registerDispatchCommands(program2){program2.command("brainstorm <agent> <slug>").description("Spawn agent with brainstorm DRAFT.md context").action(async(agent,slug)=>{await brainstormCommand(agent,slug)}),program2.command("wish <agent> <slug>").description("Spawn agent with wish DESIGN.md context").action(async(agent,slug)=>{await wishCommand(agent,slug)}),program2.command("work <ref> [agent]").description("Auto-orchestrate a wish, or dispatch work on a specific group").action(async(ref,agent)=>{try{let work=detectWorkMode(ref,agent);if(work.mode==="auto")await autoOrchestrateCommand(work.slug);else await workDispatchCommand(work.agent,work.ref)}catch(error2){console.error(`\u274C ${error2 instanceof Error?error2.message:error2}`),process.exit(1)}}),program2.command("review <agent> <ref>").description("Spawn agent with review scope for a wish group (format: <slug>#<group>)").action(async(agent,ref)=>{await reviewCommand(agent,ref)})}init_agent_registry();function formatTime(timestamp2){try{return new Date(timestamp2).toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",hour12:!1})}catch{return"??:??"}}function truncate(str2,maxLen){if(str2.length<=maxLen)return str2;return`${str2.slice(0,maxLen-3)}...`}function formatPath(path3){let shortened=path3.replace(/^\/home\/\w+\/workspace\//,"~/").replace(/^\/home\/\w+\//,"~/");return truncate(shortened,50)}function flushPendingReads(ctx){if(ctx.pendingReads.length===0)return;ctx.events.push({timestamp:ctx.lastReadTime,type:"read",summary:ctx.pendingReads.length===1?`Read: ${formatPath(ctx.pendingReads[0])}`:`Read ${ctx.pendingReads.length} files`,details:ctx.pendingReads.length>1?ctx.pendingReads.map(formatPath):void 0}),ctx.pendingReads.length=0}function flushPendingEdits(ctx){if(ctx.pendingEdits.length===0)return;ctx.events.push({timestamp:ctx.lastEditTime,type:"edit",summary:ctx.pendingEdits.length===1?`Edit: ${formatPath(ctx.pendingEdits[0])}`:`Edit ${ctx.pendingEdits.length} files`,details:ctx.pendingEdits.length>1?ctx.pendingEdits.map(formatPath):void 0}),ctx.pendingEdits.length=0}function flushAll(ctx){flushPendingReads(ctx),flushPendingEdits(ctx)}function processToolCallEntry(entry,ctx){if(!entry.toolCall)return;let{name,input}=entry.toolCall,inputRecord=input,normalizedName=name==="shell"||name==="exec_command"?"Bash":name;switch(normalizedName){case"Read":if(ctx.lastReadTime=entry.timestamp,inputRecord.file_path)ctx.pendingReads.push(String(inputRecord.file_path));break;case"Edit":if(flushPendingReads(ctx),ctx.lastEditTime=entry.timestamp,inputRecord.file_path)ctx.pendingEdits.push(String(inputRecord.file_path));break;case"Write":flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"write",summary:`Write: ${formatPath(String(inputRecord.file_path||"unknown"))}`});break;case"Bash":{flushAll(ctx);let cmd=String(inputRecord.command||"").replace(/\n/g," ");ctx.events.push({timestamp:entry.timestamp,type:"bash",summary:`Bash: ${truncate(cmd,60)}`});break}case"AskUserQuestion":{flushAll(ctx);let questions=inputRecord.questions;ctx.events.push({timestamp:entry.timestamp,type:"question",summary:`Question: ${truncate(questions?.[0]?.question||"question",60)}`});break}default:flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"bash",summary:`${normalizedName}: ${truncate(entry.text.replace(/\n/g," "),60)}`})}}function processTranscriptEntry(entry,ctx){if(entry.role==="user"){flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"prompt",summary:truncate(entry.text.replace(/\n/g," "),80)});return}if(entry.role==="tool_call"){processToolCallEntry(entry,ctx);return}if(entry.role==="assistant"&&entry.text.length>100)flushAll(ctx),ctx.events.push({timestamp:entry.timestamp,type:"response",summary:truncate(entry.text.replace(/\n/g," "),80)})}function extractEvents(entries){let ctx={events:[],pendingReads:[],pendingEdits:[],lastReadTime:"",lastEditTime:""};for(let entry of entries)processTranscriptEntry(entry,ctx);return flushAll(ctx),ctx.events}function detectStatus(entries){if(entries.length===0)return"unknown";let lastEntries=entries.slice(-10);for(let entry of lastEntries.reverse())if(entry.role==="tool_call"&&entry.toolCall?.name==="AskUserQuestion")return"question";let last=entries[entries.length-1];if(last.role==="user")return"working";if(last.role==="assistant")return"idle";return"unknown"}function formatEventsForDisplay(events,stats){let lines=[];lines.push(""),lines.push(`Session: ${stats.workerId} [${stats.provider}]${stats.branch?` (${stats.branch})`:""} | ${stats.duration} | ${stats.totalEntries} entries \u2192 ${stats.compressedLines} lines`),lines.push("");for(let event of events){let time=formatTime(event.timestamp),icon=getEventIcon(event.type);if(lines.push(`[${time}] ${icon} ${event.summary}`),event.details&&event.details.length>0){for(let detail of event.details.slice(0,5))lines.push(` ${detail}`);if(event.details.length>5)lines.push(` ... and ${event.details.length-5} more`)}if(event.result)lines.push(` \u2192 ${event.result}`)}return lines.push(""),lines.push(`Status: ${stats.status.toUpperCase()} | ${stats.exchanges} exchanges | ${stats.toolCalls} tool calls | ${stats.compressionRatio.toFixed(0)}x compression`),lines.push(""),lines.join(`
1044
1044
  `)}function getEventIcon(type2){switch(type2){case"prompt":return"\uD83D\uDCAC";case"read":return"\uD83D\uDCD6";case"edit":return"\u270F\uFE0F";case"write":return"\uD83D\uDCDD";case"bash":return"\u26A1";case"question":return"\u2753";case"answer":return"\u2705";case"permission":return"\uD83D\uDD10";case"thinking":return"\uD83E\uDD14";case"response":return"\uD83D\uDCAD";default:return"\u2022"}}function formatToolDetail(toolCall){let input=toolCall.input;if(toolCall.name==="Bash"||toolCall.name==="shell")return` ${toolCall.name}: ${input.command??""}`;if(["Read","Edit","Write"].includes(toolCall.name))return` ${toolCall.name}: ${input.file_path??""}`;return` ${toolCall.name}`}function formatTranscriptEntryForDisplay(entry){let time=formatTime(entry.timestamp);switch(entry.role){case"user":return[`
1045
1045
  [${time}] USER:`,entry.text];case"tool_call":return entry.toolCall?[`
1046
1046
  [${time}] TOOL:`,formatToolDetail(entry.toolCall)]:[];case"assistant":return[`
@@ -1217,7 +1217,7 @@ History for "${schedule.name}":
1217
1217
  Tags: ${tags.map((t)=>t.name).join(", ")}`);let blockers=await ts.getBlockers(task.id,task.repoPath);if(blockers.length>0){console.log(`
1218
1218
  Dependencies:`);for(let dep of blockers){let depTask=await ts.getTask(dep.dependsOnId,task.repoPath),label=depTask?`#${depTask.seq} ${depTask.title}`:dep.dependsOnId;console.log(` ${dep.depType}: ${label}`)}}let stageLog=await ts.getStageLog(task.id,task.repoPath);if(stageLog.length>0){console.log(`
1219
1219
  Stage History:`);for(let entry of stageLog.slice(0,10)){let who=entry.actorId??"system";console.log(` ${formatTimestamp3(entry.createdAt)}: ${entry.fromStage??"(new)"} \u2192 ${entry.toStage} by ${who}`)}}}async function printTaskMessages(task){let ts=await getTaskService6(),conv=await ts.findOrCreateConversation({linkedEntity:"task",linkedEntityId:task.id,name:`Task #${task.seq}`}),messages2=await ts.getMessages(conv.id,{limit:20});if(messages2.length>0){console.log(`
1220
- Messages:`);for(let msg of messages2){let time=formatTimestamp3(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}}}async function printTaskDetail(task){printTaskFields(task),await printTaskRelations(task),await printTaskMessages(task),console.log("")}async function handleTaskCreate(title,options){let ts=await getTaskService6(),actor=currentActor2(),repoPath,projectId;if(options.project){let project=await ts.getProjectByName(options.project);if(!project)project=await ts.createProject({name:options.project});projectId=project.id,repoPath=project.repoPath??void 0}let parentId;if(options.parent){if(parentId=await ts.resolveTaskId(options.parent,repoPath)??void 0,!parentId)console.error(`Error: Parent task not found: ${options.parent}`),process.exit(1)}let task=await ts.createTask({title,typeId:options.type,priority:options.priority,dueDate:options.due,startDate:options.start,parentId,description:options.description,estimatedEffort:options.effort},repoPath,projectId);if(await ts.assignTask(task.id,actor,"creator",{},task.repoPath),options.assign)await ts.assignTask(task.id,localActor2(options.assign),"assignee",{},task.repoPath);if(options.tags){let tagIds=options.tags.split(",").map((t)=>t.trim());await ts.tagTask(task.id,tagIds,actor,task.repoPath)}if(options.comment)await ts.commentOnTask(task.id,actor,options.comment,task.repoPath);if(console.log(`Created task #${task.seq}: ${task.title}`),console.log(` ID: ${task.id}`),console.log(` Stage: ${task.stage} | Priority: ${task.priority}`),options.due)console.log(` Due: ${options.due}`)}function registerTaskCommands(program2){let task=program2.command("task").description("Task lifecycle management");task.command("create <title>").description("Create a new task").option("--type <type>","Task type","software").option("--priority <priority>","Priority: urgent, high, normal, low","normal").option("--due <date>","Due date (YYYY-MM-DD)").option("--start <date>","Start date (YYYY-MM-DD)").option("--tags <tags>","Comma-separated tag IDs").option("--parent <id>","Parent task ID or #seq").option("--assign <name>","Assign to local actor").option("--description <text>","Task description").option("--effort <effort>",'Estimated effort (e.g., "2h", "3 points")').option("--comment <msg>","Initial comment on the task").option("--project <name>","Create task in a specific project (overrides CWD)").action(async(title,options)=>{try{await handleTaskCreate(title,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("list").description("List tasks with filters").option("--stage <stage>","Filter by stage").option("--type <type>","Filter by type").option("--status <status>","Filter by status").option("--priority <priority>","Filter by priority").option("--release <release>","Filter by release").option("--due-before <date>","Filter by due date").option("--mine","Show only tasks assigned to me").option("--project <name>","Show tasks for a specific project").option("--all","Show tasks from ALL projects").option("--json","Output as JSON").action(async(options)=>{try{let ts=await getTaskService6(),filters={stage:options.stage,typeId:options.type,status:options.status,priority:options.priority,releaseId:options.release,dueBefore:options.dueBefore,projectName:options.project,allProjects:options.all},tasks;if(options.mine)tasks=await ts.listTasksForActor(currentActor2(),filters);else tasks=await ts.listTasks(filters);if(options.json){console.log(JSON.stringify(tasks,null,2));return}printTaskList(tasks,options.all)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("show <id>").description("Show task detail (accepts task-id or #seq)").option("--json","Output as JSON").action(async(id,options)=>{try{let t=await(await getTaskService6()).getTask(id);if(!t)console.error(`Error: Task not found: ${id}`),process.exit(1);if(options.json){console.log(JSON.stringify(t,null,2));return}await printTaskDetail(t)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("move <id>").description("Move task to a new stage").requiredOption("--to <stage>","Target stage").option("--comment <msg>","Comment on the move").action(async(id,options)=>{try{let ts=await getTaskService6(),actor=currentActor2(),t=await ts.moveTask(id,options.to,actor,options.comment);console.log(`Moved task #${t.seq} to stage "${t.stage}".`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("assign <id>").description("Assign an actor to a task").requiredOption("--to <name>","Actor name").option("--role <role>","Actor role","assignee").option("--comment <msg>","Comment on the assignment").action(async(id,options)=>{try{let ts=await getTaskService6(),actor=currentActor2();if(await ts.assignTask(id,localActor2(options.to),options.role,{}),options.comment)await ts.commentOnTask(id,actor,options.comment);console.log(`Assigned "${options.to}" as ${options.role??"assignee"} on task ${id}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("tag <id> <tags...>").description("Add tags to a task").action(async(id,tags)=>{try{await(await getTaskService6()).tagTask(id,tags,currentActor2()),console.log(`Tagged task ${id} with: ${tags.join(", ")}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("comment <id> <message>").description("Add a comment to a task").option("--reply-to <msgId>","Reply to a specific message ID").action(async(id,message,options)=>{try{let ts=await getTaskService6(),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts.commentOnTask(id,currentActor2(),message,void 0,replyTo);console.log(`Comment #${msg.id} added to task ${id}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("block <id>").description("Mark task as blocked").requiredOption("--reason <reason>","Reason for blocking").option("--comment <msg>","Additional comment").action(async(id,options)=>{try{let ts=await getTaskService6(),actor=currentActor2(),t=await ts.blockTask(id,options.reason,actor,options.comment);console.log(`Task #${t.seq} blocked: ${options.reason}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unblock <id>").description("Unblock a task").option("--comment <msg>","Comment on unblock").action(async(id,options)=>{try{let ts=await getTaskService6(),actor=currentActor2(),t=await ts.unblockTask(id,actor,options.comment);console.log(`Task #${t.seq} unblocked.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("done <id>").description("Mark task as done").option("--comment <msg>","Comment on completion").action(async(id,options)=>{try{let ts=await getTaskService6(),actor=currentActor2(),t=await ts.markDone(id,actor,options.comment);console.log(`Task #${t.seq} marked as done.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("checkout <id>").description("Atomically claim a task for execution").action(async(id)=>{try{let ts=await getTaskService6(),runId=getRunId(),t=await ts.checkoutTask(id,runId);console.log(`Checked out task #${t.seq} for run: ${runId}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("release <id>").description("Release task checkout claim").action(async(id)=>{try{let ts=await getTaskService6(),runId=getRunId(),t=await ts.releaseTask(id,runId);console.log(`Released task #${t.seq} from run: ${runId}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unlock <id>").description("Force-release a stale checkout (admin override)").action(async(id)=>{try{let t=await(await getTaskService6()).forceUnlockTask(id);console.log(`Force-unlocked task #${t.seq}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("dep <id>").description("Manage task dependencies").option("--depends-on <id2>","This task depends on id2").option("--blocks <id2>","This task blocks id2").option("--relates-to <id2>","This task relates to id2").option("--remove <id2>","Remove dependency on id2").action(async(id,options)=>{try{let ts=await getTaskService6();if(options.remove){if(await ts.removeDependency(id,options.remove))console.log(`Removed dependency between ${id} and ${options.remove}.`);else console.log("No dependency found to remove.");return}if(options.dependsOn)await ts.addDependency(id,options.dependsOn,"depends_on"),console.log(`${id} now depends on ${options.dependsOn}.`);if(options.blocks)await ts.addDependency(id,options.blocks,"blocks"),console.log(`${id} now blocks ${options.blocks}.`);if(options.relatesTo)await ts.addDependency(id,options.relatesTo,"relates_to"),console.log(`${id} now relates to ${options.relatesTo}.`);if(!options.dependsOn&&!options.blocks&&!options.relatesTo)console.error("Error: Specify --depends-on, --blocks, --relates-to, or --remove."),process.exit(1)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_team_manager();import{existsSync as existsSync20}from"fs";import{copyFile as copyFile2,mkdir as mkdir11}from"fs/promises";import{join as join32,resolve as resolve6}from"path";function registerTeamNamespace(program2){let team=program2.command("team").description("Team lifecycle management");team.command("create <name>").description("Create a new team with a git worktree").requiredOption("--repo <path>","Path to the git repository").option("--branch <branch>","Base branch to create from","dev").option("--wish <slug>","Wish slug \u2014 auto-spawns a task leader with wish context").option("--session <name>","Tmux session name (avoids session explosion on parallel creates)").action(async(name,options)=>{try{await handleTeamCreate(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("hire <agent>").description('Add an agent to a team ("council" hires all 10 council members)').option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);let added=await hireAgent(teamName,agent);if(added.length===0)console.log(`Agent "${agent}" is already a member of "${teamName}".`);else if(agent==="council"){console.log(`Hired ${added.length} council members to "${teamName}":`);for(let name of added)console.log(` + ${name}`)}else console.log(`Hired "${agent}" to team "${teamName}".`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("fire <agent>").description("Remove an agent from a team").option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);if(await fireAgent(teamName,agent))console.log(`Fired "${agent}" from team "${teamName}".`);else console.error(`Agent "${agent}" is not a member of "${teamName}".`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("ls [name]").alias("list").description("List teams or members of a team").option("--json","Output as JSON").action(async(name,options)=>{try{if(name)await printMembers(name,options.json);else await printTeams(options.json)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("disband <name>").description("Disband a team: kill members, remove worktree, delete config").action(async(name)=>{try{if(await disbandTeam(name))console.log(`Team "${name}" disbanded.`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("done <name>").description("Mark a team as done and kill all members").action(async(name)=>{try{await setTeamStatus(name,"done"),await killTeamMembers(name),console.log(`Team "${name}" marked as done. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("blocked <name>").description("Mark a team as blocked and kill all members").action(async(name)=>{try{await setTeamStatus(name,"blocked"),await killTeamMembers(name),console.log(`Team "${name}" marked as blocked. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleTeamCreate(name,options){if(options.wish){let resolvedRepo=resolve6(options.repo),wishPath=join32(resolvedRepo,".genie","wishes",options.wish,"WISH.md");if(!existsSync20(wishPath))console.error(`Error: Wish not found at ${wishPath}`),process.exit(1)}let config=await createTeam(name,options.repo,options.branch);if(options.session)config.tmuxSessionName=options.session,await updateTeamConfig(name,config);if(console.log(`Team "${config.name}" created.`),console.log(` Worktree: ${config.worktreePath}`),console.log(` Branch: ${config.name} (from ${config.baseBranch})`),config.tmuxSessionName)console.log(` Session: ${config.tmuxSessionName}`);if(config.nativeTeamsEnabled)console.log(" Native teams: enabled");if(options.wish)await spawnLeaderWithWish(config,options.wish,options.repo,options.session)}async function spawnLeaderWithWish(config,slug,repoPath,sessionOverride){let{handleWorkerSpawn:handleWorkerSpawn2}=await Promise.resolve().then(() => (init_agents(),exports_agents)),{getCurrentSessionName:getCurrentSessionName2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),resolvedRepo=resolve6(repoPath),tmuxSession=sessionOverride??await getCurrentSessionName2(config.name)??config.name;config.tmuxSessionName=tmuxSession,await updateTeamConfig(config.name,config);let sourceWishPath=join32(resolvedRepo,".genie","wishes",slug,"WISH.md");if(!existsSync20(sourceWishPath))console.error(`Error: Wish not found at ${sourceWishPath}`),process.exit(1);let destWishDir=join32(config.worktreePath,".genie","wishes",slug);await mkdir11(destWishDir,{recursive:!0});let destWishPath=join32(destWishDir,"WISH.md");await copyFile2(sourceWishPath,destWishPath),console.log(` Wish: copied ${slug}/WISH.md into worktree`);let standardTeam=["team-lead","engineer","reviewer","qa","fix"];for(let role of standardTeam)await hireAgent(config.name,role);console.log(` Team: hired ${standardTeam.join(", ")}`),await handleWorkerSpawn2("team-lead",{provider:"claude",team:config.name,cwd:config.worktreePath,session:tmuxSession});let members=standardTeam.filter((r)=>r!=="team-lead").join(", "),kickoffPrompt=`Your team is "${config.name}". Repo: ${config.repo}. Branch: ${config.name}. Worktree: ${config.worktreePath}. Wish slug: ${slug}. Your team members are: ${members} (already hired \u2014 genie work will spawn them automatically). Read the wish at .genie/wishes/${slug}/WISH.md and execute the full lifecycle autonomously.`;await(await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router))).sendMessage(config.worktreePath,"cli","team-lead",kickoffPrompt),console.log(" Leader: spawned and working")}async function autoDetectTeam(){let envTeam=process.env.GENIE_TEAM;if(envTeam)return envTeam;let teams=await listTeams2();if(teams.length===1)return teams[0].name;return null}async function printMembers(name,json2){let members=await listMembers(name);if(members===null)console.error(`Team "${name}" not found.`),process.exit(1);if(json2){console.log(JSON.stringify(members,null,2));return}if(members.length===0){console.log(`Team "${name}" has no members. Hire agents with: genie team hire <agent> --team ${name}`);return}console.log(""),console.log(`MEMBERS of "${name}"`),console.log("-".repeat(60));for(let m of members)console.log(` ${m}`);console.log("")}async function printTeams(json2){let teams=await listTeams2();if(json2){console.log(JSON.stringify(teams,null,2));return}if(teams.length===0){console.log("No teams found. Create one with: genie team create <name> --repo <path>");return}console.log(""),console.log("TEAMS"),console.log("-".repeat(60));for(let t of teams)printTeamSummary(t);console.log("")}function printTeamSummary(t){let status=t.status??"in_progress";console.log(` ${t.name} [${status}]`),console.log(` Repo: ${t.repo}`),console.log(` Branch: ${t.name} (from ${t.baseBranch})`),console.log(` Worktree: ${t.worktreePath}`),console.log(` Members: ${t.members.length}`)}var _taskService7;async function getTaskService7(){if(!_taskService7)_taskService7=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService7}function padRight10(str3,len){return str3.length>=len?str3:str3+" ".repeat(len-str3.length)}function printTypeTable(types3){console.log(` ${padRight10("ID",20)} ${padRight10("NAME",30)} ${padRight10("STAGES",8)} BUILTIN`),console.log(` ${"\u2500".repeat(70)}`);for(let t of types3){let stageCount=Array.isArray(t.stages)?t.stages.length:0,builtin=t.isBuiltin?"yes":"no";console.log(` ${padRight10(t.id,20)} ${padRight10(t.name,30)} ${padRight10(String(stageCount),8)} ${builtin}`)}console.log(`
1220
+ Messages:`);for(let msg of messages2){let time=formatTimestamp3(msg.createdAt),reply=msg.replyToId?` (reply to #${msg.replyToId})`:"";console.log(` [${time}] ${msg.senderId}: ${msg.body}${reply}`)}}}async function printTaskDetail(task){printTaskFields(task),await printTaskRelations(task),await printTaskMessages(task),console.log("")}async function handleTaskCreate(title,options){let ts=await getTaskService6(),actor=currentActor2(),repoPath,projectId;if(options.project){let project=await ts.getProjectByName(options.project);if(!project)project=await ts.createProject({name:options.project});projectId=project.id,repoPath=project.repoPath??void 0}let parentId;if(options.parent){if(parentId=await ts.resolveTaskId(options.parent,repoPath)??void 0,!parentId)console.error(`Error: Parent task not found: ${options.parent}`),process.exit(1)}let task=await ts.createTask({title,typeId:options.type,priority:options.priority,dueDate:options.due,startDate:options.start,parentId,description:options.description,estimatedEffort:options.effort},repoPath,projectId);if(await ts.assignTask(task.id,actor,"creator",{},task.repoPath),options.assign)await ts.assignTask(task.id,localActor2(options.assign),"assignee",{},task.repoPath);if(options.tags){let tagIds=options.tags.split(",").map((t)=>t.trim());await ts.tagTask(task.id,tagIds,actor,task.repoPath)}if(options.comment)await ts.commentOnTask(task.id,actor,options.comment,task.repoPath);if(console.log(`Created task #${task.seq}: ${task.title}`),console.log(` ID: ${task.id}`),console.log(` Stage: ${task.stage} | Priority: ${task.priority}`),options.due)console.log(` Due: ${options.due}`)}function registerTaskCommands(program2){let task=program2.command("task").description("Task lifecycle management");task.command("create <title>").description("Create a new task").option("--type <type>","Task type","software").option("--priority <priority>","Priority: urgent, high, normal, low","normal").option("--due <date>","Due date (YYYY-MM-DD)").option("--start <date>","Start date (YYYY-MM-DD)").option("--tags <tags>","Comma-separated tag IDs").option("--parent <id>","Parent task ID or #seq").option("--assign <name>","Assign to local actor").option("--description <text>","Task description").option("--effort <effort>",'Estimated effort (e.g., "2h", "3 points")').option("--comment <msg>","Initial comment on the task").option("--project <name>","Create task in a specific project (overrides CWD)").action(async(title,options)=>{try{await handleTaskCreate(title,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("list").description("List tasks with filters").option("--stage <stage>","Filter by stage").option("--type <type>","Filter by type").option("--status <status>","Filter by status").option("--priority <priority>","Filter by priority").option("--release <release>","Filter by release").option("--due-before <date>","Filter by due date").option("--mine","Show only tasks assigned to me").option("--project <name>","Show tasks for a specific project").option("--all","Show tasks from ALL projects").option("--json","Output as JSON").action(async(options)=>{try{let ts=await getTaskService6(),filters={stage:options.stage,typeId:options.type,status:options.status,priority:options.priority,releaseId:options.release,dueBefore:options.dueBefore,projectName:options.project,allProjects:options.all},tasks;if(options.mine)tasks=await ts.listTasksForActor(currentActor2(),filters);else tasks=await ts.listTasks(filters);if(options.json){console.log(JSON.stringify(tasks,null,2));return}printTaskList(tasks,options.all)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("show <id>").description("Show task detail (accepts task-id or #seq)").option("--json","Output as JSON").action(async(id,options)=>{try{let t=await(await getTaskService6()).getTask(id);if(!t)console.error(`Error: Task not found: ${id}`),process.exit(1);if(options.json){console.log(JSON.stringify(t,null,2));return}await printTaskDetail(t)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("move <id>").description("Move task to a new stage").requiredOption("--to <stage>","Target stage").option("--comment <msg>","Comment on the move").action(async(id,options)=>{try{let ts=await getTaskService6(),actor=currentActor2(),t=await ts.moveTask(id,options.to,actor,options.comment);console.log(`Moved task #${t.seq} to stage "${t.stage}".`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("assign <id>").description("Assign an actor to a task").requiredOption("--to <name>","Actor name").option("--role <role>","Actor role","assignee").option("--comment <msg>","Comment on the assignment").action(async(id,options)=>{try{let ts=await getTaskService6(),actor=currentActor2();if(await ts.assignTask(id,localActor2(options.to),options.role,{}),options.comment)await ts.commentOnTask(id,actor,options.comment);console.log(`Assigned "${options.to}" as ${options.role??"assignee"} on task ${id}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("tag <id> <tags...>").description("Add tags to a task").action(async(id,tags)=>{try{await(await getTaskService6()).tagTask(id,tags,currentActor2()),console.log(`Tagged task ${id} with: ${tags.join(", ")}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("comment <id> <message>").description("Add a comment to a task").option("--reply-to <msgId>","Reply to a specific message ID").action(async(id,message,options)=>{try{let ts=await getTaskService6(),replyTo=options.replyTo?Number(options.replyTo):void 0,msg=await ts.commentOnTask(id,currentActor2(),message,void 0,replyTo);console.log(`Comment #${msg.id} added to task ${id}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("block <id>").description("Mark task as blocked").requiredOption("--reason <reason>","Reason for blocking").option("--comment <msg>","Additional comment").action(async(id,options)=>{try{let ts=await getTaskService6(),actor=currentActor2(),t=await ts.blockTask(id,options.reason,actor,options.comment);console.log(`Task #${t.seq} blocked: ${options.reason}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unblock <id>").description("Unblock a task").option("--comment <msg>","Comment on unblock").action(async(id,options)=>{try{let ts=await getTaskService6(),actor=currentActor2(),t=await ts.unblockTask(id,actor,options.comment);console.log(`Task #${t.seq} unblocked.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("done <id>").description("Mark task as done").option("--comment <msg>","Comment on completion").action(async(id,options)=>{try{let ts=await getTaskService6(),actor=currentActor2(),t=await ts.markDone(id,actor,options.comment);console.log(`Task #${t.seq} marked as done.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("checkout <id>").description("Atomically claim a task for execution").action(async(id)=>{try{let ts=await getTaskService6(),runId=getRunId(),t=await ts.checkoutTask(id,runId);console.log(`Checked out task #${t.seq} for run: ${runId}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("release <id>").description("Release task checkout claim").action(async(id)=>{try{let ts=await getTaskService6(),runId=getRunId(),t=await ts.releaseTask(id,runId);console.log(`Released task #${t.seq} from run: ${runId}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("unlock <id>").description("Force-release a stale checkout (admin override)").action(async(id)=>{try{let t=await(await getTaskService6()).forceUnlockTask(id);console.log(`Force-unlocked task #${t.seq}.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),task.command("dep <id>").description("Manage task dependencies").option("--depends-on <id2>","This task depends on id2").option("--blocks <id2>","This task blocks id2").option("--relates-to <id2>","This task relates to id2").option("--remove <id2>","Remove dependency on id2").action(async(id,options)=>{try{let ts=await getTaskService6();if(options.remove){if(await ts.removeDependency(id,options.remove))console.log(`Removed dependency between ${id} and ${options.remove}.`);else console.log("No dependency found to remove.");return}if(options.dependsOn)await ts.addDependency(id,options.dependsOn,"depends_on"),console.log(`${id} now depends on ${options.dependsOn}.`);if(options.blocks)await ts.addDependency(id,options.blocks,"blocks"),console.log(`${id} now blocks ${options.blocks}.`);if(options.relatesTo)await ts.addDependency(id,options.relatesTo,"relates_to"),console.log(`${id} now relates to ${options.relatesTo}.`);if(!options.dependsOn&&!options.blocks&&!options.relatesTo)console.error("Error: Specify --depends-on, --blocks, --relates-to, or --remove."),process.exit(1)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_team_manager();import{existsSync as existsSync20}from"fs";import{copyFile as copyFile2,mkdir as mkdir11}from"fs/promises";import{join as join32,resolve as resolve6}from"path";function registerTeamNamespace(program2){let team=program2.command("team").description("Team lifecycle management");team.command("create <name>").description("Create a new team with a git worktree").requiredOption("--repo <path>","Path to the git repository").option("--branch <branch>","Base branch to create from","dev").option("--wish <slug>","Wish slug \u2014 auto-spawns a task leader with wish context").option("--session <name>","Tmux session name (avoids session explosion on parallel creates)").action(async(name,options)=>{try{await handleTeamCreate(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("hire <agent>").description('Add an agent to a team ("council" hires all 10 council members)').option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);let added=await hireAgent(teamName,agent);if(added.length===0)console.log(`Agent "${agent}" is already a member of "${teamName}".`);else if(agent==="council"){console.log(`Hired ${added.length} council members to "${teamName}":`);for(let name of added)console.log(` + ${name}`)}else console.log(`Hired "${agent}" to team "${teamName}".`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("fire <agent>").description("Remove an agent from a team").option("--team <name>","Team name (auto-detects from leader context if omitted)").action(async(agent,options)=>{try{let teamName=options.team??await autoDetectTeam();if(!teamName)console.error("Error: Could not detect team. Use --team <name> to specify."),process.exit(1);if(await fireAgent(teamName,agent))console.log(`Fired "${agent}" from team "${teamName}".`);else console.error(`Agent "${agent}" is not a member of "${teamName}".`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("ls [name]").alias("list").description("List teams or members of a team").option("--json","Output as JSON").action(async(name,options)=>{try{if(name)await printMembers(name,options.json);else await printTeams(options.json)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("disband <name>").description("Disband a team: kill members, remove worktree, delete config").action(async(name)=>{try{if(await disbandTeam(name))console.log(`Team "${name}" disbanded.`);else console.error(`Team "${name}" not found.`),process.exit(1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("done <name>").description("Mark a team as done and kill all members").action(async(name)=>{try{await setTeamStatus(name,"done"),await killTeamMembers(name),console.log(`Team "${name}" marked as done. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}}),team.command("blocked <name>").description("Mark a team as blocked and kill all members").action(async(name)=>{try{await setTeamStatus(name,"blocked"),await killTeamMembers(name),console.log(`Team "${name}" marked as blocked. All members killed.`)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}})}async function handleTeamCreate(name,options){if(options.wish){let resolvedRepo=resolve6(options.repo),wishPath=join32(resolvedRepo,".genie","wishes",options.wish,"WISH.md");if(!existsSync20(wishPath))console.error(`Error: Wish not found at ${wishPath}`),process.exit(1)}let config=await createTeam(name,options.repo,options.branch);if(options.session)config.tmuxSessionName=options.session,await updateTeamConfig(name,config);if(console.log(`Team "${config.name}" created.`),console.log(` Worktree: ${config.worktreePath}`),console.log(` Branch: ${config.name} (from ${config.baseBranch})`),config.tmuxSessionName)console.log(` Session: ${config.tmuxSessionName}`);if(config.nativeTeamsEnabled)console.log(" Native teams: enabled");if(options.wish)await spawnLeaderWithWish(config,options.wish,options.repo,options.session)}async function spawnLeaderWithWish(config,slug,repoPath,sessionOverride){let{handleWorkerSpawn:handleWorkerSpawn2}=await Promise.resolve().then(() => (init_agents(),exports_agents)),{getCurrentSessionName:getCurrentSessionName2}=await Promise.resolve().then(() => (init_tmux(),exports_tmux)),resolvedRepo=resolve6(repoPath),tmuxSession=sessionOverride??await getCurrentSessionName2(config.name)??config.name;config.tmuxSessionName=tmuxSession,await updateTeamConfig(config.name,config);let sourceWishPath=join32(resolvedRepo,".genie","wishes",slug,"WISH.md");if(!existsSync20(sourceWishPath))console.error(`Error: Wish not found at ${sourceWishPath}`),process.exit(1);let destWishDir=join32(config.worktreePath,".genie","wishes",slug);await mkdir11(destWishDir,{recursive:!0});let destWishPath=join32(destWishDir,"WISH.md");await copyFile2(sourceWishPath,destWishPath),console.log(` Wish: copied ${slug}/WISH.md into worktree`);let standardTeam=["team-lead","engineer","reviewer","qa","fix"];for(let role of standardTeam)await hireAgent(config.name,role);console.log(` Team: hired ${standardTeam.join(", ")}`);let members=standardTeam.filter((r)=>r!=="team-lead").join(", "),kickoffPrompt=`Your team is "${config.name}". Repo: ${config.repo}. Branch: ${config.name}. Worktree: ${config.worktreePath}. Wish slug: ${slug}. Your team members are: ${members} (already hired \u2014 genie work will spawn them automatically). Read the wish at .genie/wishes/${slug}/WISH.md and execute the full lifecycle autonomously.`;await handleWorkerSpawn2("team-lead",{provider:"claude",team:config.name,cwd:config.worktreePath,session:tmuxSession,initialPrompt:kickoffPrompt});let result=await(await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router))).sendMessage(config.worktreePath,"cli","team-lead",kickoffPrompt);if(!result.delivered)console.warn(`\u26A0 Backup delivery to team-lead failed: ${result.reason??"unknown"}`);console.log(" Leader: spawned and working")}async function autoDetectTeam(){let envTeam=process.env.GENIE_TEAM;if(envTeam)return envTeam;let teams=await listTeams2();if(teams.length===1)return teams[0].name;return null}async function printMembers(name,json2){let members=await listMembers(name);if(members===null)console.error(`Team "${name}" not found.`),process.exit(1);if(json2){console.log(JSON.stringify(members,null,2));return}if(members.length===0){console.log(`Team "${name}" has no members. Hire agents with: genie team hire <agent> --team ${name}`);return}console.log(""),console.log(`MEMBERS of "${name}"`),console.log("-".repeat(60));for(let m of members)console.log(` ${m}`);console.log("")}async function printTeams(json2){let teams=await listTeams2();if(json2){console.log(JSON.stringify(teams,null,2));return}if(teams.length===0){console.log("No teams found. Create one with: genie team create <name> --repo <path>");return}console.log(""),console.log("TEAMS"),console.log("-".repeat(60));for(let t of teams)printTeamSummary(t);console.log("")}function printTeamSummary(t){let status=t.status??"in_progress";console.log(` ${t.name} [${status}]`),console.log(` Repo: ${t.repo}`),console.log(` Branch: ${t.name} (from ${t.baseBranch})`),console.log(` Worktree: ${t.worktreePath}`),console.log(` Members: ${t.members.length}`)}var _taskService7;async function getTaskService7(){if(!_taskService7)_taskService7=await Promise.resolve().then(() => (init_task_service(),exports_task_service));return _taskService7}function padRight10(str3,len){return str3.length>=len?str3:str3+" ".repeat(len-str3.length)}function printTypeTable(types3){console.log(` ${padRight10("ID",20)} ${padRight10("NAME",30)} ${padRight10("STAGES",8)} BUILTIN`),console.log(` ${"\u2500".repeat(70)}`);for(let t of types3){let stageCount=Array.isArray(t.stages)?t.stages.length:0,builtin=t.isBuiltin?"yes":"no";console.log(` ${padRight10(t.id,20)} ${padRight10(t.name,30)} ${padRight10(String(stageCount),8)} ${builtin}`)}console.log(`
1221
1221
  ${types3.length} type${types3.length===1?"":"s"}`)}function printTypePipeline(t){if(console.log(`
1222
1222
  Type: ${t.name} (${t.id})`),t.description)console.log(`Description: ${t.description}`);if(t.icon)console.log(`Icon: ${t.icon}`);console.log(`Built-in: ${t.isBuiltin?"yes":"no"}`),console.log("\u2500".repeat(60)),console.log(`
1223
1223
  Stage Pipeline:`);let stages=t.stages;for(let i2=0;i2<stages.length;i2++){let s=stages[i2],arrow=i2<stages.length-1?" \u2192":"",gate=s.gate?` [gate: ${s.gate}]`:"",action=s.action?` (action: ${s.action})`:"",auto=s.auto_advance?" [auto]":"";console.log(` ${i2+1}. ${s.label??s.name}${gate}${action}${auto}${arrow}`)}console.log("")}async function handleTypeList(options){let types3=await(await getTaskService7()).listTypes();if(options.json){console.log(JSON.stringify(types3,null,2));return}printTypeTable(types3)}async function handleTypeShow(id,options){let t=await(await getTaskService7()).getType(id);if(!t)console.error(`Error: Type not found: ${id}`),process.exit(1);if(options.json){console.log(JSON.stringify(t,null,2));return}printTypePipeline(t)}async function handleTypeCreate(name,options){let ts=await getTaskService7(),stages;try{if(stages=JSON.parse(options.stages),!Array.isArray(stages))throw Error("Stages must be a JSON array")}catch(err){console.error(`Error: Invalid stages JSON. ${err instanceof Error?err.message:String(err)}`),process.exit(1)}for(let s of stages)if(typeof s!=="object"||s===null||!("name"in s))console.error('Error: Each stage must have at least a "name" field.'),process.exit(1);let id=name.toLowerCase().replace(/\s+/g,"-"),t=await ts.createType({id,name,description:options.description,icon:options.icon,stages});console.log(`Created type "${t.name}" (${t.id}) with ${stages.length} stages.`)}function registerTypeCommands(program2){let type2=program2.command("type").description("Task type management");type2.command("list").description("List all task types").option("--json","Output as JSON").action(async(options)=>{try{await handleTypeList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),type2.command("show <id>").description("Show task type detail with stage pipeline").option("--json","Output as JSON").action(async(id,options)=>{try{await handleTypeShow(id,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),type2.command("create <name>").description("Create a custom task type").requiredOption("--stages <json>","Stages JSON array").option("--description <text>","Type description").option("--icon <icon>","Type icon").action(async(name,options)=>{try{await handleTypeCreate(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}try{let{execSync:execSyncStartup}=__require("child_process");if(execSyncStartup("git config core.bare",{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()==="true")execSyncStartup("git config core.bare false",{stdio:["pipe","pipe","pipe"]})}catch{}var program2=new Command;program2.name("genie").description("Genie CLI - AI-assisted development").version(VERSION);async function startNamedSession(name){let{buildTeamLeadCommand:buildTeamLeadCommand2,sessionExists:sessionExists2}=await Promise.resolve().then(() => (init_team_lead_command(),exports_team_lead_command)),{getAgentsFilePath:getAgentsFilePath2}=await Promise.resolve().then(() => (init_session(),exports_session)),systemPromptFile=getAgentsFilePath2(),hasPriorSession=sessionExists2(name),cmd=buildTeamLeadCommand2(name,{systemPromptFile:systemPromptFile??void 0,continueName:hasPriorSession?name:void 0});console.log(hasPriorSession?`Resuming session: ${name}`:`Starting new session: ${name}`);let{spawnSync:spawnSync2}=await import("child_process"),result=spawnSync2("sh",["-c",cmd],{stdio:"inherit"});if(result.status)process.exit(result.status)}program2.command("setup").description("Configure genie settings").option("--quick","Accept all defaults").option("--shortcuts","Only configure keyboard shortcuts").option("--codex","Only configure Codex integration").option("--terminal","Only configure terminal defaults").option("--session","Only configure session settings").option("--reset","Reset configuration to defaults").option("--show","Show current configuration").action(async(options)=>{await setupCommand(options)});program2.command("doctor").description("Run diagnostic checks on genie installation").action(doctorCommand);program2.command("update").description("Update Genie CLI to the latest version").option("--next","Switch to dev builds (npm @next tag)").option("--stable","Switch to stable releases (npm @latest tag)").action(updateCommand);program2.command("uninstall").description("Remove Genie CLI and clean up hooks").action(uninstallCommand);var shortcuts=program2.command("shortcuts").description("Manage tmux keyboard shortcuts");shortcuts.action(shortcutsShowCommand);shortcuts.command("show").description("Show available shortcuts and installation status").action(shortcutsShowCommand);shortcuts.command("install").description("Install shortcuts to config files (~/.tmux.conf, shell rc)").action(shortcutsInstallCommand);shortcuts.command("uninstall").description("Remove shortcuts from config files").action(shortcutsUninstallCommand);registerTeamNamespace(program2);registerDirNamespace(program2);registerAgentNamespace(program2);registerSendInboxCommands(program2);registerStateCommands(program2);registerDispatchCommands(program2);registerHookNamespace(program2);registerDbCommands(program2);registerScheduleCommands(program2);registerDaemonCommands(program2);registerTaskCommands(program2);registerTypeCommands(program2);registerTagCommands(program2);registerReleaseCommands(program2);registerProjectCommands(program2);registerNotifyCommands(program2);program2.command("spawn <name>").description("Spawn a new agent by name (resolves from directory or built-ins)").option("--provider <provider>","Provider: claude or codex","claude").option("--team <team>","Team name",process.env.GENIE_TEAM??"genie").option("--model <model>","Model override (e.g., sonnet, opus)").option("--skill <skill>","Skill to load (optional)").option("--layout <layout>","Layout mode: mosaic (default) or vertical").option("--color <color>","Teammate pane border color").option("--plan-mode","Start teammate in plan mode").option("--permission-mode <mode>","Permission mode (e.g., acceptEdits)").option("--extra-args <args...>","Extra CLI args forwarded to provider").option("--cwd <path>","Working directory for the agent (overrides directory entry)").option("--session <session>","Tmux session name to spawn into").option("--no-auto-resume","Disable auto-resume on pane death").action(async(name,options)=>{try{await handleWorkerSpawn(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("kill <name>").description("Force kill an agent by name").action(async(name)=>{try{await handleWorkerKill(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("stop <name>").description("Stop an agent (preserves session for resume)").action(async(name)=>{try{await handleWorkerStop(name)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("resume [name]").description("Resume a suspended/failed agent with its Claude session").option("--all","Resume all eligible agents").action(async(name,options)=>{try{await handleWorkerResume(name,options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});program2.command("history <name>").description("Show compressed session history for an agent").option("--full","Show full conversation without compression").option("--since <n>","Show last N user/assistant exchanges",Number.parseInt).option("--last <n>","Show last N transcript entries",Number.parseInt).option("--type <role>","Filter by role (user, assistant, tool_call)").option("--after <timestamp>","Only entries after ISO timestamp").option("--json","Output as JSON").option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--raw","Output raw JSONL entries").option("--log-file <path>","Direct path to log file (for testing)").action(async(name,options)=>{await historyCommand(name,options)});program2.command("log [agent]").description("Unified observability feed \u2014 aggregates transcript, DMs, team chat").option("--team <name>","Show interleaved feed for all agents in a team").option("--type <kind>","Filter by event kind (transcript, message, tool_call, state, system)").option("--since <timestamp>","Only events after ISO timestamp").option("--last <n>","Show last N events",Number.parseInt).option("--ndjson","Output as newline-delimited JSON (pipeable to jq)").option("--json","Output as pretty JSON").option("-f, --follow","Follow mode \u2014 real-time streaming").action(async(agent,options)=>{await logCommand(agent,options)});var qaCmd=program2.command("qa").description("QA \u2014 self-testing system for genie CLI");qaCmd.command("run [target]",{isDefault:!0}).description("Run QA specs (all, a domain, or a single spec)").option("--timeout <seconds>","Max seconds per spec",(v)=>Number(v),60).option("--parallel <n>","Max specs to run in parallel",(v)=>Number(v),5).option("--verbose","Show all collected events").option("--ndjson","Machine-readable NDJSON output").action(async(target,options)=>{await qaCommand(target,options)});qaCmd.command("status").description("Show QA dashboard with last results per spec").option("--json","Output as JSON").action(async(options)=>{await qaStatusCommand(options)});qaCmd.command("history").description("Show recent QA runs").action(async()=>{await qaHistoryCommand()});program2.command("qa-report <json>").description("Publish QA result via NATS (called by QA team-lead)").action(async(json2)=>{let team=process.env.GENIE_TEAM;if(!team)console.error("Error: GENIE_TEAM not set. This command must be run by a QA team-lead agent."),process.exit(1);try{let{publish:publish2,close:close2}=await Promise.resolve().then(() => (init_nats_client(),exports_nats_client)),data=JSON.parse(json2);await publish2(`genie.qa.${team}.result`,data),await close2(),console.log(`QA result published to genie.qa.${team}.result`)}catch(err){console.error(`Failed to publish QA result: ${err}`),process.exit(1)}});program2.command("read <name>").description("Read terminal output from an agent pane").option("-n, --lines <number>","Number of lines to read").option("--from <line>","Start line").option("--to <line>","End line").option("--range <range>",'Line range (e.g., "10-20")').option("--search <text>","Search for text").option("--grep <pattern>","Grep for pattern").option("-f, --follow","Follow mode (like tail -f)").option("--all","Show all output").option("-r, --reverse","Reverse order").option("--json","Output as JSON").action(async(name,options)=>{await readSessionLogs2(name,options)});program2.command("answer <name> <choice>").description('Answer a question for an agent (use "text:..." for text input)').action(async(name,choice)=>{await answerQuestion(name,choice)});program2.command("ls").description("List registered agents with runtime status").option("--json","Output as JSON").action(async(options)=>{try{await handleLsCommand(options)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`Error: ${message}`),process.exit(1)}});var args=process.argv.slice(2);if(args.length===0||args.every((a)=>a==="--reset")){let{sessionCommand:sessionCommand2}=await Promise.resolve().then(() => (init_session(),exports_session));await sessionCommand2({reset:args.includes("--reset")}),process.exit(0)}var sessionIdx=args.indexOf("--session");if(sessionIdx!==-1&&sessionIdx+1<args.length){let sessionName=args[sessionIdx+1];if(!args.filter((_,i2)=>i2!==sessionIdx&&i2!==sessionIdx+1).some((a)=>!a.startsWith("-")))try{await startNamedSession(sessionName),process.exit(0)}catch(err){console.error(`Error: ${err instanceof Error?err.message:err}`),process.exit(1)}else program2.parse()}else program2.parse();
@@ -2,7 +2,7 @@
2
2
  "id": "genie",
3
3
  "name": "Genie",
4
4
  "description": "Skills, agents, and hooks for the Genie CLI terminal orchestration toolkit",
5
- "version": "4.260324.12",
5
+ "version": "4.260324.14",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "4.260324.12",
3
+ "version": "4.260324.14",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie",
3
- "version": "4.260324.12",
3
+ "version": "4.260324.14",
4
4
  "description": "Human-AI partnership for Claude Code. Share a terminal, orchestrate workers, evolve together. Brainstorm ideas, turn them into wishes, execute with /work, validate with /review, and ship as one team.",
5
5
  "author": {
6
6
  "name": "Namastex Labs"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genie-plugin",
3
- "version": "4.260324.12",
3
+ "version": "4.260324.14",
4
4
  "private": true,
5
5
  "description": "Runtime dependencies for genie bundled CLIs",
6
6
  "type": "module",
@@ -0,0 +1,626 @@
1
+ ---
2
+ name: genie-hacks
3
+ description: "Browse, search, and contribute community hacks — real-world patterns for provider switching, teams, skills, hooks, cost optimization, and more."
4
+ ---
5
+
6
+ # /genie-hacks — Community Hacks & Patterns
7
+
8
+ Browse real-world Genie patterns contributed by the community. Search by problem, explore by category, or contribute your own.
9
+
10
+ ## When to Use
11
+ - User wants to discover Genie tips, tricks, or advanced patterns
12
+ - User asks "how do I optimize costs?", "how do teams work?", or similar problem-oriented questions
13
+ - User wants to contribute a hack they discovered
14
+ - User invokes `/genie-hacks` with any subcommand
15
+ - If no subcommand is given, default to `list`
16
+
17
+ ## Commands
18
+
19
+ | Command | Description |
20
+ |---------|-------------|
21
+ | `/genie-hacks` | List all hacks (same as `list`) |
22
+ | `/genie-hacks list` | List all hacks with title, problem, and category |
23
+ | `/genie-hacks search <keyword>` | Search hacks by keyword in title/problem/solution |
24
+ | `/genie-hacks show <hack-id>` | Display full hack details |
25
+ | `/genie-hacks contribute` | Submit a new hack via automated PR flow |
26
+ | `/genie-hacks help <problem>` | Describe a problem, get matched to relevant hacks |
27
+
28
+ ---
29
+
30
+ ## Hacks Registry
31
+
32
+ The canonical hacks live in the docs at `genie/hacks.mdx`. Below is the embedded registry for `list`, `search`, `show`, and `help` commands. Use this data directly — do not require an external file.
33
+
34
+ ### hack: provider-switching
35
+ - **ID:** `provider-switching`
36
+ - **Title:** Provider Switching — Right Model for the Job
37
+ - **Category:** providers
38
+ - **Problem:** You're using one provider for everything, but some tasks need speed (Codex) and others need precision (Claude).
39
+ - **Solution:** Use `--provider` flag to switch per-task. Configure provider per agent role in team config.
40
+ - **Code:**
41
+ ```bash
42
+ # Fast scaffolding with Codex
43
+ genie spawn engineer --provider codex
44
+
45
+ # Careful review with Claude
46
+ genie spawn reviewer --provider claude
47
+
48
+ # Team-level: set default per role
49
+ genie team create my-feature --repo . --wish my-slug
50
+ genie team hire engineer --provider codex
51
+ genie team hire reviewer --provider claude
52
+ ```
53
+ - **Benefit:** 2-3x faster scaffolding with Codex, higher-quality reviews with Claude. Match the model to the cognitive demand.
54
+ - **When to use:** Teams with mixed workloads — boilerplate generation vs. nuanced code review. When cost or speed matters per task.
55
+
56
+ ### hack: team-coordination
57
+ - **ID:** `team-coordination`
58
+ - **Title:** Multi-Team Coordination at Scale
59
+ - **Category:** teams
60
+ - **Problem:** You have multiple wishes that depend on each other, and running them sequentially wastes time.
61
+ - **Solution:** Use `/dream` to batch-execute wishes with dependency ordering. Same-layer wishes run in parallel.
62
+ - **Code:**
63
+ ```bash
64
+ # Queue wishes for overnight execution
65
+ /dream
66
+
67
+ # Or manually create parallel teams
68
+ genie team create auth-refactor --repo . --wish auth-refactor
69
+ genie team create api-v2 --repo . --wish api-v2
70
+
71
+ # Monitor both
72
+ genie status auth-refactor
73
+ genie status api-v2
74
+
75
+ # Cross-team messaging
76
+ genie send 'auth-refactor is done, you can proceed' --to api-v2-team-lead
77
+ ```
78
+ - **Benefit:** Parallel execution of independent wishes. Overnight batch runs that produce PRs by morning.
79
+ - **When to use:** Projects with 3+ wishes queued. Sprint planning where multiple features can be parallelized.
80
+
81
+ ### hack: overnight-batch
82
+ - **ID:** `overnight-batch`
83
+ - **Title:** Overnight Batch Execution with /dream
84
+ - **Category:** batch
85
+ - **Problem:** You have a backlog of approved wishes but limited daytime hours to supervise execution.
86
+ - **Solution:** Use `/dream` to queue SHIP-ready wishes, set dependency order, and let agents execute overnight. Wake up to PRs and a DREAM-REPORT.md.
87
+ - **Code:**
88
+ ```bash
89
+ # 1. Ensure wishes are in brainstorm.md under "Poured"
90
+ cat .genie/brainstorm.md
91
+
92
+ # 2. Launch dream run
93
+ /dream
94
+ # Select wishes: 1 3 5 (or "all")
95
+ # Confirm DREAM.md execution plan
96
+ # Go to sleep
97
+
98
+ # 3. Morning: check results
99
+ cat .genie/DREAM-REPORT.md
100
+ gh pr list --author @me
101
+ ```
102
+ - **Benefit:** 8+ hours of unattended execution. Multiple PRs ready for review by morning.
103
+ - **When to use:** End of day with 2+ SHIP-ready wishes. Sprint velocity needs a boost without more human hours.
104
+
105
+ ### hack: custom-skills
106
+ - **ID:** `custom-skills`
107
+ - **Title:** Custom Skills for Repeated Workflows
108
+ - **Category:** skills
109
+ - **Problem:** You keep typing the same sequence of commands or giving the same instructions repeatedly.
110
+ - **Solution:** Create a custom skill in `skills/<name>/SKILL.md` with YAML frontmatter. Claude loads it when invoked via `/<name>`.
111
+ - **Code:**
112
+ ```bash
113
+ mkdir -p skills/deploy-check
114
+ cat > skills/deploy-check/SKILL.md << 'EOF'
115
+ ---
116
+ name: deploy-check
117
+ description: "Pre-deploy checklist — tests, migrations, env vars."
118
+ ---
119
+ # /deploy-check
120
+ 1. Run `bun test`
121
+ 2. Check migrations: `bunx prisma migrate status`
122
+ 3. Verify env vars are set
123
+ 4. Build: `bun run build`
124
+ 5. Report pass/fail table
125
+ EOF
126
+
127
+ # Use it
128
+ /deploy-check
129
+ ```
130
+ - **Benefit:** Encode tribal knowledge as reusable skills. New team members get instant access to workflows.
131
+ - **When to use:** Any workflow you've explained more than twice. CI-like checks you want to run locally before pushing.
132
+
133
+ ### hack: hook-automation
134
+ - **ID:** `hook-automation`
135
+ - **Title:** Git Hook Automation with Genie Hooks
136
+ - **Category:** hooks
137
+ - **Problem:** You want agents to automatically react to git events — spawning a reviewer on PR creation, running tests on commit.
138
+ - **Solution:** Use Genie's hook system and Claude Code hooks in `.claude/settings.json` to trigger actions on events.
139
+ - **Code:**
140
+ ```bash
141
+ # Auto-spawn is built in — set GENIE_AGENT_NAME for dispatch:
142
+ export GENIE_AGENT_NAME=my-agent
143
+
144
+ # Claude Code hook example (.claude/settings.json):
145
+ {
146
+ "hooks": {
147
+ "PreToolUse": [{
148
+ "matcher": "Bash",
149
+ "hooks": [{
150
+ "type": "command",
151
+ "command": "echo 'Tool being used: Bash'"
152
+ }]
153
+ }]
154
+ }
155
+ }
156
+ ```
157
+ - **Benefit:** Automated reactions to development events. Less manual orchestration.
158
+ - **When to use:** Teams wanting CI-like automation within the agent workflow. Projects where wish-to-PR should be fully autonomous.
159
+
160
+ ### hack: cost-optimization
161
+ - **ID:** `cost-optimization`
162
+ - **Title:** Cost Optimization Strategies
163
+ - **Category:** cost
164
+ - **Problem:** Agent usage costs add up, especially with large teams or long-running dream runs.
165
+ - **Solution:** Provider switching (Codex for bulk, Claude for review), tight wish scoping, `/refine` for prompt optimization, and usage monitoring.
166
+ - **Code:**
167
+ ```bash
168
+ # 1. Cheaper providers for boilerplate
169
+ genie team hire engineer --provider codex
170
+ genie team hire reviewer --provider claude
171
+
172
+ # 2. Tight wish scoping
173
+ # BAD: "Refactor the entire codebase"
174
+ # GOOD: "Extract auth middleware into src/middleware/auth.ts"
175
+
176
+ # 3. Refine prompts before dispatching
177
+ /refine
178
+
179
+ # 4. Monitor token usage
180
+ genie history engineer --ndjson | jq '.tokens' | paste -sd+ | bc
181
+ ```
182
+ - **Benefit:** 30-50% cost reduction by matching provider to task complexity. Tighter scoping means fewer fix loops.
183
+ - **When to use:** Budget-conscious teams. High agent concurrency. Before scaling to `/dream` batch runs.
184
+
185
+ ### hack: integration-patterns
186
+ - **ID:** `integration-patterns`
187
+ - **Title:** Integration Patterns — Connect Genie to Your Stack
188
+ - **Category:** integration
189
+ - **Problem:** You want Genie to integrate with existing tools — Slack notifications, CI/CD pipelines, monitoring.
190
+ - **Solution:** Use shell commands in skills for custom integrations, GitHub CLI for PR/issue management, webhooks for notifications.
191
+ - **Code:**
192
+ ```bash
193
+ # Post to Slack via webhook
194
+ curl -X POST "$SLACK_WEBHOOK_URL" \
195
+ -H 'Content-Type: application/json' \
196
+ -d '{"text": "Genie: wish auth-refactor done. PR #123"}'
197
+
198
+ # Create GitHub issues from findings
199
+ gh issue create --title "Bug: auth token expiry" \
200
+ --body "Found during /trace: refresh fails silently"
201
+
202
+ # Trigger CI after PR
203
+ gh workflow run ci.yml --ref feat/my-feature
204
+ ```
205
+ - **Benefit:** Genie becomes part of your existing workflow. Notifications go where your team already looks.
206
+ - **When to use:** Teams with Slack/Discord channels. Projects with CI/CD pipelines that should trigger on agent PRs.
207
+
208
+ ### hack: debugging-tips
209
+ - **ID:** `debugging-tips`
210
+ - **Title:** Debugging Agent Issues Like a Pro
211
+ - **Category:** debugging
212
+ - **Problem:** An agent is stuck, producing wrong output, or a team isn't making progress.
213
+ - **Solution:** Use `genie read` for live output, `genie history` for transcripts, `/trace` for root cause analysis, and `genie reset` for recovery.
214
+ - **Code:**
215
+ ```bash
216
+ # Live agent output
217
+ genie read engineer
218
+
219
+ # Compressed timeline
220
+ genie history engineer
221
+
222
+ # Filter to tool calls
223
+ genie history engineer --ndjson | jq 'select(.type == "tool_use") | .name'
224
+
225
+ # Systematic investigation
226
+ /trace
227
+
228
+ # Check team status
229
+ genie team ls my-team
230
+ genie status my-wish-slug
231
+
232
+ # Unstick a blocked group
233
+ genie reset my-wish-slug#2
234
+ ```
235
+ - **Benefit:** Full visibility into agent behavior. Systematic debugging instead of guessing.
236
+ - **When to use:** Agent taking too long. Output quality dropping. Team progress stalled. Post-mortem on failed dream run.
237
+
238
+ ---
239
+
240
+ ## Command: `list`
241
+
242
+ Display all hacks from the registry in a formatted table.
243
+
244
+ **Output format:**
245
+ ```
246
+ Genie Hacks — Community Patterns & Tips
247
+
248
+ | ID | Title | Category |
249
+ |----------------------|------------------------------------------|-------------|
250
+ | provider-switching | Provider Switching — Right Model for Job | providers |
251
+ | team-coordination | Multi-Team Coordination at Scale | teams |
252
+ | overnight-batch | Overnight Batch Execution with /dream | batch |
253
+ | custom-skills | Custom Skills for Repeated Workflows | skills |
254
+ | hook-automation | Git Hook Automation with Genie Hooks | hooks |
255
+ | cost-optimization | Cost Optimization Strategies | cost |
256
+ | integration-patterns | Integration Patterns — Connect to Stack | integration |
257
+ | debugging-tips | Debugging Agent Issues Like a Pro | debugging |
258
+
259
+ 8 hacks available. Run `/genie-hacks show <id>` for details.
260
+ Contribute your own: `/genie-hacks contribute`
261
+ ```
262
+
263
+ ## Command: `search <keyword>`
264
+
265
+ Search hack titles, problems, solutions, and code for the keyword (case-insensitive). Display matching hacks.
266
+
267
+ **Output format:**
268
+ ```
269
+ Search results for "<keyword>":
270
+
271
+ 1. [provider-switching] Provider Switching — Right Model for the Job
272
+ Problem: You're using one provider for everything...
273
+ Category: providers
274
+
275
+ 2. [cost-optimization] Cost Optimization Strategies
276
+ Problem: Agent usage costs add up...
277
+ Category: cost
278
+
279
+ 2 hacks matched. Run `/genie-hacks show <id>` for full details.
280
+ ```
281
+
282
+ If no matches: `No hacks found for "<keyword>". Try broader terms or run /genie-hacks list to browse all.`
283
+
284
+ ## Command: `show <hack-id>`
285
+
286
+ Display the full hack with all fields. Look up by exact ID (case-insensitive).
287
+
288
+ **Output format:**
289
+ ```
290
+ ## <Title>
291
+
292
+ **Category:** <category> | **ID:** `<hack-id>`
293
+
294
+ ### Problem
295
+ <problem text>
296
+
297
+ ### Solution
298
+ <solution text>
299
+
300
+ ### Code
301
+ <code block>
302
+
303
+ ### Benefit
304
+ <benefit text>
305
+
306
+ ### When to Use
307
+ <when-to-use text>
308
+
309
+ ---
310
+ Found this useful? Share your own: `/genie-hacks contribute`
311
+ Full docs: https://docs.automagik.dev/genie/hacks
312
+ ```
313
+
314
+ If not found: `Hack "<id>" not found. Did you mean: <suggest closest IDs>? Run /genie-hacks list to see all.`
315
+
316
+ ## Command: `help <problem>`
317
+
318
+ Fuzzy-match a problem description to relevant hacks. Extract keywords, score by overlap across all fields, show top 3.
319
+
320
+ **Keyword mapping hints:**
321
+ - "speed", "fast", "slow" → provider-switching, cost-optimization
322
+ - "cost", "expensive", "budget", "tokens" → cost-optimization, provider-switching
323
+ - "team", "parallel", "coordinate", "multiple" → team-coordination, overnight-batch
324
+ - "overnight", "batch", "sleep", "queue" → overnight-batch, team-coordination
325
+ - "repeat", "workflow", "automate", "reuse" → custom-skills, hook-automation
326
+ - "hook", "event", "trigger", "auto" → hook-automation, custom-skills
327
+ - "slack", "ci", "integrate", "notify" → integration-patterns
328
+ - "stuck", "debug", "broken", "error", "fail" → debugging-tips
329
+ - "codex", "claude", "model", "provider" → provider-switching
330
+ - "dream", "night", "unattended" → overnight-batch
331
+
332
+ **Output format:**
333
+ ```
334
+ Based on your problem: "<problem>"
335
+
336
+ Best matches:
337
+
338
+ 1. [provider-switching] Provider Switching — Right Model for the Job
339
+ Why: Matches your need for speed/cost optimization across providers
340
+ Quick tip: Use `genie spawn engineer --provider codex` for fast scaffolding
341
+
342
+ 2. [cost-optimization] Cost Optimization Strategies
343
+ Why: Directly addresses cost reduction techniques
344
+ Quick tip: Scope wishes tightly and use `/refine` before dispatching
345
+
346
+ 3. [team-coordination] Multi-Team Coordination at Scale
347
+ Why: Parallel execution reduces wall-clock time
348
+ Quick tip: Use `/dream` for batch overnight execution
349
+
350
+ Run `/genie-hacks show <id>` for full details on any hack.
351
+ ```
352
+
353
+ If no reasonable matches: `No hacks closely match your problem. Try describing it differently, or run /genie-hacks list to browse all. Got a solution? Run /genie-hacks contribute!`
354
+
355
+ ## Command: `contribute`
356
+
357
+ ### Overview
358
+
359
+ Guide the user through contributing a new hack to the community docs via an automated PR to `automagik-dev/docs`. This is the full end-to-end flow.
360
+
361
+ ### Step 1: Gather Hack Details
362
+
363
+ Prompt the user for each field interactively. Wait for each response before proceeding.
364
+
365
+ **Prompt sequence:**
366
+
367
+ 1. **Title**: "What's your hack title? (short, descriptive)"
368
+ - Example: "Provider Switching for Speed vs Safety"
369
+
370
+ 2. **Problem**: "What problem does it solve? (1-2 sentences)"
371
+ - Example: "Different tasks need different AI providers — speed for scaffolding, careful reasoning for security-sensitive code."
372
+
373
+ 3. **Solution**: "How does it work? Show the commands, config, or code. (Use code blocks)"
374
+ - Example: multi-line code block with genie commands
375
+
376
+ 4. **Category**: "What category? Pick one: `providers` | `teams` | `skills` | `hooks` | `cost` | `integration` | `debugging` | `batch` | `other`"
377
+ - Validate input is one of the allowed categories. If invalid, re-prompt.
378
+
379
+ 5. **Benefit**: "What's the key benefit? (one line)"
380
+ - Example: "Right tool for each job — 3x faster scaffolding, safer reviews."
381
+
382
+ 6. **When to use**: "When should someone use this? (one line)"
383
+ - Example: "When your project has both speed-critical and safety-critical tasks."
384
+
385
+ ### Step 2: Preview & Confirm
386
+
387
+ Format the hack in the standard template and show the user:
388
+
389
+ ```markdown
390
+ ### <Title>
391
+
392
+ **ID:** `<generated-id>`
393
+ **Category:** <category>
394
+
395
+ **Problem:** <problem>
396
+
397
+ **Solution:**
398
+
399
+ <solution with code blocks>
400
+
401
+ **Benefit:** <benefit>
402
+
403
+ **When to use:** <when>
404
+ ```
405
+
406
+ Ask: "Does this look good? (yes/edit/cancel)"
407
+ - **yes** → proceed to Step 3
408
+ - **edit** → ask which field to change, re-prompt for that field, re-preview
409
+ - **cancel** → abort with "No worries! Run `/genie-hacks contribute` anytime."
410
+
411
+ ### Step 3: GitHub CLI Preflight
412
+
413
+ Run these checks in order. Stop at first failure with a helpful error.
414
+
415
+ ```bash
416
+ # 1. Check gh is installed
417
+ command -v gh >/dev/null 2>&1
418
+ # If missing: "GitHub CLI (gh) is required. Install: https://cli.github.com/"
419
+
420
+ # 2. Check gh is authenticated
421
+ gh auth status 2>&1
422
+ # If not authenticated: "Run `gh auth login` to authenticate with GitHub first."
423
+
424
+ # 3. Check git is available
425
+ command -v git >/dev/null 2>&1
426
+ # If missing: "git is required but not found in PATH."
427
+ ```
428
+
429
+ If any check fails, display the error message and suggest manual steps:
430
+ ```
431
+ Manual contribution steps:
432
+ 1. Fork https://github.com/automagik-dev/docs
433
+ 2. Edit genie/hacks.mdx — add your hack in the correct category section
434
+ 3. Commit: git commit -m "hack: <title>"
435
+ 4. Open PR to automagik-dev/docs (base: dev)
436
+ ```
437
+
438
+ ### Step 4: Fork & Clone
439
+
440
+ ```bash
441
+ # Cache directory for docs repo
442
+ DOCS_CACHE="$HOME/.genie/cache/docs-fork"
443
+
444
+ # Fork the docs repo (idempotent — no-op if already forked)
445
+ gh repo fork automagik-dev/docs --clone=false 2>/dev/null || true
446
+
447
+ # Get the user's GitHub username for the fork URL
448
+ GH_USER=$(gh api user --jq '.login')
449
+
450
+ # Clone or update the cached fork
451
+ if [ -d "$DOCS_CACHE/.git" ]; then
452
+ cd "$DOCS_CACHE"
453
+ git fetch origin
454
+ git checkout dev
455
+ git pull origin dev
456
+ else
457
+ gh repo clone "$GH_USER/docs" "$DOCS_CACHE" -- --branch dev
458
+ cd "$DOCS_CACHE"
459
+ git remote add upstream https://github.com/automagik-dev/docs.git 2>/dev/null || true
460
+ git fetch upstream
461
+ fi
462
+
463
+ # Create a branch for the hack
464
+ BRANCH="hack/$(echo '<title>' | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | tr -cd 'a-z0-9-')"
465
+ git checkout -b "$BRANCH" origin/dev
466
+ ```
467
+
468
+ If clone/fork fails, show the error and fall back to manual steps (same as Step 3 fallback).
469
+
470
+ ### Step 5: Append Hack to hacks.mdx
471
+
472
+ Read `genie/hacks.mdx` from the cloned repo. Find the correct category section (look for `## <Category>` heading — e.g., `## Providers`, `## Teams`, etc.). Append the new hack entry just before the next `## ` heading or at the end of the category section.
473
+
474
+ If the category section doesn't exist (e.g., for `other`), append a new section at the end of the file, before the Contributing section:
475
+
476
+ ```markdown
477
+ ## Other
478
+
479
+ ### <Title>
480
+
481
+ **ID:** `<generated-id>`
482
+
483
+ **Problem:** <problem>
484
+
485
+ **Solution:**
486
+
487
+ <solution>
488
+
489
+ **Benefit:** <benefit>
490
+
491
+ **When to use:** <when>
492
+
493
+ ---
494
+ ```
495
+
496
+ ### Step 6: Commit & Push
497
+
498
+ ```bash
499
+ cd "$DOCS_CACHE"
500
+ git add genie/hacks.mdx
501
+ git commit -m "hack: <title>"
502
+ git push -u origin "$BRANCH"
503
+ ```
504
+
505
+ ### Step 7: Create PR
506
+
507
+ ```bash
508
+ # Create PR targeting the dev branch of the upstream docs repo
509
+ PR_URL=$(gh pr create \
510
+ --repo automagik-dev/docs \
511
+ --base dev \
512
+ --head "$GH_USER:$BRANCH" \
513
+ --title "hack: <title>" \
514
+ --body "$(cat <<'PREOF'
515
+ ## New Community Hack
516
+
517
+ **Title:** <title>
518
+ **Category:** <category>
519
+ **Problem:** <problem>
520
+
521
+ **Solution:**
522
+ <solution summary>
523
+
524
+ **Benefit:** <benefit>
525
+ **When to use:** <when>
526
+
527
+ ---
528
+
529
+ *Submitted via `/genie-hacks contribute`*
530
+ PREOF
531
+ )")
532
+
533
+ echo "PR created: $PR_URL"
534
+ ```
535
+
536
+ ### Step 8: Success
537
+
538
+ Display the result to the user:
539
+
540
+ ```
541
+ Your hack has been submitted!
542
+
543
+ PR: <PR_URL>
544
+ Title: hack: <title>
545
+ Target: automagik-dev/docs (dev branch)
546
+
547
+ What happens next:
548
+ - A maintainer will review your hack
549
+ - They may suggest edits via PR comments
550
+ - Once approved, it'll appear on the hacks page
551
+
552
+ Thank you for contributing to the Genie community!
553
+ Join the discussion on Discord: https://discord.gg/automagik
554
+ ```
555
+
556
+ ### Error Recovery
557
+
558
+ At any point if a step fails:
559
+
560
+ | Error | Recovery |
561
+ |-------|----------|
562
+ | `gh` not installed | Show install URL, fall back to manual steps |
563
+ | `gh` not authenticated | Show `gh auth login` command |
564
+ | Fork fails | Check if fork already exists with `gh repo view $GH_USER/docs` |
565
+ | Clone fails | Remove cache dir and retry: `rm -rf $DOCS_CACHE && gh repo clone ...` |
566
+ | `hacks.mdx` not found | Create a new one with the standard template header |
567
+ | Push fails (auth) | Suggest `gh auth refresh` or SSH key setup |
568
+ | PR creation fails | Show the branch/commit info so user can create PR manually via GitHub web UI |
569
+ | Editor fails | Write the hack content to a temp file, show the path for manual copy |
570
+
571
+ ### Offline / Manual Fallback
572
+
573
+ If GitHub operations fail entirely, save the hack locally and guide the user:
574
+
575
+ ```bash
576
+ # Save hack to local file
577
+ mkdir -p ~/.genie/cache/pending-hacks
578
+ cat > ~/.genie/cache/pending-hacks/<hack-id>.md << 'EOF'
579
+ <formatted hack content>
580
+ EOF
581
+ ```
582
+
583
+ Then display:
584
+ ```
585
+ Saved your hack locally at: ~/.genie/cache/pending-hacks/<hack-id>.md
586
+
587
+ To submit manually:
588
+ 1. Fork https://github.com/automagik-dev/docs
589
+ 2. Copy the hack content into genie/hacks.mdx under the "<category>" section
590
+ 3. Commit with message: "hack: <title>"
591
+ 4. Open PR targeting the `dev` branch
592
+ ```
593
+
594
+ ## Categories Reference
595
+
596
+ | Category | ID | Description |
597
+ |----------|----|-------------|
598
+ | Providers | `providers` | Provider switching, model selection, BYOA |
599
+ | Teams | `teams` | Multi-agent coordination, team patterns |
600
+ | Skills | `skills` | Custom skills, skill chains, automation |
601
+ | Hooks | `hooks` | Git hooks, auto-spawn, event-driven flows |
602
+ | Cost | `cost` | Token optimization, model routing, budget control |
603
+ | Integration | `integration` | External tools, APIs, CI/CD, Slack, etc. |
604
+ | Debugging | `debugging` | Agent debugging, tracing, fixing bad behavior |
605
+ | Batch | `batch` | Overnight execution, queued processing |
606
+ | Other | `other` | Uncategorized community patterns |
607
+
608
+ ## Docs Cache
609
+
610
+ The docs repo is cached at `~/.genie/cache/docs-fork/` to avoid re-cloning on every contribute. The cache is updated (`git pull`) on each contribute invocation. If the cache becomes corrupted, delete it and re-run — the contribute flow will re-clone automatically.
611
+
612
+ ## Rules
613
+
614
+ - All hack IDs are lowercase kebab-case.
615
+ - Never invent hacks that don't exist in the registry — only show what's listed above.
616
+ - For `help`, always try to find at least one relevant hack. Only say "no matches" if truly nothing fits.
617
+ - Keep output concise — tables for `list`, full format only for `show`.
618
+ - Always end `list` and `show` with a nudge toward `contribute`.
619
+ - The `contribute` command should be friendly and low-friction — one question at a time.
620
+ - Link to Discord (https://discord.gg/automagik) for community discussion.
621
+ - Link to docs (https://docs.automagik.dev/genie/hacks) for the full hacks page.
622
+ - Always target the `dev` branch for PRs — never `main`/`master`.
623
+ - Hack IDs must be unique — check existing IDs before appending.
624
+ - All hacks must be realistic and tested — do not submit aspirational or untested patterns.
625
+ - Preserve the standard format: problem, solution, code, benefit, when to use.
626
+ - Keep code examples concise — show the minimum needed to understand the hack.
@@ -408,20 +408,20 @@ export async function brainstormCommand(agentName: string, slug: string): Promis
408
408
  console.log(`📝 Dispatching brainstorm to ${agentName} for "${slug}"`);
409
409
  console.log(` Draft: ${draftPath}`);
410
410
 
411
+ const brainstormPrompt = `Brainstorm "${slug}". Your context is in the system prompt. Explore the idea, ask clarifying questions, and build toward a design.`;
411
412
  await handleWorkerSpawn(agentName, {
412
413
  provider: 'claude',
413
414
  team: process.env.GENIE_TEAM ?? 'genie',
414
415
  extraArgs: ['--append-system-prompt-file', contextFile],
416
+ initialPrompt: brainstormPrompt,
415
417
  });
416
418
 
417
- // Deliver work prompt via mailbox (durable, queued to disk)
419
+ // Deliver work prompt via mailbox as backup (durable, queued to disk)
418
420
  const repoPath = process.cwd();
419
- await protocolRouter.sendMessage(
420
- repoPath,
421
- 'cli',
422
- agentName,
423
- `Brainstorm "${slug}". Your context is in the system prompt. Explore the idea, ask clarifying questions, and build toward a design.`,
424
- );
421
+ const result = await protocolRouter.sendMessage(repoPath, 'cli', agentName, brainstormPrompt);
422
+ if (!result.delivered) {
423
+ console.warn(`⚠ Backup delivery to ${agentName} failed: ${result.reason ?? 'unknown'}`);
424
+ }
425
425
  }
426
426
 
427
427
  /**
@@ -448,20 +448,20 @@ export async function wishCommand(agentName: string, slug: string): Promise<void
448
448
  console.log(`📝 Dispatching wish to ${agentName} for "${slug}"`);
449
449
  console.log(` Design: ${designPath}`);
450
450
 
451
+ const wishPrompt = `Create a wish from the design for "${slug}". Your context is in the system prompt. Write the WISH.md with execution groups, acceptance criteria, and validation commands.`;
451
452
  await handleWorkerSpawn(agentName, {
452
453
  provider: 'claude',
453
454
  team: process.env.GENIE_TEAM ?? 'genie',
454
455
  extraArgs: ['--append-system-prompt-file', contextFile],
456
+ initialPrompt: wishPrompt,
455
457
  });
456
458
 
457
- // Deliver work prompt via mailbox (durable, queued to disk)
459
+ // Deliver work prompt via mailbox as backup (durable, queued to disk)
458
460
  const repoPath = process.cwd();
459
- await protocolRouter.sendMessage(
460
- repoPath,
461
- 'cli',
462
- agentName,
463
- `Create a wish from the design for "${slug}". Your context is in the system prompt. Write the WISH.md with execution groups, acceptance criteria, and validation commands.`,
464
- );
461
+ const result = await protocolRouter.sendMessage(repoPath, 'cli', agentName, wishPrompt);
462
+ if (!result.delivered) {
463
+ console.warn(`⚠ Backup delivery to ${agentName} failed: ${result.reason ?? 'unknown'}`);
464
+ }
465
465
  }
466
466
 
467
467
  /**
@@ -529,21 +529,21 @@ export async function workDispatchCommand(agentName: string, ref: string): Promi
529
529
  console.log(` Group: ${group}`);
530
530
 
531
531
  const effectiveRole = `${agentName}-${group}`;
532
+ const workPrompt = `Execute Group ${group} of wish "${slug}". Your full context is in the system prompt. Read the wish at ${wishPath} if needed. Implement all deliverables, run validation, and report completion.\n\nWhen done:\n1. Run: genie done ${slug}#${group}\n2. Run: genie send 'Group ${group} complete. <summary>' --to team-lead`;
532
533
  await handleWorkerSpawn(agentName, {
533
534
  provider: 'claude',
534
535
  team: process.env.GENIE_TEAM ?? 'genie',
535
536
  role: effectiveRole,
536
537
  extraArgs: ['--append-system-prompt-file', contextFile],
538
+ initialPrompt: workPrompt,
537
539
  });
538
540
 
539
- // Deliver work prompt via mailbox (durable, queued to disk)
541
+ // Deliver work prompt via mailbox as backup (durable, queued to disk)
540
542
  const repoPath = process.cwd();
541
- await protocolRouter.sendMessage(
542
- repoPath,
543
- 'cli',
544
- effectiveRole,
545
- `Execute Group ${group} of wish "${slug}". Your full context is in the system prompt. Read the wish at ${wishPath} if needed. Implement all deliverables, run validation, and report completion.\n\nWhen done:\n1. Run: genie done ${slug}#${group}\n2. Run: genie send 'Group ${group} complete. <summary>' --to team-lead`,
546
- );
543
+ const result = await protocolRouter.sendMessage(repoPath, 'cli', effectiveRole, workPrompt);
544
+ if (!result.delivered) {
545
+ console.warn(`⚠ Backup delivery to ${effectiveRole} failed: ${result.reason ?? 'unknown'}`);
546
+ }
547
547
  }
548
548
 
549
549
  /**
@@ -593,20 +593,20 @@ export async function reviewCommand(agentName: string, ref: string): Promise<voi
593
593
  console.log(` Group: ${group}`);
594
594
  if (diff) console.log(` Diff: ${diff.split('\n').length} lines`);
595
595
 
596
+ const reviewPrompt = `Review "${ref}". Your context and diff are in the system prompt. Evaluate against acceptance criteria and return SHIP, FIX-FIRST, or BLOCKED with severity-tagged findings.\n\nWhen done, report your verdict:\nRun: genie send '<SHIP|FIX-FIRST|BLOCKED> — <summary>' --to team-lead`;
596
597
  await handleWorkerSpawn(agentName, {
597
598
  provider: 'claude',
598
599
  team: process.env.GENIE_TEAM ?? 'genie',
599
600
  extraArgs: ['--append-system-prompt-file', contextFile],
601
+ initialPrompt: reviewPrompt,
600
602
  });
601
603
 
602
- // Deliver work prompt via mailbox (durable, queued to disk)
604
+ // Deliver work prompt via mailbox as backup (durable, queued to disk)
603
605
  const repoPath = process.cwd();
604
- await protocolRouter.sendMessage(
605
- repoPath,
606
- 'cli',
607
- agentName,
608
- `Review "${ref}". Your context and diff are in the system prompt. Evaluate against acceptance criteria and return SHIP, FIX-FIRST, or BLOCKED with severity-tagged findings.\n\nWhen done, report your verdict:\nRun: genie send '<SHIP|FIX-FIRST|BLOCKED> — <summary>' --to team-lead`,
609
- );
606
+ const result = await protocolRouter.sendMessage(repoPath, 'cli', agentName, reviewPrompt);
607
+ if (!result.delivered) {
608
+ console.warn(`⚠ Backup delivery to ${agentName} failed: ${result.reason ?? 'unknown'}`);
609
+ }
610
610
  }
611
611
 
612
612
  // ============================================================================
@@ -255,19 +255,23 @@ async function spawnLeaderWithWish(
255
255
  }
256
256
  console.log(` Team: hired ${standardTeam.join(', ')}`);
257
257
 
258
- // Spawn leader — AGENTS.md comes from the built-in resolver, prompt delivered via mailbox
258
+ // Spawn leader — AGENTS.md comes from the built-in resolver, prompt delivered as initialPrompt
259
+ const members = standardTeam.filter((r) => r !== 'team-lead').join(', ');
260
+ const kickoffPrompt = `Your team is "${config.name}". Repo: ${config.repo}. Branch: ${config.name}. Worktree: ${config.worktreePath}. Wish slug: ${slug}. Your team members are: ${members} (already hired — genie work will spawn them automatically). Read the wish at .genie/wishes/${slug}/WISH.md and execute the full lifecycle autonomously.`;
259
261
  await handleWorkerSpawn('team-lead', {
260
262
  provider: 'claude',
261
263
  team: config.name,
262
264
  cwd: config.worktreePath,
263
265
  session: tmuxSession,
266
+ initialPrompt: kickoffPrompt,
264
267
  });
265
268
 
266
- // Deliver kickoff prompt via mailbox (durable, queued to disk)
267
- const members = standardTeam.filter((r) => r !== 'team-lead').join(', ');
268
- const kickoffPrompt = `Your team is "${config.name}". Repo: ${config.repo}. Branch: ${config.name}. Worktree: ${config.worktreePath}. Wish slug: ${slug}. Your team members are: ${members} (already hired — genie work will spawn them automatically). Read the wish at .genie/wishes/${slug}/WISH.md and execute the full lifecycle autonomously.`;
269
+ // Deliver kickoff prompt via mailbox as backup (durable, queued to disk)
269
270
  const protocolRouter = await import('../lib/protocol-router.js');
270
- await protocolRouter.sendMessage(config.worktreePath, 'cli', 'team-lead', kickoffPrompt);
271
+ const result = await protocolRouter.sendMessage(config.worktreePath, 'cli', 'team-lead', kickoffPrompt);
272
+ if (!result.delivered) {
273
+ console.warn(`⚠ Backup delivery to team-lead failed: ${result.reason ?? 'unknown'}`);
274
+ }
271
275
  console.log(' Leader: spawned and working');
272
276
  }
273
277