@automagik/genie 4.260416.1 → 4.260416.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/genie.js
CHANGED
|
@@ -299,7 +299,7 @@ Next steps:`),console.log(" 1. Reload tmux: tmux source ~/.tmux.conf"),console.
|
|
|
299
299
|
DELETE FROM audit_events WHERE entity_type LIKE 'otel_%' AND created_at < now() - interval '30 days';
|
|
300
300
|
DELETE FROM genie_runtime_events WHERE created_at < now() - interval '14 days';
|
|
301
301
|
`),retentionRan=!0}catch(retErr){retentionRan=!0;let msg=retErr instanceof Error?retErr.message:String(retErr);process.stderr.write(`[genie] retention cleanup warning: ${msg}
|
|
302
|
-
`)}}async function ensurePgserve(){if(activePort===null){let testPort=await resolveTestPort();if(testPort!==null)return activePort=testPort,process.env.GENIE_PG_AVAILABLE="true",testPort}if(ensurePromise)return ensurePromise;ensurePromise=_ensurePgserve();try{return await ensurePromise}finally{ensurePromise=null}}async function autoStartDaemon(){for(let pidName of["serve.pid","scheduler.pid"]){let pidPath=join14(GENIE_HOME2,pidName);try{let pidStr=readFileSync9(pidPath,"utf-8").trim(),pid=Number.parseInt(pidStr,10);if(!Number.isNaN(pid)&&pid>0)try{process.kill(pid,0);return}catch{}}catch{}}let bunPath=process.execPath??"bun",genieBin=process.argv[1]??"genie";spawn3(bunPath,[genieBin,"serve","start","--headless"],{detached:!0,stdio:"ignore",env:{...process.env}}).unref()}async function resolveTestPort(){let raw=process.env.GENIE_TEST_PG_PORT;if(!raw)return null;let parsed=Number.parseInt(raw,10);if(Number.isNaN(parsed)||parsed<=0||parsed>=65536||!await isPostgresHealthy(parsed))throw Error(`GENIE_TEST_PG_PORT=${raw} set but not reachable`);return parsed}async function _ensurePgserve(){if(activePort!==null)return activePort;let port=getPort(),portFromFile=readLockfile();if(portFromFile!==null&&await isPostgresHealthy(portFromFile))return activePort=portFromFile,process.env.GENIE_PG_AVAILABLE="true",portFromFile;if(await isPostgresHealthy(port))return activePort=port,process.env.GENIE_PG_AVAILABLE="true",writeLockfile(port),port;if(process.env.CI==="true")throw process.env.GENIE_PG_AVAILABLE="false",Error("pgserve not available in CI");if(process.env.GENIE_IS_DAEMON==="1"){mkdirSync7(DATA_DIR,{recursive:!0}),selfHealPostgres(DATA_DIR);try{let startedPort=await startPgserveOnPort(port);return registerExitHandler(),startedPort}catch(err){process.env.GENIE_PG_AVAILABLE="false";let message=err instanceof Error?err.message:String(err);throw Error(`pgserve failed to start: ${maskCredentials(message)}`)}}await autoStartDaemon();let deadline=Date.now()+16000;while(Date.now()<deadline){let p=readLockfile();if(p!==null&&await isPostgresHealthy(p))return activePort=p,process.env.GENIE_PG_AVAILABLE="true",p;await new Promise((r)=>setTimeout(r,500))}throw process.env.GENIE_PG_AVAILABLE="false",Error("Timed out waiting for genie serve to start pgserve (16s). Run: genie serve")}function findPgserveBin(){try{let resolved=__require.resolve("pgserve/bin/pgserve-wrapper.cjs");if(existsSync13(resolved))return resolved}catch{}let globalBin=join14(homedir13(),".bun","bin","pgserve");if(existsSync13(globalBin))return globalBin;try{return execSync4("which pgserve",{encoding:"utf-8",timeout:3000}).trim()}catch{return"pgserve"}}async function startPgserveOnPort(port){mkdirSync7(DATA_DIR,{recursive:!0});let child=spawn3(findPgserveBin(),["--port",String(port),"--host",DEFAULT_HOST,"--data",DATA_DIR,"--log","warn","--no-stats","--no-cluster","--pgvector"],{detached:!0,stdio:"ignore"});child.unref(),pgserveChild=child;let timeout=Number(process.env.GENIE_PGSERVE_TIMEOUT)||30000,deadline=Date.now()+timeout;while(Date.now()<deadline){if(await isPostgresHealthy(port))return activePort=port,ownsLockfile=!0,process.env.GENIE_PG_AVAILABLE="true",writeLockfile(port),port;await new Promise((r)=>setTimeout(r,500))}try{child.kill("SIGTERM")}catch{}throw Error(`pgserve failed to start on port ${port} (timeout after ${timeout/1000}s)`)}function registerExitHandler(){if(exitHandlerRegistered)return;exitHandlerRegistered=!0;let cleanup=()=>{if(pgserveChild){try{pgserveChild.kill("SIGTERM")}catch{}pgserveChild=null}if(ownsLockfile)removeLockfile(),ownsLockfile=!1};process.on("exit",cleanup),process.on("SIGINT",()=>{cleanup(),process.exit(130)}),process.on("SIGTERM",()=>{cleanup(),process.exit(143)})}async function healthCheckCachedClient(){if(!sqlClient)return null;try{return await sqlClient`SELECT 1`,sqlClient}catch{try{await sqlClient.end({timeout:2})}catch{}return sqlClient=null,activePort=null,null}}async function runPostConnectSetup(client,testSchema,timings){let _t2=Date.now();if(!testSchema)await runMigrations(client);let _t3=Date.now();if(!testSchema&&needsSeed())await runSeed(client);let _t4=Date.now();if(!testSchema&&!retentionRan)await runRetention(client);let _t5=Date.now();if(process.env.GENIE_PROFILE_DB)console.error(`[db-profile] pgserve=${timings.t1-timings.t0}ms migrate=${_t3-_t2}ms seed=${_t4-_t3}ms retention=${_t5-_t4}ms total=${_t5-timings.t0}ms`)}async function getConnection(){let cached=await healthCheckCachedClient();if(cached)return cached;let _t0=Date.now(),port=await ensurePgserve(),_t1=Date.now(),pgModule=(await Promise.resolve().then(() => (init_src(),exports_src))).default,testSchema=process.env.GENIE_TEST_SCHEMA;sqlClient=pgModule({host:DEFAULT_HOST,port,database:DB_NAME,username:"postgres",password:"postgres",max:50,idle_timeout:1,connect_timeout:5,onnotice:()=>{},connection:{client_min_messages:"warning",...testSchema?{search_path:`${testSchema}, public`}:{}}});try{await runPostConnectSetup(sqlClient,testSchema,{t0:_t0,t1:_t1})}catch(err){try{await sqlClient.end({timeout:2})}catch{}throw sqlClient=null,activePort=null,err}return sqlClient}function isConnected(){return sqlClient!==null}async function resetConnection(){if(sqlClient)await
|
|
302
|
+
`)}}async function ensurePgserve(){if(activePort===null){let testPort=await resolveTestPort();if(testPort!==null)return activePort=testPort,process.env.GENIE_PG_AVAILABLE="true",testPort}if(ensurePromise)return ensurePromise;ensurePromise=_ensurePgserve();try{return await ensurePromise}finally{ensurePromise=null}}async function autoStartDaemon(){for(let pidName of["serve.pid","scheduler.pid"]){let pidPath=join14(GENIE_HOME2,pidName);try{let pidStr=readFileSync9(pidPath,"utf-8").trim(),pid=Number.parseInt(pidStr,10);if(!Number.isNaN(pid)&&pid>0)try{process.kill(pid,0);return}catch{}}catch{}}let bunPath=process.execPath??"bun",genieBin=process.argv[1]??"genie";spawn3(bunPath,[genieBin,"serve","start","--headless"],{detached:!0,stdio:"ignore",env:{...process.env}}).unref()}async function resolveTestPort(){let raw=process.env.GENIE_TEST_PG_PORT;if(!raw)return null;let parsed=Number.parseInt(raw,10);if(Number.isNaN(parsed)||parsed<=0||parsed>=65536||!await isPostgresHealthy(parsed))throw Error(`GENIE_TEST_PG_PORT=${raw} set but not reachable`);return parsed}async function _ensurePgserve(){if(activePort!==null)return activePort;let port=getPort(),portFromFile=readLockfile();if(portFromFile!==null&&await isPostgresHealthy(portFromFile))return activePort=portFromFile,process.env.GENIE_PG_AVAILABLE="true",portFromFile;if(await isPostgresHealthy(port))return activePort=port,process.env.GENIE_PG_AVAILABLE="true",writeLockfile(port),port;if(process.env.CI==="true")throw process.env.GENIE_PG_AVAILABLE="false",Error("pgserve not available in CI");if(process.env.GENIE_IS_DAEMON==="1"){mkdirSync7(DATA_DIR,{recursive:!0}),selfHealPostgres(DATA_DIR);try{let startedPort=await startPgserveOnPort(port);return registerExitHandler(),startedPort}catch(err){process.env.GENIE_PG_AVAILABLE="false";let message=err instanceof Error?err.message:String(err);throw Error(`pgserve failed to start: ${maskCredentials(message)}`)}}await autoStartDaemon();let deadline=Date.now()+16000;while(Date.now()<deadline){let p=readLockfile();if(p!==null&&await isPostgresHealthy(p))return activePort=p,process.env.GENIE_PG_AVAILABLE="true",p;await new Promise((r)=>setTimeout(r,500))}throw process.env.GENIE_PG_AVAILABLE="false",Error("Timed out waiting for genie serve to start pgserve (16s). Run: genie serve")}function findPgserveBin(){try{let resolved=__require.resolve("pgserve/bin/pgserve-wrapper.cjs");if(existsSync13(resolved))return resolved}catch{}let globalBin=join14(homedir13(),".bun","bin","pgserve");if(existsSync13(globalBin))return globalBin;try{return execSync4("which pgserve",{encoding:"utf-8",timeout:3000}).trim()}catch{return"pgserve"}}async function startPgserveOnPort(port){mkdirSync7(DATA_DIR,{recursive:!0});let child=spawn3(findPgserveBin(),["--port",String(port),"--host",DEFAULT_HOST,"--data",DATA_DIR,"--log","warn","--no-stats","--no-cluster","--pgvector"],{detached:!0,stdio:"ignore"});child.unref(),pgserveChild=child;let timeout=Number(process.env.GENIE_PGSERVE_TIMEOUT)||30000,deadline=Date.now()+timeout;while(Date.now()<deadline){if(await isPostgresHealthy(port))return activePort=port,ownsLockfile=!0,process.env.GENIE_PG_AVAILABLE="true",writeLockfile(port),port;await new Promise((r)=>setTimeout(r,500))}try{child.kill("SIGTERM")}catch{}throw Error(`pgserve failed to start on port ${port} (timeout after ${timeout/1000}s)`)}function registerExitHandler(){if(exitHandlerRegistered)return;exitHandlerRegistered=!0;let cleanup=()=>{if(pgserveChild){try{pgserveChild.kill("SIGTERM")}catch{}pgserveChild=null}if(ownsLockfile)removeLockfile(),ownsLockfile=!1};process.on("exit",cleanup),process.on("SIGINT",()=>{cleanup(),process.exit(130)}),process.on("SIGTERM",()=>{cleanup(),process.exit(143)})}async function healthCheckCachedClient(){if(!sqlClient)return null;try{return await sqlClient`SELECT 1`,sqlClient}catch{try{await sqlClient.end({timeout:2})}catch{}return sqlClient=null,activePort=null,null}}async function runPostConnectSetup(client,testSchema,timings){let _t2=Date.now();if(!testSchema)await runMigrations(client);let _t3=Date.now();if(!testSchema&&needsSeed())await runSeed(client);let _t4=Date.now();if(!testSchema&&!retentionRan)await runRetention(client);let _t5=Date.now();if(process.env.GENIE_PROFILE_DB)console.error(`[db-profile] pgserve=${timings.t1-timings.t0}ms migrate=${_t3-_t2}ms seed=${_t4-_t3}ms retention=${_t5-_t4}ms total=${_t5-timings.t0}ms`)}async function getConnection(){let cached=await healthCheckCachedClient();if(cached)return cached;let _t0=Date.now(),port=await ensurePgserve(),_t1=Date.now(),pgModule=(await Promise.resolve().then(() => (init_src(),exports_src))).default,testSchema=process.env.GENIE_TEST_SCHEMA;sqlClient=pgModule({host:DEFAULT_HOST,port,database:DB_NAME,username:"postgres",password:"postgres",max:50,idle_timeout:1,connect_timeout:5,onnotice:()=>{},connection:{client_min_messages:"warning",...testSchema?{search_path:`${testSchema}, public`}:{}}});try{await runPostConnectSetup(sqlClient,testSchema,{t0:_t0,t1:_t1})}catch(err){try{await sqlClient.end({timeout:2})}catch{}throw sqlClient=null,activePort=null,err}return sqlClient}function isConnected(){return sqlClient!==null}async function resetConnection(){if(sqlClient){let dying=sqlClient;sqlClient=null,await dying.end({timeout:5})}}async function isAvailable(){try{return await(await getConnection())`SELECT 1`,!0}catch{return!1}}async function shutdown(){if(sqlClient)await sqlClient.end({timeout:5}),sqlClient=null;if(ownsLockfile)removeLockfile(),ownsLockfile=!1}function getDataDir(){return DATA_DIR}function getActivePort(){return activePort??getPort()}function getLockfilePath(){return LOCKFILE_PATH}var DEFAULT_PORT=19642,DEFAULT_HOST="127.0.0.1",GENIE_HOME2,DATA_DIR,LOCKFILE_PATH,DB_NAME="genie",pgserveChild=null,sqlClient=null,activePort=null,ensurePromise=null,ownsLockfile=!1,exitHandlerRegistered=!1,retentionRan=!1;var init_db=__esm(()=>{init_db_migrations();init_pg_seed();GENIE_HOME2=process.env.GENIE_HOME??join14(homedir13(),".genie"),DATA_DIR=join14(GENIE_HOME2,"data","pgserve"),LOCKFILE_PATH=join14(GENIE_HOME2,"pgserve.port")});var exports_audit={};__export(exports_audit,{recordAuditEvent:()=>recordAuditEvent,queryToolUsage:()=>queryToolUsage,queryTimeline:()=>queryTimeline,querySummary:()=>querySummary,queryErrorPatterns:()=>queryErrorPatterns,queryCostBreakdown:()=>queryCostBreakdown,queryAuditEvents:()=>queryAuditEvents,getActor:()=>getActor,generateTraceId:()=>generateTraceId,followAuditEvents:()=>followAuditEvents});async function recordAuditEvent(entityType,entityId,eventType,actor,details){try{if(!await isAvailable())return;let sql=await getConnection();await sql`
|
|
303
303
|
INSERT INTO audit_events (entity_type, entity_id, event_type, actor, details)
|
|
304
304
|
VALUES (${entityType}, ${entityId}, ${eventType}, ${actor??null}, ${sql.json(details??{})})
|
|
305
305
|
`}catch{}}function parseSince(since){let match=since.match(/^(\d+)([smhd])$/);if(!match)return since;let amount=Number.parseInt(match[1],10),unit=match[2],ms={s:1000,m:60000,h:3600000,d:86400000}[unit]??3600000;return new Date(Date.now()-amount*ms).toISOString()}async function queryAuditEvents(options={}){let sql=await getConnection(),conditions=[],values2=[],paramIdx=1;if(options.type)conditions.push(`event_type = $${paramIdx++}`),values2.push(options.type);if(options.entity)conditions.push(`(entity_type = $${paramIdx} OR entity_id = $${paramIdx})`),paramIdx++,values2.push(options.entity);if(options.since)conditions.push(`created_at >= $${paramIdx++}::timestamptz`),values2.push(parseSince(options.since));if(options.errorsOnly)conditions.push("event_type LIKE '%error%' OR (details::text LIKE '%error%')");let where=conditions.length>0?`WHERE ${conditions.join(" AND ")}`:"",limit=options.limit??50;return await sql.unsafe(`SELECT id, entity_type, entity_id, event_type, actor, details, created_at
|
|
@@ -1178,7 +1178,7 @@ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.o
|
|
|
1178
1178
|
ORDER BY created_at ASC
|
|
1179
1179
|
`}async function pollApprovalState(approvalId,defaultAction){let sql=await getConnection(),[approval]=await sql`
|
|
1180
1180
|
SELECT decision, timeout_at FROM approvals WHERE id = ${approvalId}
|
|
1181
|
-
`;if(!approval)return"deny";if(approval.decision==="allow"||approval.decision==="deny")return approval.decision;if(new Date(approval.timeout_at)<=new Date)return await resolveApproval(approvalId,defaultAction,"timeout"),defaultAction;return null}async function waitForResolution(approvalId,timeoutAt,defaultAction){let sql=await getConnection();return new Promise((resolve5)=>{let resolved=!1,listener=null,pollTimer=null,finish=(decision)=>{if(resolved)return;if(resolved=!0,listener)listener.unlisten().catch(()=>{});if(pollTimer)clearInterval(pollTimer);resolve5(decision)},checkResolution=async()=>{if(resolved)return;try{let result2=await pollApprovalState(approvalId,defaultAction);if(result2!==null)finish(result2)}catch{finish("deny")}};sql.listen("genie_approval_resolved",(payload)=>{if(payload===approvalId)checkResolution()}).then((l)=>{if(listener=l,resolved)l.unlisten().catch(()=>{})}).catch(()=>{}),pollTimer=setInterval(checkResolution,5000);let msUntilTimeout=Math.max(0,timeoutAt.getTime()-Date.now()+1000);setTimeout(()=>{if(!resolved)resolveApproval(approvalId,defaultAction,"timeout").catch(()=>{}),finish(defaultAction)},msUntilTimeout).unref(),checkResolution()})}function createRemoteApprovalGate(config){let timeoutSec=config.permissions?.timeout??300,defaultAction=config.permissions?.defaultAction??"deny";return async(input)=>{let hookInput=input,toolName=hookInput.tool_name,toolInput=hookInput.tool_input??{},preview=JSON.stringify(toolInput).slice(0,500),timeoutAt=new Date(Date.now()+timeoutSec*1000),approvalId=await insertApproval(config.executorId,config.agentName,toolName,preview,timeoutAt);if(config.permissions?.omniChat&&config.permissions?.omniInstance)sendApprovalToOmni(approvalId,config.agentName,toolName,preview,config.permissions).catch(()=>{});return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:await waitForResolution(approvalId,timeoutAt,defaultAction)}}}}var recentOmniSends,BATCH_WINDOW_MS=1e4,BATCH_THRESHOLD=3;var init_claude_sdk_remote_approval=__esm(()=>{init_db();recentOmniSends=[]});var exports_claude_sdk={};__export(exports_claude_sdk,{translateSdkConfig:()=>translateSdkConfig,ClaudeSdkProvider:()=>ClaudeSdkProvider});import{readFileSync as readFileSync16}from"fs";import{query}from"@anthropic-ai/claude-agent-sdk";function translateSdkConfig(sdkConfig){let opts={};for(let key of SDK_TRUTHY_FIELDS)if(sdkConfig[key])opts[key]=sdkConfig[key];for(let key of SDK_NULLABLE_FIELDS)if(sdkConfig[key]!=null)opts[key]=sdkConfig[key];for(let key of SDK_CAST_FIELDS)if(sdkConfig[key])opts[key]=sdkConfig[key];return opts}function mergeHooks(...sources){let merged={};for(let src of sources){if(!src)continue;for(let[event,matchers]of Object.entries(src)){if(!matchers)continue;if(!merged[event])merged[event]=[...matchers];else merged[event].push(...matchers)}}return merged}class ClaudeSdkProvider{name="claude-sdk";transport="process";activeQueries=new Map;buildSpawnCommand(ctx){return{command:"claude-sdk-in-process",provider:"claude-sdk",meta:{role:ctx.role,skill:ctx.skill}}}runQuery(ctx,prompt2,permissionConfig,extraOptions,sdkConfig){ensureTeammateBypassPermissions();let abortController=new AbortController,tracker={abortController,done:!1};this.activeQueries.set(ctx.executorId,tracker);let permHooks;if(sdkConfig?.permissionMode==="remoteApproval"){let ws=findWorkspace(ctx.cwd),permissions=ws?getWorkspaceConfig(ws.root).permissions:void 0;permHooks={PreToolUse:[{matcher:"*",hooks:[createRemoteApprovalGate({executorId:ctx.executorId,agentName:ctx.agentId??ctx.role??"unknown",permissions})]}]}}else if(permissionConfig)permHooks={PreToolUse:[{matcher:"*",hooks:[createPermissionGate(permissionConfig)]}]};let translatedSdk=sdkConfig?translateSdkConfig(sdkConfig):void 0,mergedHooks=mergeHooks(permHooks,translatedSdk?.hooks,extraOptions?.hooks),hasHooks=Object.keys(mergedHooks).length>0,resolvedSystemPrompt=ctx.systemPrompt;if(!resolvedSystemPrompt&&ctx.systemPromptFile)try{resolvedSystemPrompt=readFileSync16(ctx.systemPromptFile,"utf-8")}catch{}let options={cwd:ctx.cwd,abortController,...ctx.model&&{model:ctx.model},...resolvedSystemPrompt&&{systemPrompt:resolvedSystemPrompt},...translatedSdk,...extraOptions,...hasHooks&&{hooks:mergedHooks},permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0},messages2=query({prompt:prompt2,options}),originalReturn=messages2.return.bind(messages2);messages2.return=async(value)=>{return tracker.done=!0,this.activeQueries.delete(ctx.executorId),originalReturn(value)};let self2=this,wrappedMessages=async function*(){try{for await(let msg of messages2)yield msg,routeSdkMessage(msg,ctx.executorId,ctx.agentId).catch(()=>{})}finally{tracker.done=!0,self2.activeQueries.delete(ctx.executorId)}}();return wrappedMessages.interrupt=messages2.interrupt.bind(messages2),wrappedMessages.setPermissionMode=messages2.setPermissionMode.bind(messages2),wrappedMessages.setModel=messages2.setModel.bind(messages2),wrappedMessages.return=messages2.return.bind(messages2),wrappedMessages.throw=messages2.throw.bind(messages2),{messages:wrappedMessages,abortController}}async extractSession(executor){let sessionId=executor.claudeSessionId;if(!sessionId)return null;return{sessionId}}async detectState(executor){let tracker=this.activeQueries.get(executor.id);if(!tracker)return"done";return tracker.done?"done":"running"}async terminate(executor){let tracker=this.activeQueries.get(executor.id);if(tracker)tracker.abortController.abort(),tracker.done=!0,this.activeQueries.delete(executor.id)}canResume(){return!1}}var SDK_TRUTHY_FIELDS,SDK_NULLABLE_FIELDS,SDK_CAST_FIELDS;var init_claude_sdk=__esm(()=>{init_claude_settings();init_workspace();init_claude_sdk_events();init_claude_sdk_permissions();init_claude_sdk_remote_approval();SDK_TRUTHY_FIELDS=["tools","allowedTools","disallowedTools","plugins","systemPrompt","betas","settingSources"],SDK_NULLABLE_FIELDS=["maxTurns","maxBudgetUsd","persistSession","enableFileCheckpointing","includePartialMessages","includeHookEvents","promptSuggestions","agentProgressSummaries"],SDK_CAST_FIELDS=["effort","thinking","agents","mcpServers","outputFormat","sandbox","settings"]});class CodexProvider{name="codex";transport="api";buildSpawnCommand(ctx){let params=spawnContextToParams3(ctx);return buildCodexCommand(params)}async extractSession(_executor){return null}async detectState(executor){if(executor.state==="terminated"||executor.endedAt)return"terminated";return"working"}async terminate(executor){if(executor.pid)try{process.kill(executor.pid,"SIGTERM")}catch{}}canResume(){return!1}async deliverMessage(_executorId,_message){}}function spawnContextToParams3(ctx){return{provider:"codex",team:ctx.team,role:ctx.role,skill:ctx.skill,agentId:ctx.agentId,executorId:ctx.executorId,extraArgs:ctx.extraArgs}}var init_codex=__esm(()=>{init_provider_adapters()});function getProvider(name){return providers.get(name)}var providers,claude,codex,appPty,claudeSdk;var init_registry=__esm(()=>{init_app_pty();init_claude_code();init_claude_sdk();init_codex();providers=new Map;claude=new ClaudeCodeProvider,codex=new CodexProvider,appPty=new AppPtyProvider,claudeSdk=new ClaudeSdkProvider;providers.set("claude",claude);providers.set("codex",codex);providers.set("app-pty",appPty);providers.set("claude-sdk",claudeSdk)});var exports_wish_state={};__export(exports_wish_state,{startGroup:()=>startGroup,resolveRepoPath:()=>resolveRepoPath,resetInProgressGroups:()=>resetInProgressGroups,resetGroup:()=>resetGroup,isWishComplete:()=>isWishComplete,getState:()=>getState,getOrCreateState:()=>getOrCreateState,getGroupState:()=>getGroupState,findGroupByAssignee:()=>findGroupByAssignee,findAnyGroupByAssignee:()=>findAnyGroupByAssignee,createState:()=>createState,completeGroup:()=>completeGroup});import{execSync as execSync8}from"child_process";import{existsSync as existsSync26}from"fs";import{dirname as dirname5}from"path";function normalizeGitPath(path3){if(process.platform!=="darwin")return path3;if(!path3.startsWith("/private/"))return path3;let logicalPath=path3.slice(8);return existsSync26(logicalPath)?logicalPath:path3}function resolveRepoPath(cwd){if(cwd)return cwd;try{let currentDir=process.cwd(),commonDir=execSync8("git rev-parse --path-format=absolute --git-common-dir",{encoding:"utf-8",stdio:["pipe","pipe","pipe"],env:{...process.env,GIT_CEILING_DIRECTORIES:dirname5(currentDir)}}).trim();return normalizeGitPath(dirname5(commonDir))}catch{return normalizeGitPath(process.cwd())}}function wishFilePath(slug){return`.genie/wishes/${slug}/WISH.md`}function toISO(v){if(v==null)return;if(v instanceof Date)return v.toISOString();return String(v)}async function findParent(sql,slug,repoPath){let wishFile=wishFilePath(slug),rows=await sql`
|
|
1181
|
+
`;if(!approval)return"deny";if(approval.decision==="allow"||approval.decision==="deny")return approval.decision;if(new Date(approval.timeout_at)<=new Date)return await resolveApproval(approvalId,defaultAction,"timeout"),defaultAction;return null}async function waitForResolution(approvalId,timeoutAt,defaultAction){let sql=await getConnection();return new Promise((resolve5)=>{let resolved=!1,listener=null,pollTimer=null,finish=(decision)=>{if(resolved)return;if(resolved=!0,listener)listener.unlisten().catch(()=>{});if(pollTimer)clearInterval(pollTimer);resolve5(decision)},checkResolution=async()=>{if(resolved)return;try{let result2=await pollApprovalState(approvalId,defaultAction);if(result2!==null)finish(result2)}catch{finish("deny")}};sql.listen("genie_approval_resolved",(payload)=>{if(payload===approvalId)checkResolution()}).then((l)=>{if(listener=l,resolved)l.unlisten().catch(()=>{})}).catch(()=>{}),pollTimer=setInterval(checkResolution,5000);let msUntilTimeout=Math.max(0,timeoutAt.getTime()-Date.now()+1000);setTimeout(()=>{if(!resolved)resolveApproval(approvalId,defaultAction,"timeout").catch(()=>{}),finish(defaultAction)},msUntilTimeout).unref(),checkResolution()})}function createRemoteApprovalGate(config){let timeoutSec=config.permissions?.timeout??300,defaultAction=config.permissions?.defaultAction??"deny";return async(input)=>{let hookInput=input,toolName=hookInput.tool_name,toolInput=hookInput.tool_input??{},preview=JSON.stringify(toolInput).slice(0,500),timeoutAt=new Date(Date.now()+timeoutSec*1000),approvalId=await insertApproval(config.executorId,config.agentName,toolName,preview,timeoutAt);if(config.permissions?.omniChat&&config.permissions?.omniInstance)sendApprovalToOmni(approvalId,config.agentName,toolName,preview,config.permissions).catch(()=>{});return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:await waitForResolution(approvalId,timeoutAt,defaultAction)}}}}var recentOmniSends,BATCH_WINDOW_MS=1e4,BATCH_THRESHOLD=3;var init_claude_sdk_remote_approval=__esm(()=>{init_db();recentOmniSends=[]});var exports_claude_sdk={};__export(exports_claude_sdk,{translateSdkConfig:()=>translateSdkConfig,ClaudeSdkProvider:()=>ClaudeSdkProvider});import{readFileSync as readFileSync16}from"fs";import{query}from"@anthropic-ai/claude-agent-sdk";function translateSdkConfig(sdkConfig){let opts={};for(let key of SDK_TRUTHY_FIELDS)if(sdkConfig[key])opts[key]=sdkConfig[key];for(let key of SDK_NULLABLE_FIELDS)if(sdkConfig[key]!=null)opts[key]=sdkConfig[key];for(let key of SDK_CAST_FIELDS)if(sdkConfig[key])opts[key]=sdkConfig[key];return opts}function mergeHooks(...sources){let merged={};for(let src of sources){if(!src)continue;for(let[event,matchers]of Object.entries(src)){if(!matchers)continue;if(!merged[event])merged[event]=[...matchers];else merged[event].push(...matchers)}}return merged}class ClaudeSdkProvider{name="claude-sdk";transport="process";activeQueries=new Map;buildSpawnCommand(ctx){return{command:"claude-sdk-in-process",provider:"claude-sdk",meta:{role:ctx.role,skill:ctx.skill}}}runQuery(ctx,prompt2,permissionConfig,extraOptions,sdkConfig){ensureTeammateBypassPermissions();let abortController=new AbortController,tracker={abortController,done:!1};this.activeQueries.set(ctx.executorId,tracker);let permHooks;if(sdkConfig?.permissionMode==="remoteApproval"){let ws=findWorkspace(ctx.cwd),permissions=ws?getWorkspaceConfig(ws.root).permissions:void 0;permHooks={PreToolUse:[{matcher:"*",hooks:[createRemoteApprovalGate({executorId:ctx.executorId,agentName:ctx.agentId??ctx.role??"unknown",permissions})]}]}}else if(permissionConfig)permHooks={PreToolUse:[{matcher:"*",hooks:[createPermissionGate(permissionConfig)]}]};let translatedSdk=sdkConfig?translateSdkConfig(sdkConfig):void 0,mergedHooks=mergeHooks(permHooks,translatedSdk?.hooks,extraOptions?.hooks),hasHooks=Object.keys(mergedHooks).length>0,resolvedSystemPrompt=ctx.systemPrompt;if(!resolvedSystemPrompt&&ctx.systemPromptFile)try{resolvedSystemPrompt=readFileSync16(ctx.systemPromptFile,"utf-8")}catch{}let options={cwd:ctx.cwd,abortController,...ctx.model&&{model:ctx.model},...resolvedSystemPrompt&&{systemPrompt:resolvedSystemPrompt},...translatedSdk,...extraOptions,...hasHooks&&{hooks:mergedHooks},permissionMode:"bypassPermissions",allowDangerouslySkipPermissions:!0},messages2=query({prompt:prompt2,options}),originalReturn=messages2.return.bind(messages2);messages2.return=async(value)=>{return tracker.done=!0,this.activeQueries.delete(ctx.executorId),originalReturn(value)};let self2=this,wrappedMessages=async function*(){try{for await(let msg of messages2)yield msg,routeSdkMessage(msg,ctx.executorId,ctx.agentId).catch(()=>{})}finally{tracker.done=!0,self2.activeQueries.delete(ctx.executorId)}}();return wrappedMessages.interrupt=messages2.interrupt.bind(messages2),wrappedMessages.setPermissionMode=messages2.setPermissionMode.bind(messages2),wrappedMessages.setModel=messages2.setModel.bind(messages2),wrappedMessages.return=messages2.return.bind(messages2),wrappedMessages.throw=messages2.throw.bind(messages2),{messages:wrappedMessages,abortController}}async extractSession(executor){let sessionId=executor.claudeSessionId;if(!sessionId)return null;return{sessionId}}async detectState(executor){let tracker=this.activeQueries.get(executor.id);if(!tracker)return"done";return tracker.done?"done":"running"}async terminate(executor){let tracker=this.activeQueries.get(executor.id);if(tracker)tracker.abortController.abort(),tracker.done=!0,this.activeQueries.delete(executor.id)}canResume(){return!1}}var SDK_TRUTHY_FIELDS,SDK_NULLABLE_FIELDS,SDK_CAST_FIELDS;var init_claude_sdk=__esm(()=>{init_claude_settings();init_workspace();init_claude_sdk_events();init_claude_sdk_permissions();init_claude_sdk_remote_approval();SDK_TRUTHY_FIELDS=["tools","allowedTools","disallowedTools","plugins","systemPrompt","betas","settingSources"],SDK_NULLABLE_FIELDS=["maxTurns","maxBudgetUsd","persistSession","enableFileCheckpointing","includePartialMessages","includeHookEvents","promptSuggestions","agentProgressSummaries"],SDK_CAST_FIELDS=["effort","thinking","agents","mcpServers","outputFormat","sandbox","settings"]});class CodexProvider{name="codex";transport="api";buildSpawnCommand(ctx){let params=spawnContextToParams3(ctx);return buildCodexCommand(params)}async extractSession(_executor){return null}async detectState(executor){if(executor.state==="terminated"||executor.endedAt)return"terminated";return"working"}async terminate(executor){if(executor.pid)try{process.kill(executor.pid,"SIGTERM")}catch{}}canResume(){return!1}async deliverMessage(_executorId,_message){}}function spawnContextToParams3(ctx){return{provider:"codex",team:ctx.team,role:ctx.role,skill:ctx.skill,agentId:ctx.agentId,executorId:ctx.executorId,extraArgs:ctx.extraArgs}}var init_codex=__esm(()=>{init_provider_adapters()});function getProvider(name){return providers.get(name)}var providers,claude,codex,appPty,claudeSdk;var init_registry=__esm(()=>{init_app_pty();init_claude_code();init_claude_sdk();init_codex();providers=new Map;claude=new ClaudeCodeProvider,codex=new CodexProvider,appPty=new AppPtyProvider,claudeSdk=new ClaudeSdkProvider;providers.set("claude",claude);providers.set("codex",codex);providers.set("app-pty",appPty);providers.set("claude-sdk",claudeSdk)});var exports_wish_state={};__export(exports_wish_state,{wipeState:()=>wipeState,startGroup:()=>startGroup,resolveRepoPath:()=>resolveRepoPath,resetInProgressGroups:()=>resetInProgressGroups,resetGroup:()=>resetGroup,isWishComplete:()=>isWishComplete,getState:()=>getState,getOrCreateState:()=>getOrCreateState,getGroupState:()=>getGroupState,findGroupByAssignee:()=>findGroupByAssignee,findAnyGroupByAssignee:()=>findAnyGroupByAssignee,createState:()=>createState,computeGroupsSignature:()=>computeGroupsSignature,completeGroup:()=>completeGroup,WishStateMismatchError:()=>WishStateMismatchError});import{execSync as execSync8}from"child_process";import{createHash as createHash4}from"crypto";import{existsSync as existsSync26}from"fs";import{dirname as dirname5}from"path";function computeGroupsSignature(groups){let canonical=groups.map((g)=>({name:g.name,dependsOn:[...g.dependsOn??[]].sort()})).sort((a,b2)=>a.name<b2.name?-1:a.name>b2.name?1:0);return createHash4("sha256").update(JSON.stringify(canonical)).digest("hex")}function diffGroups(prev,next){let prevMap=new Map(prev.map((g)=>[g.name,[...g.dependsOn??[]].sort()])),nextMap=new Map(next.map((g)=>[g.name,[...g.dependsOn??[]].sort()])),added=[],removed=[],changed=[];for(let[name,deps]of nextMap)if(!prevMap.has(name))added.push(name);else if(JSON.stringify(prevMap.get(name))!==JSON.stringify(deps))changed.push(name);for(let name of prevMap.keys())if(!nextMap.has(name))removed.push(name);return{added:added.sort(),removed:removed.sort(),changed:changed.sort()}}function normalizeGitPath(path3){if(process.platform!=="darwin")return path3;if(!path3.startsWith("/private/"))return path3;let logicalPath=path3.slice(8);return existsSync26(logicalPath)?logicalPath:path3}function resolveRepoPath(cwd){if(cwd)return cwd;try{let currentDir=process.cwd(),commonDir=execSync8("git rev-parse --path-format=absolute --git-common-dir",{encoding:"utf-8",stdio:["pipe","pipe","pipe"],env:{...process.env,GIT_CEILING_DIRECTORIES:dirname5(currentDir)}}).trim();return normalizeGitPath(dirname5(commonDir))}catch{return normalizeGitPath(process.cwd())}}function wishFilePath(slug){return`.genie/wishes/${slug}/WISH.md`}function toISO(v){if(v==null)return;if(v instanceof Date)return v.toISOString();return String(v)}async function findParent(sql,slug,repoPath){let wishFile=wishFilePath(slug),rows=await sql`
|
|
1182
1182
|
SELECT * FROM tasks
|
|
1183
1183
|
WHERE wish_file = ${wishFile} AND repo_path = ${repoPath} AND parent_id IS NULL
|
|
1184
1184
|
LIMIT 1
|
|
@@ -1186,9 +1186,12 @@ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.o
|
|
|
1186
1186
|
SELECT * FROM tasks
|
|
1187
1187
|
WHERE parent_id = ${parentId} AND group_name = ${groupName}
|
|
1188
1188
|
LIMIT 1
|
|
1189
|
-
`;return rows.length>0?rows[0]:null}function validateGroupRefs(groups){let groupNames=new Set(groups.map((g)=>g.name));for(let group of groups){if(group.dependsOn?.includes(group.name))throw Error(`Group "${group.name}" depends on itself`);for(let dep of group.dependsOn??[])if(!groupNames.has(dep))throw Error(`Group "${group.name}" depends on non-existent group "${dep}"`)}}function detectCycles(groups){let inDegree={},adjacency={};for(let group of groups)inDegree[group.name]=(group.dependsOn??[]).length,adjacency[group.name]=[];for(let group of groups)for(let dep of group.dependsOn??[])adjacency[dep].push(group.name);let queue=Object.entries(inDegree).filter(([,deg])=>deg===0).map(([name])=>name),processed=0;while(queue.length>0){let node=queue.shift();if(!node)break;processed++;for(let neighbor of adjacency[node])if(inDegree[neighbor]--,inDegree[neighbor]===0)queue.push(neighbor)}if(processed!==groups.length){let remaining=Object.entries(inDegree).filter(([,deg])=>deg>0).map(([name])=>name);throw Error(`Dependency cycle detected among groups: ${remaining.join(", ")}`)}}function validateGroups(groups){validateGroupRefs(groups),detectCycles(groups)}async function createState(slug,groups,cwd){validateGroups(groups);let sql=await getConnection(),repoPath=resolveRepoPath(cwd),wishFile=wishFilePath(slug),existingParent=await findParent(sql,slug,repoPath);if(existingParent)await sql`DELETE FROM tasks WHERE id = ${existingParent.id}`;let[parent]=await sql`
|
|
1190
|
-
INSERT INTO tasks (repo_path, title, wish_file, type_id, stage, status)
|
|
1191
|
-
VALUES (
|
|
1189
|
+
`;return rows.length>0?rows[0]:null}function validateGroupRefs(groups){let groupNames=new Set(groups.map((g)=>g.name));for(let group of groups){if(group.dependsOn?.includes(group.name))throw Error(`Group "${group.name}" depends on itself`);for(let dep of group.dependsOn??[])if(!groupNames.has(dep))throw Error(`Group "${group.name}" depends on non-existent group "${dep}"`)}}function detectCycles(groups){let inDegree={},adjacency={};for(let group of groups)inDegree[group.name]=(group.dependsOn??[]).length,adjacency[group.name]=[];for(let group of groups)for(let dep of group.dependsOn??[])adjacency[dep].push(group.name);let queue=Object.entries(inDegree).filter(([,deg])=>deg===0).map(([name])=>name),processed=0;while(queue.length>0){let node=queue.shift();if(!node)break;processed++;for(let neighbor of adjacency[node])if(inDegree[neighbor]--,inDegree[neighbor]===0)queue.push(neighbor)}if(processed!==groups.length){let remaining=Object.entries(inDegree).filter(([,deg])=>deg>0).map(([name])=>name);throw Error(`Dependency cycle detected among groups: ${remaining.join(", ")}`)}}function validateGroups(groups){validateGroupRefs(groups),detectCycles(groups)}async function createState(slug,groups,cwd){validateGroups(groups);let sql=await getConnection(),repoPath=resolveRepoPath(cwd),wishFile=wishFilePath(slug),existingParent=await findParent(sql,slug,repoPath);if(existingParent)await sql`DELETE FROM tasks WHERE id = ${existingParent.id}`;let groupsSignature=computeGroupsSignature(groups),[parent]=await sql`
|
|
1190
|
+
INSERT INTO tasks (repo_path, title, wish_file, type_id, stage, status, metadata)
|
|
1191
|
+
VALUES (
|
|
1192
|
+
${repoPath}, ${slug}, ${wishFile}, 'software', 'draft', 'ready',
|
|
1193
|
+
${sql.json({groupsSignature})}
|
|
1194
|
+
)
|
|
1192
1195
|
RETURNING *
|
|
1193
1196
|
`,childIds={};for(let group of groups){let status=(group.dependsOn??[]).length===0?"ready":"blocked",[child]=await sql`
|
|
1194
1197
|
INSERT INTO tasks (repo_path, title, parent_id, group_name, type_id, stage, status)
|
|
@@ -1247,7 +1250,12 @@ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.o
|
|
|
1247
1250
|
AND t.group_name IS NOT NULL
|
|
1248
1251
|
AND p.wish_file IS NOT NULL
|
|
1249
1252
|
ORDER BY t.created_at
|
|
1250
|
-
`;for(let row of rows){let assignee=row.assignee;if(assignee!==workerId&&!workerId.endsWith(`-${assignee}`))continue;let slugMatch=row.wish_file.match(/\.genie\/wishes\/([^/]+)\/WISH\.md/);if(!slugMatch)continue;let slug=slugMatch[1],groupName=row.group_name,state=await getState(slug,cwd);if(!state)continue;let group=state.groups[groupName];if(!group)continue;return{slug,groupName,group:{...group}}}return null}async function getOrCreateState(slug,groups,cwd){let existing=await getState(slug,cwd);if(existing)return existing;return createState(slug,groups,cwd)}async function
|
|
1253
|
+
`;for(let row of rows){let assignee=row.assignee;if(assignee!==workerId&&!workerId.endsWith(`-${assignee}`))continue;let slugMatch=row.wish_file.match(/\.genie\/wishes\/([^/]+)\/WISH\.md/);if(!slugMatch)continue;let slug=slugMatch[1],groupName=row.group_name,state=await getState(slug,cwd);if(!state)continue;let group=state.groups[groupName];if(!group)continue;return{slug,groupName,group:{...group}}}return null}async function getOrCreateState(slug,groups,cwd){let existing=await getState(slug,cwd);if(existing)return await validateSignatureOrThrow(slug,groups,cwd),existing;return createState(slug,groups,cwd)}async function validateSignatureOrThrow(slug,groups,cwd){let sql=await getConnection(),repoPath=resolveRepoPath(cwd),parent=await findParent(sql,slug,repoPath);if(!parent)return;let stored=readStoredSignature(parent.metadata);if(!stored)return;let fresh=computeGroupsSignature(groups);if(stored===fresh)return;let prevGroups=await readGroupDefinitions(sql,parent.id),{added,removed,changed}=diffGroups(prevGroups,groups);throw new WishStateMismatchError(slug,added,removed,changed)}function readStoredSignature(metadata){if(metadata==null)return null;let parsed=metadata;if(typeof metadata==="string")try{parsed=JSON.parse(metadata)}catch{return null}if(typeof parsed!=="object"||parsed===null)return null;let sig=parsed.groupsSignature;return typeof sig==="string"?sig:null}async function readGroupDefinitions(sql,parentId){let children=await sql`SELECT id, group_name FROM tasks WHERE parent_id = ${parentId} ORDER BY created_at`;if(children.length===0)return[];let childIds=children.map((c)=>c.id),deps=await sql`
|
|
1254
|
+
SELECT td.task_id, t.group_name AS dep_group
|
|
1255
|
+
FROM task_dependencies td
|
|
1256
|
+
JOIN tasks t ON t.id = td.depends_on_id
|
|
1257
|
+
WHERE td.task_id = ANY(${childIds})
|
|
1258
|
+
`,depsByTask={};for(let dep of deps){let taskId=dep.task_id;if(!depsByTask[taskId])depsByTask[taskId]=[];depsByTask[taskId].push(dep.dep_group)}return children.map((c)=>({name:c.group_name,dependsOn:depsByTask[c.id]??[]}))}async function getState(slug,cwd){let sql=await getConnection(),repoPath=resolveRepoPath(cwd),parent=await findParent(sql,slug,repoPath);if(!parent)return null;let children=await sql`SELECT * FROM tasks WHERE parent_id = ${parent.id} ORDER BY created_at`;if(children.length===0)return null;let childIds=children.map((c)=>c.id),deps=await sql`
|
|
1251
1259
|
SELECT td.task_id, t.group_name AS dep_group
|
|
1252
1260
|
FROM task_dependencies td
|
|
1253
1261
|
JOIN tasks t ON t.id = td.depends_on_id
|
|
@@ -1255,7 +1263,7 @@ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.o
|
|
|
1255
1263
|
`,depsMap={};for(let dep of deps){let taskId=dep.task_id;if(!depsMap[taskId])depsMap[taskId]=[];depsMap[taskId].push(dep.dep_group)}let actors=await sql`
|
|
1256
1264
|
SELECT task_id, actor_id FROM task_actors
|
|
1257
1265
|
WHERE task_id = ANY(${childIds}) AND role = 'assignee'
|
|
1258
|
-
`,assigneeMap={};for(let actor of actors)assigneeMap[actor.task_id]=actor.actor_id;let groups={};for(let child of children){let{id,group_name:groupName}=child;groups[groupName]={status:child.status,assignee:assigneeMap[id],dependsOn:depsMap[id]??[],startedAt:toISO(child.started_at),completedAt:toISO(child.ended_at)}}return{wish:slug,groups,createdAt:toISO(parent.created_at)??"",updatedAt:toISO(parent.updated_at)??""}}async function getGroupState(slug,groupName,cwd){let state=await getState(slug,cwd);if(!state)return null;return state.groups[groupName]??null}async function isWishComplete(slug,cwd){let state=await getState(slug,cwd);if(!state)return!1;let groups=Object.values(state.groups);return groups.length>0&&groups.every((g)=>g.status==="done")}async function resetInProgressGroups(slug,cwd){let sql=await getConnection(),repoPath=resolveRepoPath(cwd),parent=await findParent(sql,slug,repoPath);if(!parent)return 0;let now=new Date,inProgress=await sql`
|
|
1266
|
+
`,assigneeMap={};for(let actor of actors)assigneeMap[actor.task_id]=actor.actor_id;let groups={};for(let child of children){let{id,group_name:groupName}=child;groups[groupName]={status:child.status,assignee:assigneeMap[id],dependsOn:depsMap[id]??[],startedAt:toISO(child.started_at),completedAt:toISO(child.ended_at)}}return{wish:slug,groups,createdAt:toISO(parent.created_at)??"",updatedAt:toISO(parent.updated_at)??""}}async function getGroupState(slug,groupName,cwd){let state=await getState(slug,cwd);if(!state)return null;return state.groups[groupName]??null}async function isWishComplete(slug,cwd){let state=await getState(slug,cwd);if(!state)return!1;let groups=Object.values(state.groups);return groups.length>0&&groups.every((g)=>g.status==="done")}async function wipeState(slug,cwd){let sql=await getConnection(),repoPath=resolveRepoPath(cwd),parent=await findParent(sql,slug,repoPath);if(!parent)return!1;return await sql`DELETE FROM tasks WHERE id = ${parent.id}`,!0}async function resetInProgressGroups(slug,cwd){let sql=await getConnection(),repoPath=resolveRepoPath(cwd),parent=await findParent(sql,slug,repoPath);if(!parent)return 0;let now=new Date,inProgress=await sql`
|
|
1259
1267
|
SELECT id FROM tasks
|
|
1260
1268
|
WHERE parent_id = ${parent.id} AND status = 'in_progress'
|
|
1261
1269
|
`;if(inProgress.length===0)return 0;let ids=inProgress.map((r)=>r.id);return await sql`
|
|
@@ -1263,7 +1271,8 @@ Stopping inbox watcher...`),stopInboxWatcher2(handle),process.exit(0)};process.o
|
|
|
1263
1271
|
WHERE id = ANY(${ids})
|
|
1264
1272
|
`,await sql`
|
|
1265
1273
|
DELETE FROM task_actors WHERE task_id = ANY(${ids}) AND role = 'assignee'
|
|
1266
|
-
`,ids.length}var GroupStatusSchema,GroupStateSchema,WishStateSchema;var init_wish_state=__esm(()=>{init_zod();init_db();GroupStatusSchema=exports_external.enum(["blocked","ready","in_progress","done"]),GroupStateSchema=exports_external.object({status:GroupStatusSchema,assignee:exports_external.string().optional(),dependsOn:exports_external.array(exports_external.string()).default([]),startedAt:exports_external.string().optional(),completedAt:exports_external.string().optional()}),WishStateSchema=exports_external.object({wish:exports_external.string(),groups:exports_external.record(exports_external.string(),GroupStateSchema),createdAt:exports_external.string(),updatedAt:exports_external.string()})
|
|
1274
|
+
`,ids.length}var GroupStatusSchema,GroupStateSchema,WishStateSchema,WishStateMismatchError;var init_wish_state=__esm(()=>{init_zod();init_db();GroupStatusSchema=exports_external.enum(["blocked","ready","in_progress","done"]),GroupStateSchema=exports_external.object({status:GroupStatusSchema,assignee:exports_external.string().optional(),dependsOn:exports_external.array(exports_external.string()).default([]),startedAt:exports_external.string().optional(),completedAt:exports_external.string().optional()}),WishStateSchema=exports_external.object({wish:exports_external.string(),groups:exports_external.record(exports_external.string(),GroupStateSchema),createdAt:exports_external.string(),updatedAt:exports_external.string()});WishStateMismatchError=class WishStateMismatchError extends Error{slug;added;removed;changed;constructor(slug,added,removed,changed){let lines=[`Wish "${slug}" group structure has changed since state was created.`];if(added.length>0)lines.push(` + added: ${added.join(", ")}`);if(removed.length>0)lines.push(` - removed: ${removed.join(", ")}`);if(changed.length>0)lines.push(` ~ changed deps: ${changed.join(", ")}`);lines.push(""),lines.push(`Run \`genie reset ${slug}\` to recreate state from the current WISH.md.`);super(lines.join(`
|
|
1275
|
+
`));this.name="WishStateMismatchError",this.slug=slug,this.added=added,this.removed=removed,this.changed=changed}}});var exports_protocol_router_spawn={};__export(exports_protocol_router_spawn,{spawnWorkerFromTemplate:()=>spawnWorkerFromTemplate,injectResumeContext:()=>injectResumeContext,_deps:()=>_deps});import{exec as exec2}from"child_process";import{readFile as readFile6}from"fs/promises";import{join as join31}from"path";import{promisify as promisify2}from"util";async function resolveParentSession(_repoPath,team){let teamConfig=await getTeam(team);if(teamConfig?.nativeTeamParentSessionId)return teamConfig.nativeTeamParentSessionId;return await discoverClaudeParentSessionId()??`genie-${team}`}function buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId){let isClaude=template.provider==="claude"||template.provider==="claude-sdk",sessionName=template.role?`${template.team}-${template.role}`:void 0,newSessionId=isClaude&&!resumeSessionId?crypto.randomUUID():void 0,params={provider:template.provider,team:template.team,role:template.role,skill:template.skill,extraArgs:template.extraArgs,sessionId:newSessionId,resume:isClaude?resumeSessionId:void 0,name:sessionName};if(isClaude)params.nativeTeam={enabled:!0,parentSessionId,color:spawnColor,agentType:template.role??"general-purpose",agentName:template.role};return params}function buildFullCommand(launch){if(launch.env&&Object.keys(launch.env).length>0)return`env ${Object.entries(launch.env).map(([k,v])=>`${k}=${v}`).join(" ")} ${launch.command}`;return launch.command}async function generateWorkerId(team,role){let base=role?`${team}-${role}`:team;return(await list()).some((w)=>w.id===base)?`${base}-${crypto.randomUUID().slice(0,8)}`:base}async function terminateActiveExecutorIfRunning(agentId){let currentExec=await getCurrentExecutor(agentId);if(!currentExec||currentExec.state==="terminated"||currentExec.state==="done")return;let providerImpl=getProvider(currentExec.provider);if(providerImpl)try{await providerImpl.terminate(currentExec)}catch{}await terminateActiveExecutor(agentId)}async function capturePanePid(paneId){try{let{stdout:pidOut}=await execAsync(genieTmuxCmd(`display -t '${paneId}' -p '#{pane_pid}'`)),parsed=Number.parseInt(pidOut.trim(),10);return parsed>0?parsed:null}catch{return null}}function resolveExecutorTransport(provider){if(provider==="codex")return"api";if(provider==="claude-sdk")return"process";return"tmux"}async function createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow){let agentName=template.role??"worker",agentIdentity=await findOrCreateAgent(agentName,template.team,template.role);await(await getConnection()).begin(async(tx)=>{await tx`SELECT pg_advisory_xact_lock(hashtext(${agentIdentity.id}))`,await terminateActiveExecutorIfRunning(agentIdentity.id);let pid=await capturePanePid(paneId),executor=await createExecutor(agentIdentity.id,template.provider,resolveExecutorTransport(template.provider),{pid,tmuxSession:session,tmuxPaneId:paneId,tmuxWindow:teamWindow?.windowName??null,tmuxWindowId:teamWindow?.windowId??null,claudeSessionId:effectiveSessionId??null,state:"spawning",repoPath,paneColor:spawnColor??null});await setCurrentExecutor(agentIdentity.id,executor.id)})}async function spawnPaneInSession(session,team,repoPath,fullCommand){let teamWindow=null;try{teamWindow=await ensureTeamWindow(session,team,repoPath)}catch{}let splitTarget=teamWindow?`-t '${teamWindow.windowId}'`:"",escapedCmd=fullCommand.replace(/'/g,"'\\''"),{stdout}=await execAsync(genieTmuxCmd(`split-window -d ${splitTarget} -P -F '#{pane_id}' '${escapedCmd}'`)),paneId=stdout.trim(),layoutTarget=`${session}:${teamWindow?.windowName??""}`;if(!teamWindow){let wins=await listWindows(session);layoutTarget=wins[0]?wins[0].id:`${session}:`}try{await execAsync(genieTmuxCmd(buildLayoutCommand(layoutTarget,resolveLayoutMode())))}catch{}return{paneId,teamWindow}}async function registerNativeTeamMember(team,agentName,template,paneId,repoPath,spawnColor,resumeSessionId){let now=new Date().toISOString();await registerNativeMember(team,{agentName,agentType:template.role??"general-purpose",color:spawnColor??"blue",tmuxPaneId:paneId,cwd:repoPath});let leaderInboxTarget;try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));leaderInboxTarget=await resolveLeaderName2(team)}catch{leaderInboxTarget=team}try{await _deps.writeNativeInbox(team,leaderInboxTarget,{from:agentName,text:`Worker ${agentName} (${template.provider}) auto-spawned${resumeSessionId?" with --resume":""}. Ready for tasks.`,summary:`${agentName} auto-spawned`,timestamp:now,color:spawnColor??"blue",read:!1})}catch(err){let msg=err instanceof Error?err.message:String(err);console.warn(`[protocol-router] Native inbox write failed for team="${team}" target="${leaderInboxTarget}": ${msg}`)}}async function tryAutoBrain(workerId,repoPath){try{let brain=await import("@khal-os/brain");if(brain.autoBrain)await brain.autoBrain({agentId:workerId,workdir:repoPath})}catch{}}async function spawnWorkerFromTemplate(template,resumeSessionId){let repoPath=template.cwd??process.cwd(),team=template.team,parentSessionId=await resolveParentSession(repoPath,team);await ensureNativeTeam(team,`Genie team: ${team}`,parentSessionId);let spawnColor=await assignColor(team),params=buildSpawnParams(template,parentSessionId,spawnColor,resumeSessionId),launch=buildLaunchCommand(validateSpawnParams(params)),fullCommand=buildFullCommand(launch),workerId=await generateWorkerId(team,template.role),session=(await getTeam(team))?.tmuxSessionName??await resolveRepoSession(repoPath)??await getCurrentSessionName()??team,{paneId,teamWindow}=await spawnPaneInSession(session,team,repoPath,fullCommand),now=new Date().toISOString(),agentName=template.role??"worker",isClaude=template.provider==="claude"||template.provider==="claude-sdk",effectiveSessionId=resumeSessionId??params.sessionId,workerEntry={id:workerId,paneId,session,provider:template.provider,transport:"tmux",role:template.role,skill:template.skill,team,worktree:null,startedAt:now,state:"spawning",lastStateChange:now,repoPath,claudeSessionId:effectiveSessionId,nativeTeamEnabled:isClaude,nativeAgentId:`${agentName}@${team}`,nativeColor:spawnColor,parentSessionId,window:teamWindow?.windowName,windowName:teamWindow?.windowName,windowId:teamWindow?.windowId};await register(workerEntry);try{await createExecutorForAutoSpawn(template,paneId,session,repoPath,effectiveSessionId,spawnColor,teamWindow)}catch{}if(isClaude)await registerNativeTeamMember(team,agentName,template,paneId,repoPath,spawnColor,resumeSessionId);if(spawnColor)await applyPaneColor(paneId,spawnColor,teamWindow?.windowId);return await injectResumeContext(repoPath,workerId,agentName,team),await tryAutoBrain(workerId,repoPath),{worker:workerEntry,paneId,workerId}}function extractGroupSection(content,groupName){let escaped=groupName.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),pattern=new RegExp(`^### Group ${escaped}:`,"m"),match=content.match(pattern);if(!match||match.index===void 0)return null;let start=match.index,nextBoundary=content.slice(start).slice(1).search(/^### Group \d|^---$/m),end=nextBoundary!==-1?start+1+nextBoundary:content.length;return content.slice(start,end).trim()}async function getRecentGitLog(repoPath,count=3){try{let{stdout}=await execAsync(`git -C '${repoPath}' log --oneline -${count} 2>/dev/null`);return stdout.trim()}catch{return""}}async function getGitStatus(repoPath){try{let{stdout}=await execAsync(`git -C '${repoPath}' status --short 2>/dev/null`);return stdout.trim()}catch{return""}}async function injectResumeContext(repoPath,workerId,agentName,_team){try{let match=await _deps.findAnyGroupByAssignee(workerId,repoPath)??await _deps.findAnyGroupByAssignee(agentName,repoPath);if(!match)return;let{slug,groupName,group}=match,wishPath=join31(repoPath,".genie","wishes",slug,"WISH.md"),groupSection="";try{let wishContent=await readFile6(wishPath,"utf-8");groupSection=extractGroupSection(wishContent,groupName)??""}catch{}let gitLog=await getRecentGitLog(repoPath),gitStatus=await getGitStatus(repoPath),resumePrompt=[`RESUME CONTEXT: You were working on wish "${slug}", group "${groupName}".`,`Status: ${group.status}. Started at: ${group.startedAt??"unknown"}.`,`Wish file: .genie/wishes/${slug}/WISH.md`,"",groupSection?`Group section:
|
|
1267
1276
|
${groupSection}`:"","",gitLog?`Last git log:
|
|
1268
1277
|
${gitLog}`:"","",gitStatus?`Uncommitted changes:
|
|
1269
1278
|
${gitStatus}`:"","","Pick up where you left off. Read the wish file for full context."].filter(Boolean).join(`
|
|
@@ -3258,25 +3267,25 @@ Done. ${results.length} migration${results.length===1?"":"s"} applied.`),await s
|
|
|
3258
3267
|
`,count=Number(rows[0].cnt);console.log(`Would delete ${count} event${count===1?"":"s"} older than ${options.olderThan}.`)}else{let result2=await sql`
|
|
3259
3268
|
DELETE FROM genie_runtime_events
|
|
3260
3269
|
WHERE created_at < now() - make_interval(secs => ${intervalSec})
|
|
3261
|
-
`,count=Number(result2.count);console.log(`Deleted ${count} event${count===1?"":"s"} older than ${options.olderThan}.`)}await shutdown()}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`Prune failed: ${message}`),process.exit(1)}}function registerDbCommands(program2){let db=program2.command("db").description("Database management (pgserve)");db.command("status").description("Show pgserve health, port, data dir, and table counts").action(dbStatusCommand),db.command("migrate").description("Run pending database migrations").action(dbMigrateCommand),db.command("query <sql>").description("Execute arbitrary SQL and print results").action(dbQueryCommand),db.command("url").description("Print postgres connection URL for direct access").option("--quiet","Print URL only, no trailing newline (for scripts)").action((options)=>{let url=`postgres://postgres:postgres@127.0.0.1:${getActivePort()}/genie`;if(options.quiet)process.stdout.write(url);else console.log(url)}),db.command("prune-events").description("Prune old runtime events beyond retention period").option("--older-than <duration>","Delete events older than (e.g., 30d, 7d)","14d").option("--dry-run","Show count without deleting").action(dbPruneEventsCommand),db.command("backup").description("Dump database to .genie/snapshot.sql.gz").action(dbBackupCommand),db.command("restore [file]").description("Restore database from snapshot (default: .genie/snapshot.sql.gz)").option("-y, --yes","Skip confirmation prompt").action(dbRestoreCommand)}init_protocol_router();import{execSync as execSync14}from"child_process";import{existsSync as existsSync39}from"fs";import{mkdir as mkdir6,readFile as readFile9,writeFile as writeFile3}from"fs/promises";import{tmpdir as tmpdir3}from"os";import{join as join48}from"path";init_tmux();import{existsSync as existsSync34}from"fs";import{join as join43}from"path";var REPOS_BASE="/home/genie/workspace/repos";function parseWishRef(ref){let trimmed=ref.trim();if(!trimmed)throw Error("Wish reference cannot be empty");let slashIndex=trimmed.indexOf("/");if(slashIndex===-1)return{slug:trimmed};let namespace=trimmed.slice(0,slashIndex),slug=trimmed.slice(slashIndex+1);if(!namespace)throw Error(`Invalid wish reference "${ref}": namespace is empty`);if(!slug)throw Error(`Invalid wish reference "${ref}": slug is empty`);if(slug.includes("/"))throw Error(`Invalid wish reference "${ref}": slug cannot contain "/"`);return{namespace,slug}}async function resolveWish(ref){let parsed=parseWishRef(ref);if(!parsed.namespace){let cwdWishPath=join43(process.cwd(),".genie","wishes",parsed.slug,"WISH.md");if(existsSync34(cwdWishPath)){let repo2=process.cwd(),session2=await resolveRepoSession(repo2);return{repo:repo2,wishPath:cwdWishPath,session:session2,slug:parsed.slug}}throw Error(`Wish "${parsed.slug}" not found in current directory. Use namespace/slug format (e.g., genie/${parsed.slug}) to specify the repo.`)}let repo=join43(REPOS_BASE,parsed.namespace);if(!existsSync34(repo))throw Error(`Repository "${parsed.namespace}" not found at ${repo}. Available repos are in ${REPOS_BASE}.`);let wishPath=join43(repo,".genie","wishes",parsed.slug,"WISH.md");if(!existsSync34(wishPath))throw Error(`Wish "${parsed.slug}" not found in repo "${parsed.namespace}". Expected: ${wishPath}`);let session=await resolveRepoSession(repo);return{repo,wishPath,session,slug:parsed.slug}}init_wish_state();init_agents();init_term_format();init_wish_state();import{execSync as execSync12}from"child_process";import{existsSync as existsSync35}from"fs";import{readFile as readFile8}from"fs/promises";import{dirname as dirname9,join as join44}from"path";async function loadExecutorInfo(){let[registryMod,executorMod,assignmentMod]=await Promise.all([Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),Promise.resolve().then(() => (init_assignment_registry(),exports_assignment_registry))]);return{registry:registryMod,executorRegistry:executorMod,assignmentRegistry:assignmentMod}}function normalizeGitPath2(path3){if(process.platform!=="darwin")return path3;if(!path3.startsWith("/private/"))return path3;let logicalPath=path3.slice(8);return existsSync35(logicalPath)?logicalPath:path3}function resolveWishPath(slug,cwd){let base=cwd??process.cwd(),cwdPath=join44(base,".genie","wishes",slug,"WISH.md");if(existsSync35(cwdPath))return cwdPath;try{let commonDir=execSync12("git rev-parse --path-format=absolute --git-common-dir",{encoding:"utf-8",cwd:base,stdio:["pipe","pipe","pipe"]}).trim(),repoRoot=normalizeGitPath2(dirname9(commonDir));if(repoRoot!==base){let repoPath=join44(repoRoot,".genie","wishes",slug,"WISH.md");if(existsSync35(repoPath))return repoPath}}catch{}return null}function parseRef(ref){let hashIdx=ref.indexOf("#");if(hashIdx===-1)throw Error(`Invalid reference "${ref}". Expected format: <slug>#<group>`);let slug=ref.slice(0,hashIdx),group=ref.slice(hashIdx+1);if(!slug||!group)throw Error(`Invalid reference "${ref}". Both slug and group are required.`);return{slug,group}}var STATUS_ICONS={blocked:"\uD83D\uDD12",ready:"\uD83D\uDFE2",in_progress:"\uD83D\uDD04",done:"\u2705"};async function detectWaveCompletion(slug,groupName,cwd){let wishPath=resolveWishPath(slug,cwd);if(!wishPath)return null;let content=await readFile8(wishPath,"utf-8"),targetWave=parseExecutionStrategy(content).find((w)=>w.groups.some((g)=>g.group===groupName));if(!targetWave)return null;let state=await getState(slug,cwd);if(!state)return null;let waveGroupNames=targetWave.groups.map((g)=>g.group);if(!waveGroupNames.every((g)=>state.groups[g]?.status==="done"))return null;return{waveName:targetWave.name,waveGroups:waveGroupNames}}async function ensureWorkPushed(slug,group){try{if(execSync12("git status --porcelain",{encoding:"utf-8"}).trim())console.log(" Committing dirty working tree..."),execSync12("git add -A",{encoding:"utf-8"}),execSync12(`git commit -m "wip: ${slug}#${group}"`,{encoding:"utf-8"}),console.log(` Committed as "wip: ${slug}#${group}"`)}catch{}try{if(execSync12("git log @{u}..HEAD --oneline",{encoding:"utf-8"}).trim())console.log(" Pushing unpushed commits..."),execSync12("git push",{encoding:"utf-8",timeout:30000}),console.log(" Push complete.")}catch{try{let branch=execSync12("git rev-parse --abbrev-ref HEAD",{encoding:"utf-8"}).trim();if(branch&&branch!=="HEAD")execSync12(`git push -u origin ${branch}`,{encoding:"utf-8",timeout:30000}),console.log(" Push complete (set upstream).")}catch{console.log(" \u26A0\uFE0F Push failed \u2014 manual push may be needed.")}}}async function autoCleanupTeam(){let teamName=process.env.GENIE_TEAM;if(!teamName)return;try{let teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),config=await teamManager.getTeam(teamName);if(!config)return;if(config.status==="done")return;console.log(` \uD83E\uDDF9 Auto-cleaning team "${teamName}"...`),await teamManager.setTeamStatus(teamName,"done"),await teamManager.killTeamMembers(teamName),console.log(` \u2705 Team "${teamName}" marked done, members killed.`)}catch{console.log(` \u26A0\uFE0F Auto-cleanup skipped \u2014 run \`genie team done ${teamName}\` manually.`)}}function autoKillPane(){let paneId=process.env.TMUX_PANE;if(paneId)setTimeout(()=>{try{let{genieTmuxCmd:genieTmuxCmd2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper));execSync12(genieTmuxCmd2(`kill-pane -t '${paneId}'`),{encoding:"utf-8"})}catch{process.exit(0)}},1000);else process.exit(0)}async function resolveNotificationTargets(){let teamName=process.env.GENIE_TEAM;if(!teamName)return{leader:"team-lead"};try{let teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leader=await teamManager.resolveLeaderName(teamName),config=await teamManager.getTeam(teamName);return{leader,spawner:config?.spawner}}catch{return{leader:teamName}}}async function notifyWaveCompletion(waveResult,wishComplete){console.log(` \uD83C\uDF0A ${waveResult.waveName} complete! All groups done: ${waveResult.waveGroups.join(", ")}`);try{let protocolRouter=await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router)),repoPath=process.cwd(),{leader,spawner}=await resolveNotificationTargets(),message=wishComplete?`WISH COMPLETE \u2014 all groups done: [${waveResult.waveGroups.join(", ")}]. Run \`genie team done\` to clean up.`:`${waveResult.waveName} complete. All groups done: [${waveResult.waveGroups.join(", ")}]. Run /review or advance to next wave.`,result2=await protocolRouter.sendMessage(repoPath,"cli",leader,message);if(result2&&typeof result2==="object"&&"delivered"in result2&&!result2.delivered)console.warn(` \u26A0\uFE0F Wave-complete notification to ${leader} may not have been delivered.`);else console.log(` Notified ${leader} of wave completion.`);if(spawner&&spawner!==leader&&spawner!=="cli")await protocolRouter.sendMessage(repoPath,"cli",spawner,message).catch(()=>{}),console.log(` Notified spawner (${spawner}) of wave completion.`)}catch{console.warn(" \u26A0\uFE0F Could not notify leader (messaging unavailable).")}}async function doneCommand(ref){try{let{slug,group}=parseRef(ref),result2=await completeGroup(slug,group);if(console.log(`\u2705 Group "${group}" marked as done in wish "${slug}"`),result2.completedAt)console.log(` Completed at: ${formatTimestamp(result2.completedAt)}`);let state=await getState(slug);if(state){let nowReady=Object.entries(state.groups).filter(([,g])=>g.status==="ready"&&g.dependsOn.includes(group)).map(([name])=>name);if(nowReady.length>0)console.log(` Unblocked: ${nowReady.join(", ")}`)}await ensureWorkPushed(slug,group);let wishComplete=await isWishComplete(slug),waveResult=await detectWaveCompletion(slug,group);if(waveResult)await notifyWaveCompletion(waveResult,wishComplete);if(wishComplete)console.log(" \uD83C\uDF89 Wish fully complete \u2014 all groups done."),await autoCleanupTeam();autoKillPane()}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}}async function autoInitWishState(slug){let wishPath=resolveWishPath(slug);if(!wishPath)console.error(`\u274C No state found for wish "${slug}" and no WISH.md found in cwd or repo root`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);let content=await readFile8(wishPath,"utf-8"),groups=parseWishGroups(content);if(groups.length===0)console.error(`\u274C No execution groups found in ${wishPath}`),process.exit(1);let state=await createState(slug,groups);return console.log(`\uD83D\uDCDD Auto-initialized state for wish "${slug}" (${groups.length} groups)`),state}async function printWishExecutors(slug){try{let{registry:registry2,executorRegistry,assignmentRegistry}=await loadExecutorInfo(),agents=await registry2.listAgents({team:process.env.GENIE_TEAM}),executorInfoLines=[];for(let agent of agents){if(!agent.currentExecutorId)continue;let executor=await executorRegistry.getExecutor(agent.currentExecutorId);if(!executor||executor.state==="terminated"||executor.state==="done")continue;let assignment=await assignmentRegistry.getActiveAssignment(executor.id),taskLabel=assignment?.wishSlug===slug?`Group ${assignment.groupNumber??"?"}`:assignment?.wishSlug??"-",name=agent.customName??agent.role??agent.id.slice(0,12);executorInfoLines.push(` Agent: ${padRight(name,16)} | Executor: ${executor.id.slice(0,12)} (${executor.provider}) | State: ${padRight(executor.state,10)} | Task: ${taskLabel}`)}if(executorInfoLines.length>0){console.log(`
|
|
3270
|
+
`,count=Number(result2.count);console.log(`Deleted ${count} event${count===1?"":"s"} older than ${options.olderThan}.`)}await shutdown()}catch(err){let message=err instanceof Error?err.message:String(err);console.error(`Prune failed: ${message}`),process.exit(1)}}function registerDbCommands(program2){let db=program2.command("db").description("Database management (pgserve)");db.command("status").description("Show pgserve health, port, data dir, and table counts").action(dbStatusCommand),db.command("migrate").description("Run pending database migrations").action(dbMigrateCommand),db.command("query <sql>").description("Execute arbitrary SQL and print results").action(dbQueryCommand),db.command("url").description("Print postgres connection URL for direct access").option("--quiet","Print URL only, no trailing newline (for scripts)").action((options)=>{let url=`postgres://postgres:postgres@127.0.0.1:${getActivePort()}/genie`;if(options.quiet)process.stdout.write(url);else console.log(url)}),db.command("prune-events").description("Prune old runtime events beyond retention period").option("--older-than <duration>","Delete events older than (e.g., 30d, 7d)","14d").option("--dry-run","Show count without deleting").action(dbPruneEventsCommand),db.command("backup").description("Dump database to .genie/snapshot.sql.gz").action(dbBackupCommand),db.command("restore [file]").description("Restore database from snapshot (default: .genie/snapshot.sql.gz)").option("-y, --yes","Skip confirmation prompt").action(dbRestoreCommand)}init_protocol_router();import{execSync as execSync14}from"child_process";import{existsSync as existsSync39}from"fs";import{mkdir as mkdir6,readFile as readFile9,writeFile as writeFile3}from"fs/promises";import{tmpdir as tmpdir3}from"os";import{join as join48}from"path";init_tmux();import{existsSync as existsSync34}from"fs";import{join as join43}from"path";var REPOS_BASE="/home/genie/workspace/repos";function parseWishRef(ref){let trimmed=ref.trim();if(!trimmed)throw Error("Wish reference cannot be empty");let slashIndex=trimmed.indexOf("/");if(slashIndex===-1)return{slug:trimmed};let namespace=trimmed.slice(0,slashIndex),slug=trimmed.slice(slashIndex+1);if(!namespace)throw Error(`Invalid wish reference "${ref}": namespace is empty`);if(!slug)throw Error(`Invalid wish reference "${ref}": slug is empty`);if(slug.includes("/"))throw Error(`Invalid wish reference "${ref}": slug cannot contain "/"`);return{namespace,slug}}async function resolveWish(ref){let parsed=parseWishRef(ref);if(!parsed.namespace){let cwdWishPath=join43(process.cwd(),".genie","wishes",parsed.slug,"WISH.md");if(existsSync34(cwdWishPath)){let repo2=process.cwd(),session2=await resolveRepoSession(repo2);return{repo:repo2,wishPath:cwdWishPath,session:session2,slug:parsed.slug}}throw Error(`Wish "${parsed.slug}" not found in current directory. Use namespace/slug format (e.g., genie/${parsed.slug}) to specify the repo.`)}let repo=join43(REPOS_BASE,parsed.namespace);if(!existsSync34(repo))throw Error(`Repository "${parsed.namespace}" not found at ${repo}. Available repos are in ${REPOS_BASE}.`);let wishPath=join43(repo,".genie","wishes",parsed.slug,"WISH.md");if(!existsSync34(wishPath))throw Error(`Wish "${parsed.slug}" not found in repo "${parsed.namespace}". Expected: ${wishPath}`);let session=await resolveRepoSession(repo);return{repo,wishPath,session,slug:parsed.slug}}init_wish_state();init_agents();init_interactivity();init_term_format();init_wish_state();import{execSync as execSync12}from"child_process";import{existsSync as existsSync35}from"fs";import{readFile as readFile8}from"fs/promises";import{dirname as dirname9,join as join44}from"path";async function loadExecutorInfo(){let[registryMod,executorMod,assignmentMod]=await Promise.all([Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry)),Promise.resolve().then(() => (init_executor_registry(),exports_executor_registry)),Promise.resolve().then(() => (init_assignment_registry(),exports_assignment_registry))]);return{registry:registryMod,executorRegistry:executorMod,assignmentRegistry:assignmentMod}}function normalizeGitPath2(path3){if(process.platform!=="darwin")return path3;if(!path3.startsWith("/private/"))return path3;let logicalPath=path3.slice(8);return existsSync35(logicalPath)?logicalPath:path3}function resolveWishPath(slug,cwd){let base=cwd??process.cwd(),cwdPath=join44(base,".genie","wishes",slug,"WISH.md");if(existsSync35(cwdPath))return cwdPath;try{let commonDir=execSync12("git rev-parse --path-format=absolute --git-common-dir",{encoding:"utf-8",cwd:base,stdio:["pipe","pipe","pipe"]}).trim(),repoRoot=normalizeGitPath2(dirname9(commonDir));if(repoRoot!==base){let repoPath=join44(repoRoot,".genie","wishes",slug,"WISH.md");if(existsSync35(repoPath))return repoPath}}catch{}return null}function parseRef(ref){let hashIdx=ref.indexOf("#");if(hashIdx===-1)throw Error(`Invalid reference "${ref}". Expected format: <slug>#<group>`);let slug=ref.slice(0,hashIdx),group=ref.slice(hashIdx+1);if(!slug||!group)throw Error(`Invalid reference "${ref}". Both slug and group are required.`);return{slug,group}}var STATUS_ICONS={blocked:"\uD83D\uDD12",ready:"\uD83D\uDFE2",in_progress:"\uD83D\uDD04",done:"\u2705"};async function detectWaveCompletion(slug,groupName,cwd){let wishPath=resolveWishPath(slug,cwd);if(!wishPath)return null;let content=await readFile8(wishPath,"utf-8"),targetWave=parseExecutionStrategy(content).find((w)=>w.groups.some((g)=>g.group===groupName));if(!targetWave)return null;let state=await getState(slug,cwd);if(!state)return null;let waveGroupNames=targetWave.groups.map((g)=>g.group);if(!waveGroupNames.every((g)=>state.groups[g]?.status==="done"))return null;return{waveName:targetWave.name,waveGroups:waveGroupNames}}async function ensureWorkPushed(slug,group){try{if(execSync12("git status --porcelain",{encoding:"utf-8"}).trim())console.log(" Committing dirty working tree..."),execSync12("git add -A",{encoding:"utf-8"}),execSync12(`git commit -m "wip: ${slug}#${group}"`,{encoding:"utf-8"}),console.log(` Committed as "wip: ${slug}#${group}"`)}catch{}try{if(execSync12("git log @{u}..HEAD --oneline",{encoding:"utf-8"}).trim())console.log(" Pushing unpushed commits..."),execSync12("git push",{encoding:"utf-8",timeout:30000}),console.log(" Push complete.")}catch{try{let branch=execSync12("git rev-parse --abbrev-ref HEAD",{encoding:"utf-8"}).trim();if(branch&&branch!=="HEAD")execSync12(`git push -u origin ${branch}`,{encoding:"utf-8",timeout:30000}),console.log(" Push complete (set upstream).")}catch{console.log(" \u26A0\uFE0F Push failed \u2014 manual push may be needed.")}}}async function autoCleanupTeam(){let teamName=process.env.GENIE_TEAM;if(!teamName)return;try{let teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),config=await teamManager.getTeam(teamName);if(!config)return;if(config.status==="done")return;console.log(` \uD83E\uDDF9 Auto-cleaning team "${teamName}"...`),await teamManager.setTeamStatus(teamName,"done"),await teamManager.killTeamMembers(teamName),console.log(` \u2705 Team "${teamName}" marked done, members killed.`)}catch{console.log(` \u26A0\uFE0F Auto-cleanup skipped \u2014 run \`genie team done ${teamName}\` manually.`)}}function autoKillPane(){let paneId=process.env.TMUX_PANE;if(paneId)setTimeout(()=>{try{let{genieTmuxCmd:genieTmuxCmd2}=(init_tmux_wrapper(),__toCommonJS(exports_tmux_wrapper));execSync12(genieTmuxCmd2(`kill-pane -t '${paneId}'`),{encoding:"utf-8"})}catch{process.exit(0)}},1000);else process.exit(0)}async function resolveNotificationTargets(){let teamName=process.env.GENIE_TEAM;if(!teamName)return{leader:"team-lead"};try{let teamManager=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager)),leader=await teamManager.resolveLeaderName(teamName),config=await teamManager.getTeam(teamName);return{leader,spawner:config?.spawner}}catch{return{leader:teamName}}}async function notifyWaveCompletion(waveResult,wishComplete){console.log(` \uD83C\uDF0A ${waveResult.waveName} complete! All groups done: ${waveResult.waveGroups.join(", ")}`);try{let protocolRouter=await Promise.resolve().then(() => (init_protocol_router(),exports_protocol_router)),repoPath=process.cwd(),{leader,spawner}=await resolveNotificationTargets(),message=wishComplete?`WISH COMPLETE \u2014 all groups done: [${waveResult.waveGroups.join(", ")}]. Run \`genie team done\` to clean up.`:`${waveResult.waveName} complete. All groups done: [${waveResult.waveGroups.join(", ")}]. Run /review or advance to next wave.`,result2=await protocolRouter.sendMessage(repoPath,"cli",leader,message);if(result2&&typeof result2==="object"&&"delivered"in result2&&!result2.delivered)console.warn(` \u26A0\uFE0F Wave-complete notification to ${leader} may not have been delivered.`);else console.log(` Notified ${leader} of wave completion.`);if(spawner&&spawner!==leader&&spawner!=="cli")await protocolRouter.sendMessage(repoPath,"cli",spawner,message).catch(()=>{}),console.log(` Notified spawner (${spawner}) of wave completion.`)}catch{console.warn(" \u26A0\uFE0F Could not notify leader (messaging unavailable).")}}async function doneCommand(ref){try{let{slug,group}=parseRef(ref),result2=await completeGroup(slug,group);if(console.log(`\u2705 Group "${group}" marked as done in wish "${slug}"`),result2.completedAt)console.log(` Completed at: ${formatTimestamp(result2.completedAt)}`);let state=await getState(slug);if(state){let nowReady=Object.entries(state.groups).filter(([,g])=>g.status==="ready"&&g.dependsOn.includes(group)).map(([name])=>name);if(nowReady.length>0)console.log(` Unblocked: ${nowReady.join(", ")}`)}await ensureWorkPushed(slug,group);let wishComplete=await isWishComplete(slug),waveResult=await detectWaveCompletion(slug,group);if(waveResult)await notifyWaveCompletion(waveResult,wishComplete);if(wishComplete)console.log(" \uD83C\uDF89 Wish fully complete \u2014 all groups done."),await autoCleanupTeam();autoKillPane()}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}}async function autoInitWishState(slug){let wishPath=resolveWishPath(slug);if(!wishPath)console.error(`\u274C No state found for wish "${slug}" and no WISH.md found in cwd or repo root`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);let content=await readFile8(wishPath,"utf-8"),groups=parseWishGroups(content);if(groups.length===0)console.error(`\u274C No execution groups found in ${wishPath}`),process.exit(1);let state=await createState(slug,groups);return console.log(`\uD83D\uDCDD Auto-initialized state for wish "${slug}" (${groups.length} groups)`),state}async function printWishExecutors(slug){try{let{registry:registry2,executorRegistry,assignmentRegistry}=await loadExecutorInfo(),agents=await registry2.listAgents({team:process.env.GENIE_TEAM}),executorInfoLines=[];for(let agent of agents){if(!agent.currentExecutorId)continue;let executor=await executorRegistry.getExecutor(agent.currentExecutorId);if(!executor||executor.state==="terminated"||executor.state==="done")continue;let assignment=await assignmentRegistry.getActiveAssignment(executor.id),taskLabel=assignment?.wishSlug===slug?`Group ${assignment.groupNumber??"?"}`:assignment?.wishSlug??"-",name=agent.customName??agent.role??agent.id.slice(0,12);executorInfoLines.push(` Agent: ${padRight(name,16)} | Executor: ${executor.id.slice(0,12)} (${executor.provider}) | State: ${padRight(executor.state,10)} | Task: ${taskLabel}`)}if(executorInfoLines.length>0){console.log(`
|
|
3262
3271
|
Active Executors:`),console.log("\u2500".repeat(60));for(let line of executorInfoLines)console.log(line)}}catch{}}async function statusCommand(slug){try{let state=await getState(slug)??await autoInitWishState(slug);console.log(`
|
|
3263
|
-
Wish: ${state.wish}`),console.log("\u2500".repeat(60));let entries=Object.entries(state.groups),maxNameLen=Math.max(...entries.map(([name])=>name.length),5);console.log(` ${padRight("GROUP",maxNameLen)} STATUS ASSIGNEE STARTED COMPLETED`),console.log(` ${"\u2500".repeat(maxNameLen+62)}`);for(let[name,group]of entries){let icon=STATUS_ICONS[group.status]??"\u2753",status=padRight(`${icon} ${group.status}`,13),assignee=padRight(group.assignee??"-",13),started=padRight(formatTimestamp(group.startedAt)||"-",14),completed=formatTimestamp(group.completedAt)||"-";console.log(` ${padRight(name,maxNameLen)} ${status} ${assignee} ${started} ${completed}`)}let total=entries.length,done=entries.filter(([,g])=>g.status==="done").length,inProgress=entries.filter(([,g])=>g.status==="in_progress").length,ready=entries.filter(([,g])=>g.status==="ready").length,blocked=entries.filter(([,g])=>g.status==="blocked").length;console.log(""),console.log(` Progress: ${done}/${total} done | ${inProgress} in progress | ${ready} ready | ${blocked} blocked`),await printWishExecutors(slug),console.log("")}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}}function registerStateCommands(program2){program2.command("done <ref>").description("Mark a wish group as done (format: <slug>#<group>)").action(async(ref)=>{await doneCommand(ref)}),program2.command("status <slug>").description("Show wish state overview for all groups").action(async(slug)=>{await statusCommand(slug)}),program2.command("reset <ref>").description("Reset
|
|
3272
|
+
Wish: ${state.wish}`),console.log("\u2500".repeat(60));let entries=Object.entries(state.groups),maxNameLen=Math.max(...entries.map(([name])=>name.length),5);console.log(` ${padRight("GROUP",maxNameLen)} STATUS ASSIGNEE STARTED COMPLETED`),console.log(` ${"\u2500".repeat(maxNameLen+62)}`);for(let[name,group]of entries){let icon=STATUS_ICONS[group.status]??"\u2753",status=padRight(`${icon} ${group.status}`,13),assignee=padRight(group.assignee??"-",13),started=padRight(formatTimestamp(group.startedAt)||"-",14),completed=formatTimestamp(group.completedAt)||"-";console.log(` ${padRight(name,maxNameLen)} ${status} ${assignee} ${started} ${completed}`)}let total=entries.length,done=entries.filter(([,g])=>g.status==="done").length,inProgress=entries.filter(([,g])=>g.status==="in_progress").length,ready=entries.filter(([,g])=>g.status==="ready").length,blocked=entries.filter(([,g])=>g.status==="blocked").length;console.log(""),console.log(` Progress: ${done}/${total} done | ${inProgress} in progress | ${ready} ready | ${blocked} blocked`),await printWishExecutors(slug),console.log("")}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}}function registerStateCommands(program2){program2.command("done <ref>").description("Mark a wish group as done (format: <slug>#<group>)").action(async(ref)=>{await doneCommand(ref)}),program2.command("status <slug>").description("Show wish state overview for all groups").action(async(slug)=>{await statusCommand(slug)}),program2.command("reset <ref>").option("-y, --yes","Skip confirmation prompt (required in non-interactive mode)").description("Reset wish state. <slug>#<group> resets one in-progress group; bare <slug> wipes the wish and recreates from current WISH.md").action(async(ref,options)=>{try{if(ref.includes("#")){let{slug,group}=parseRef(ref),result2=await resetGroup(slug,group);if(console.log(`\uD83D\uDD04 Group "${group}" reset to ready in wish "${slug}"`),result2.status==="ready")console.log(" Status: ready (assignee cleared)");return}await resetWishCommand(ref,options?.yes??!1)}catch(error2){let message=error2 instanceof Error?error2.message:String(error2);console.error(`\u274C ${message}`),process.exit(1)}})}async function resetWishCommand(slug,confirmed){let wishPath=resolveWishPath(slug);if(!wishPath)throw Error(`No WISH.md found for "${slug}" \u2014 searched cwd and repo root`);let content=await readFile8(wishPath,"utf-8"),groups=parseWishGroups(content);if(groups.length===0)throw Error(`No execution groups found in ${wishPath}`);let existing=await getState(slug);if(existing){let groupCount=Object.keys(existing.groups).length,inProgress=Object.values(existing.groups).filter((g)=>g.status==="in_progress").length,summary=`Wipe all state for "${slug}" (${groupCount} groups, ${inProgress} in-progress)?`;if(!isInteractive()){if(!confirmed)console.error(`\u274C ${summary}`),console.error(" Refusing to wipe in non-interactive mode. Pass --yes to confirm."),process.exit(2)}else{let{confirm:confirm2}=await Promise.resolve().then(() => (init_esm14(),exports_esm));if(!await confirm2({message:summary,default:!1})){console.log("Aborted.");return}}}if(existing)console.log(`\uD83D\uDDD1\uFE0F Replacing existing state for wish "${slug}"`);else console.log(`\u2139\uFE0F No existing state for wish "${slug}" \u2014 creating fresh`);let state=await createState(slug,groups);console.log(`\uD83D\uDCDD Recreated state from ${wishPath} (${groups.length} groups)`),console.log(""),console.log(`Wish: ${state.wish}`),console.log("\u2500".repeat(60));for(let[name,group]of Object.entries(state.groups)){let icon=STATUS_ICONS[group.status]??"\u2753";console.log(` ${name} ${icon} ${group.status}`)}}async function writeContextFile(content){let dir=join48(tmpdir3(),"genie-dispatch");await mkdir6(dir,{recursive:!0});let ts3=Date.now().toString(36),rand=Math.random().toString(36).slice(2,8),filePath=join48(dir,`ctx-${ts3}-${rand}.md`);return await writeFile3(filePath,content),filePath}function extractGroup(content,groupName){let pattern=new RegExp(`^### Group ${escapeRegExp(groupName)}:`,"m"),match=content.match(pattern);if(!match||match.index===void 0)return null;let start2=match.index,nextBoundary=content.slice(start2).slice(1).search(/^### Group \d|^---$/m),end=nextBoundary!==-1?start2+1+nextBoundary:content.length;return content.slice(start2,end).trim()}function extractWishContext(content){let execGroupsIdx=content.indexOf("## Execution Groups");if(execGroupsIdx!==-1)return content.slice(0,execGroupsIdx).trim();return content.slice(0,2000).trim()}var SLUG_PATTERN=/^[a-zA-Z0-9._-]+$/;function validateSlug(slug){if(!SLUG_PATTERN.test(slug))console.error(`\u274C Invalid slug: "${slug}"`),console.error(" Slugs must match [a-zA-Z0-9._-]+ (no slashes, dots-dots, or special characters)"),process.exit(1)}function escapeRegExp(str5){return str5.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function buildContextPrompt(opts){let parts=[`# Dispatch Context (${opts.command})`,"",`**Source file:** \`${opts.filePath}\``,"(Read the full document at the path above for complete context)",""];if(opts.wishContext)parts.push("## Wish Context","",opts.wishContext,"");if(parts.push("## Assigned Section","",opts.sectionContent,""),opts.enrichedContext)parts.push(opts.enrichedContext);if(opts.skill)parts.push("## Initial Command","",`Run \`/${opts.skill}\` to begin.`,"");return parts.join(`
|
|
3264
3273
|
`)}function getGitDiff(){try{let diff=execSync14("git diff HEAD",{encoding:"utf-8",maxBuffer:1048576}),staged=execSync14("git diff --cached",{encoding:"utf-8",maxBuffer:1048576}),combined=[diff,staged].filter(Boolean).join(`
|
|
3265
3274
|
`);if(combined.length>50000)return`${combined.slice(0,50000)}
|
|
3266
3275
|
|
|
3267
3276
|
... (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],start2=match.index,rest=content.slice(start2+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 depsNormalized=depsMatch[1].trim().replace(/\s*\([^)]*\)/g,"").trim();if(depsNormalized.toLowerCase()!=="none")dependsOn=depsNormalized.split(",").map((d)=>d.trim().replace(/^groups?\s*/i,"").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"}))}]}async function resolveLeaderTarget(){let teamName=process.env.GENIE_TEAM;if(!teamName)return"team-lead";try{let{resolveLeaderName:resolveLeaderName2}=await Promise.resolve().then(() => (init_team_manager(),exports_team_manager));return await resolveLeaderName2(teamName)}catch{return teamName}}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,actualSlug=slug;if(parseWishRef(slug).namespace){let resolved=await resolveWish(slug);wishPath=resolved.wishPath,actualSlug=resolved.slug;let{handleTeamCreate:handleTeamCreate2}=await Promise.resolve().then(() => (init_team(),exports_team));await handleTeamCreate2(actualSlug,{repo:resolved.repo,branch:"dev",wish:actualSlug,tmuxSession:resolved.session});return}if(validateSlug(slug),wishPath=join48(process.cwd(),".genie","wishes",slug,"WISH.md"),!existsSync39(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);Promise.resolve().then(() => (init_wish_sync(),exports_wish_sync)).then((ws)=>ws.syncWishes(process.cwd())).catch(()=>{});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 state=await getOrCreateState(slug,groups),nextWave=waves.find((wave)=>wave.groups.some((g)=>{let gs=state?.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(`
|
|
3268
|
-
\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){validateSlug(slug);let draftPath=join48(process.cwd(),".genie","brainstorms",slug,"DRAFT.md");if(!existsSync39(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",
|
|
3277
|
+
\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){validateSlug(slug);let draftPath=join48(process.cwd(),".genie","brainstorms",slug,"DRAFT.md");if(!existsSync39(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",extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:brainstormPrompt});let repoPath=process.cwd(),result2=await sendMessage2(repoPath,"cli",agentName,brainstormPrompt);if(!result2.delivered)console.warn(`\u26A0 Backup delivery to ${agentName} failed: ${result2.reason??"unknown"}`)}async function wishCommand(agentName,slug){validateSlug(slug);let designPath=join48(process.cwd(),".genie","brainstorms",slug,"DESIGN.md");if(!existsSync39(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",extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:wishPrompt});let repoPath=process.cwd(),result2=await sendMessage2(repoPath,"cli",agentName,wishPrompt);if(!result2.delivered)console.warn(`\u26A0 Backup delivery to ${agentName} failed: ${result2.reason??"unknown"}`)}async function workDispatchCommand(agentName,ref){let{slug,group}=parseRef(ref);validateSlug(slug);let wishPath=join48(process.cwd(),".genie","wishes",slug,"WISH.md");if(!existsSync39(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),enrichedContext;try{let{enrichContext:enrichContext2}=await Promise.resolve().then(() => (init_context_enrichment(),exports_context_enrichment));enrichedContext=enrichContext2({query:`${slug} ${group}: ${groupSection.slice(0,500)}`})||void 0}catch{}let context=buildContextPrompt({filePath:wishPath,sectionContent:groupSection,wishContext,command:`work ${ref}`,skill:"work",enrichedContext}),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}`,leaderTarget=await resolveLeaderTarget(),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.
|
|
3269
3278
|
|
|
3270
3279
|
When done:
|
|
3271
3280
|
1. Run: genie done ${slug}#${group}
|
|
3272
|
-
2. Run: genie send 'Group ${group} complete. <summary>' --to ${leaderTarget}`;await handleWorkerSpawn(agentName,{provider:"claude",
|
|
3281
|
+
2. Run: genie send 'Group ${group} complete. <summary>' --to ${leaderTarget}`;await handleWorkerSpawn(agentName,{provider:"claude",role:effectiveRole,extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:workPrompt});let repoPath=process.cwd(),result2=await sendMessage2(repoPath,"cli",effectiveRole,workPrompt);if(!result2.delivered)console.warn(`\u26A0 Backup delivery to ${effectiveRole} failed: ${result2.reason??"unknown"}`)}async function reviewCommand(agentName,ref){let{slug,group}=parseRef(ref);validateSlug(slug);let wishPath=join48(process.cwd(),".genie","wishes",slug,"WISH.md");if(!existsSync39(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
|
|
3273
3282
|
${diff}
|
|
3274
3283
|
\`\`\``:"(no uncommitted changes found \u2014 review committed changes)"].join(`
|
|
3275
3284
|
`),enrichedReviewContext;try{let{enrichContext:enrichContext2}=await Promise.resolve().then(() => (init_context_enrichment(),exports_context_enrichment));enrichedReviewContext=enrichContext2({query:`review ${slug} ${group}: ${groupSection.slice(0,500)}`})||void 0}catch{}let context=buildContextPrompt({filePath:wishPath,sectionContent:reviewContent,wishContext,command:`review ${ref}`,skill:"review",enrichedContext:enrichedReviewContext}),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(`
|
|
3276
3285
|
`).length} lines`);let reviewLeaderTarget=await resolveLeaderTarget(),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.
|
|
3277
3286
|
|
|
3278
3287
|
When done, report your verdict:
|
|
3279
|
-
Run: genie send '<SHIP|FIX-FIRST|BLOCKED> \u2014 <summary>' --to ${reviewLeaderTarget}`;await handleWorkerSpawn(agentName,{provider:"claude",
|
|
3288
|
+
Run: genie send '<SHIP|FIX-FIRST|BLOCKED> \u2014 <summary>' --to ${reviewLeaderTarget}`;await handleWorkerSpawn(agentName,{provider:"claude",extraArgs:["--append-system-prompt-file",contextFile],initialPrompt:reviewPrompt});let repoPath=process.cwd(),result2=await sendMessage2(repoPath,"cli",agentName,reviewPrompt);if(!result2.delivered)console.warn(`\u26A0 Backup delivery to ${agentName} failed: ${result2.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_export_format();import{existsSync as existsSync40,mkdirSync as mkdirSync17,writeFileSync as writeFileSync19}from"fs";import{dirname as dirname10}from"path";async function getSql(){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));return getConnection2()}async function getVersion(){let{VERSION:VERSION2}=await Promise.resolve().then(() => (init_version(),exports_version));return VERSION2}async function getActorName(){let{getActor:getActor2}=await Promise.resolve().then(() => (init_audit(),exports_audit));return getActor2()}async function detectTables(sql,tables){let{filterAvailableTables:filterAvailableTables2}=await Promise.resolve().then(() => exports_table_detect);return filterAvailableTables2(sql,tables)}function outputDocument(doc,options){let json2=options.pretty?JSON.stringify(doc,null,2):JSON.stringify(doc);if(options.output){let dir=dirname10(options.output);if(!existsSync40(dir))mkdirSync17(dir,{recursive:!0});writeFileSync19(options.output,`${json2}
|
|
3280
3289
|
`);let tables=Object.keys(doc.data),rows=Object.values(doc.data).reduce((sum,arr)=>sum+arr.length,0);if(console.log(`Exported ${tables.length} tables (${rows} rows) to ${options.output}`),doc.skippedTables.length>0)console.log(`Skipped tables (not found): ${doc.skippedTables.join(", ")}`)}else console.log(json2)}function autoOutputName(){let d=new Date;return`genie-backup-${`${d.getFullYear()}${String(d.getMonth()+1).padStart(2,"0")}${String(d.getDate()).padStart(2,"0")}`}.json`}async function exportGroup(sql,group,filter){let tables=GROUP_TABLES[group],{available,skipped}=await detectTables(sql,tables),data={};for(let table of available)if(filter)data[table]=[...await sql.unsafe(`SELECT * FROM ${table} WHERE ${filter.column} = $1`,[filter.value])];else data[table]=[...await sql.unsafe(`SELECT * FROM ${table}`)];return{data,skipped}}async function exportBoards(sql,name){let tables=GROUP_TABLES.boards,{available,skipped}=await detectTables(sql,tables),data={};for(let table of available)if(name&&table==="boards")data[table]=[...await sql`SELECT * FROM boards WHERE name = ${name}`];else if(table==="task_types")data[table]=[...await sql`SELECT * FROM task_types WHERE is_builtin = false`];else data[table]=[...await sql.unsafe(`SELECT * FROM ${table}`)];return{data,skipped}}var TASK_JOIN_ALIASES={task_tags:"tt",task_actors:"ta",task_dependencies:"td",task_stage_log:"tsl"};async function resolveProjectId2(sql,projectName){let projects=await sql`SELECT id FROM projects WHERE name = ${projectName}`;if(projects.length===0)throw Error(`Project not found: ${projectName}`);return projects[0].id}function stripEphemeralFields(rows){return rows.map((r)=>{let{checkout_run_id,execution_locked_at,session_id,pane_id,...rest}=r;return rest})}async function exportTaskTable(sql,table,projectId){let alias=TASK_JOIN_ALIASES[table];if(table==="tasks"){let rows=projectId?[...await sql.unsafe("SELECT * FROM tasks WHERE project_id = $1",[projectId])]:[...await sql`SELECT * FROM tasks`];return stripEphemeralFields(rows)}if(alias&&projectId)return[...await sql.unsafe(`SELECT ${alias}.* FROM ${table} ${alias} JOIN tasks t ON ${alias}.task_id = t.id WHERE t.project_id = $1`,[projectId])];return[...await sql.unsafe(`SELECT * FROM ${table}`)]}async function exportTasks(sql,projectName){let tables=GROUP_TABLES.tasks,{available,skipped}=await detectTables(sql,tables),data={},projectId=projectName?await resolveProjectId2(sql,projectName):null;for(let table of available)data[table]=await exportTaskTable(sql,table,projectId);return{data,skipped}}async function exportSchedules(sql,name){let{available,skipped}=await detectTables(sql,["schedules"]),data={};if(available.includes("schedules"))if(name)data.schedules=[...await sql`SELECT * FROM schedules WHERE name = ${name}`];else data.schedules=[...await sql`SELECT * FROM schedules`];return{data,skipped}}async function exportTags(sql){let{available,skipped}=await detectTables(sql,["tags"]),data={};if(available.includes("tags"))data.tags=[...await sql`SELECT * FROM tags WHERE name NOT LIKE 'test-%'`];return{data,skipped}}async function exportAll(sql){let allSkipped=[],allData={};for(let group of ALL_GROUPS){let result2;switch(group){case"boards":result2=await exportBoards(sql);break;case"tasks":result2=await exportTasks(sql);break;case"tags":result2=await exportTags(sql);break;case"schedules":result2=await exportSchedules(sql);break;default:result2=await exportGroup(sql,group);break}Object.assign(allData,result2.data),allSkipped.push(...result2.skipped)}return{data:allData,skipped:allSkipped}}async function runExport(groups,type2,exportFn,options){let sql=await getSql(),[version,actor]=await Promise.all([getVersion(),getActorName()]),doc=createExportDocument(type2,groups,version,actor),{data,skipped}=await exportFn(sql);doc.data=data,doc.skippedTables=skipped,outputDocument(doc,options)}function registerExportCommands(program2){let exp=program2.command("export").description("Export genie data as JSON").option("--output <file>","Write to file instead of stdout").option("-o <file>","Alias for --output").option("--pretty","Pretty-print JSON").action(async(options)=>{try{if(!options.output)options.output=autoOutputName();await runExport([...ALL_GROUPS],"full",(sql)=>exportAll(sql),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts=(cmd)=>cmd.option("--output <file>","Write to file instead of stdout").option("--pretty","Pretty-print JSON");sharedOpts(exp.command("all").description("Full backup (all present tables)")).action(async(options)=>{try{if(!options.output)options.output=autoOutputName();await runExport([...ALL_GROUPS],"full",(sql)=>exportAll(sql),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("boards [name]").description("Export boards, templates, and task types")).action(async(name,options)=>{try{await runExport(["boards"],"partial",(sql)=>exportBoards(sql,name),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("tasks").description("Export tasks with deps, actors, and stage log").option("--project <name>","Filter by project name")).action(async(options)=>{try{await runExport(["tasks"],"partial",(sql)=>exportTasks(sql,options.project),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("tags").description("Export tags")).action(async(options)=>{try{await runExport(["tags"],"partial",(sql)=>exportTags(sql),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("projects").description("Export projects")).action(async(options)=>{try{await runExport(["projects"],"partial",(sql)=>exportGroup(sql,"projects"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("schedules [name]").description("Export schedules with run_spec")).action(async(name,options)=>{try{await runExport(["schedules"],"partial",(sql)=>exportSchedules(sql,name),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("agents").description("Export agents, templates, and checkpoints")).action(async(options)=>{try{await runExport(["agents"],"partial",(sql)=>exportGroup(sql,"agents"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("comms").description("Export conversations, messages, mailbox")).action(async(options)=>{try{await runExport(["comms"],"partial",(sql)=>exportGroup(sql,"comms"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),sharedOpts(exp.command("config").description("Export OS config (graceful skip if missing)")).action(async(options)=>{try{await runExport(["config"],"partial",(sql)=>exportGroup(sql,"config"),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_history();init_export_format();import{readFileSync as readFileSync25}from"fs";var IMPORT_LEVELS=[["schedules","sessions","projects","agent_templates","agent_checkpoints","tags","task_types","notification_preferences","os_config","golden_images","warm_pool","instances"],["triggers","boards","board_templates","agents","conversations"],["tasks","runs","messages","conversation_members","mailbox","team_chat"],["task_tags","task_actors","task_dependencies","task_stage_log","heartbeats","machine_snapshots"]],SELF_REFERENTIAL_COLUMNS={tasks:"parent_id",messages:"reply_to_id",conversations:"parent_message_id"};function getTableLevel(table){for(let i2=0;i2<IMPORT_LEVELS.length;i2++)if(IMPORT_LEVELS[i2].includes(table))return i2;return-1}function sortByImportOrder(tables){return[...tables].sort((a,b2)=>{let la=getTableLevel(a),lb=getTableLevel(b2);return(la===-1?999:la)-(lb===-1?999:lb)})}function getPrimaryKey(table){return{task_tags:["task_id","tag_id"],task_actors:["task_id","actor_type","actor_id","role"],task_dependencies:["task_id","depends_on_id"],conversation_members:["conversation_id","actor_type","actor_id"],notification_preferences:["actor_type","actor_id","channel"]}[table]??["id"]}var VALID_TABLES=new Set(Object.values(GROUP_TABLES).flat());function assertValidTable(name){if(!VALID_TABLES.has(name))throw Error(`Invalid table name: "${name}" is not in the schema whitelist`)}var VALID_COLUMN_RE=/^[a-zA-Z_][a-zA-Z0-9_]*$/;function assertValidColumnName(name){if(!VALID_COLUMN_RE.test(name))throw Error(`Invalid column name: "${name.slice(0,60)}" contains disallowed characters. Column names must match /^[a-zA-Z_][a-zA-Z0-9_]*$/.`)}async function getSql2(){let{getConnection:getConnection2}=await Promise.resolve().then(() => (init_db(),exports_db));return getConnection2()}async function getActorName2(){let{getActor:getActor2}=await Promise.resolve().then(() => (init_audit(),exports_audit));return getActor2()}async function detectTables2(sql,tables){let{filterAvailableTables:filterAvailableTables2}=await Promise.resolve().then(() => exports_table_detect);return filterAvailableTables2(sql,tables)}async function detectConflicts(sql,table,rows){if(rows.length===0)return[];assertValidTable(table);let pk=getPrimaryKey(table);if(pk.length===1){let key=pk[0],ids=rows.map((r)=>r[key]),existing=await sql.unsafe(`SELECT ${key} FROM ${table} WHERE ${key} = ANY($1)`,[ids]),existingSet=new Set(existing.map((r)=>String(r[key])));return rows.filter((r)=>existingSet.has(String(r[key])))}let conflicts=[];for(let row of rows){let conditions=pk.map((col,i2)=>`${col} = $${i2+1}`).join(" AND "),values2=pk.map((col)=>row[col]);if((await sql.unsafe(`SELECT 1 FROM ${table} WHERE ${conditions} LIMIT 1`,values2)).length>0)conflicts.push(row)}return conflicts}function prepareRow(row,table,selfRefUpdates){let selfRefCol=SELF_REFERENTIAL_COLUMNS[table],entries=Object.entries(row),columns=entries.map(([k])=>k),values2=entries.map(([,v])=>v);for(let col of columns)assertValidColumnName(col);if(selfRefCol&&row[selfRefCol]!=null){let idx=columns.indexOf(selfRefCol);if(idx!==-1){let originalSelfRef=values2[idx];values2[idx]=null;let pk=getPrimaryKey(table);selfRefUpdates.push({pk:pk.length===1?row[pk[0]]:pk.map((k)=>row[k]),value:originalSelfRef})}}return{columns,values:values2,quotedCols:columns.map((c)=>`"${c}"`).join(", "),placeholders:values2.map((_,i2)=>`$${i2+1}`).join(", ")}}async function insertOneRow(tx,table,row,prepared,mode){assertValidTable(table);let{quotedCols,placeholders,values:values2}=prepared,pk=getPrimaryKey(table);if(mode==="overwrite"){let pkCondition=pk.map((col,i2)=>`"${col}" = $${values2.length+i2+1}`).join(" AND "),pkValues=pk.map((col)=>row[col]);await tx.unsafe(`DELETE FROM ${table} WHERE ${pkCondition}`,pkValues),await tx.unsafe(`INSERT INTO ${table} (${quotedCols}) VALUES (${placeholders})`,values2)}else if(mode==="merge"){let onConflict=pk.map((c)=>`"${c}"`).join(", ");await tx.unsafe(`INSERT INTO ${table} (${quotedCols}) VALUES (${placeholders}) ON CONFLICT (${onConflict}) DO NOTHING`,values2)}else await tx.unsafe(`INSERT INTO ${table} (${quotedCols}) VALUES (${placeholders})`,values2)}async function updateSelfRefs(tx,table,updates){assertValidTable(table);let selfRefCol=SELF_REFERENTIAL_COLUMNS[table],pk=getPrimaryKey(table);if(pk.length!==1)return;for(let{pk:pkVal,value}of updates)await tx.unsafe(`UPDATE ${table} SET "${selfRefCol}" = $1 WHERE "${pk[0]}" = $2`,[value,pkVal])}async function insertRows(tx,table,rows,mode){if(rows.length===0)return 0;let selfRefUpdates=[];for(let row of rows){let prepared=prepareRow(row,table,selfRefUpdates);await insertOneRow(tx,table,row,prepared,mode)}if(selfRefUpdates.length>0)await updateSelfRefs(tx,table,selfRefUpdates);return rows.length}function parseExportFile(filePath){let raw=readFileSync25(filePath,"utf-8"),parsed;try{parsed=JSON.parse(raw)}catch{throw Error(`Invalid JSON in ${filePath}`)}let validation=validateExportDocument(parsed);if(!validation.valid)throw Error(`Invalid export document: ${validation.error}`);return validation.doc}async function filterTablesByGroup(allTables,groupFilter){if(!groupFilter||groupFilter.length===0)return allTables;let{GROUP_TABLES:GROUP_TABLES2}=await Promise.resolve().then(() => (init_export_format(),exports_export_format)),allowedTables=new Set;for(let group of groupFilter){let tables=GROUP_TABLES2[group];if(tables)for(let t of tables)allowedTables.add(t);else console.warn(`Warning: Unknown group "${group}", skipping`)}return allTables.filter((t)=>allowedTables.has(t))}async function checkConflicts(sql,tables,data){for(let table of tables){let rows=data[table];if(!rows||rows.length===0)continue;let conflicts=await detectConflicts(sql,table,rows);if(conflicts.length>0){let pk=getPrimaryKey(table),ids=conflicts.slice(0,5).map((r)=>pk.map((k)=>r[k]).join(",")).join("; ");throw Error(`Conflict in table "${table}": ${conflicts.length} existing row(s) (e.g., ${ids}). Use --merge or --overwrite to resolve.`)}}}async function runImport(filePath,mode,groupFilter){let doc=parseExportFile(filePath),tablesToImport=await filterTablesByGroup(Object.keys(doc.data),groupFilter);if(tablesToImport.length===0){console.log("No tables to import.");return}tablesToImport=sortByImportOrder(tablesToImport);let sql=await getSql2(),{available}=await detectTables2(sql,tablesToImport),skippedTables=tablesToImport.filter((t)=>!available.includes(t));if(tablesToImport=available,skippedTables.length>0)console.log(`Skipping tables not in database: ${skippedTables.join(", ")}`);if(mode==="fail")await checkConflicts(sql,tablesToImport,doc.data);let totalInserted=0,tableStats={};await sql.begin(async(tx)=>{for(let table of tablesToImport){let rows=doc.data[table];if(!rows||rows.length===0)continue;let count=await insertRows(tx,table,rows,mode);tableStats[table]=count,totalInserted+=count}});let actor=await getActorName2(),{recordAuditEvent:recordAuditEvent3}=await Promise.resolve().then(() => (init_audit(),exports_audit));await recordAuditEvent3("import",filePath,"import_complete",actor,{mode,tables:tableStats,totalRows:totalInserted,skippedTables,sourceVersion:doc.version,sourceDate:doc.exportedAt}),console.log(`Import complete: ${totalInserted} rows across ${Object.keys(tableStats).length} tables`);for(let[table,count]of Object.entries(tableStats))if(count>0)console.log(` ${table}: ${count} rows`);if(skippedTables.length>0)console.log(`Skipped (not in DB): ${skippedTables.join(", ")}`)}function registerImportCommands(program2){program2.command("import <file>").description("Import genie data from JSON export").option("--fail","Abort on any conflict (default)").option("--merge","Skip existing rows, import new ones").option("--overwrite","Replace existing rows with imported data").option("--groups <list>","Comma-separated groups to import (e.g., boards,tags)").action(async(file,options)=>{try{let mode="fail";if(options.overwrite)mode="overwrite";else if(options.merge)mode="merge";let groupFilter=options.groups?.split(",").map((g)=>g.trim());await runImport(file,mode,groupFilter)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_esm14();import{existsSync as existsSync44,mkdirSync as mkdirSync20,symlinkSync,writeFileSync as writeFileSync22}from"fs";import{basename as basename10,join as join53,relative as relative4,resolve as resolve9,sep as sep2}from"path";import{cpSync,existsSync as existsSync42,mkdirSync as mkdirSync18,renameSync as renameSync3,rmSync as rmSync4}from"fs";import{join as join50,relative as relative3}from"path";var import_ignore=__toESM(require_ignore(),1);import{existsSync as existsSync41,readFileSync as readFileSync26,readdirSync as readdirSync9,statSync as statSync3}from"fs";import{join as join49,relative as relative2}from"path";var GENIEIGNORE_DEFAULTS=`node_modules
|
|
3281
3290
|
.git
|
|
3282
3291
|
.genie/worktrees
|
|
@@ -3320,7 +3329,7 @@ Event Throughput:`),console.log(` Emitted: ${data.events_emitted}`),console.lo
|
|
|
3320
3329
|
Project: ${p.name}`),console.log("\u2500".repeat(50)),console.log(` ID: ${p.id}`),console.log(` Type: ${p.repoPath?"repo":"virtual"}`),p.repoPath)console.log(` Path: ${p.repoPath}`);if(p.description)console.log(` Desc: ${p.description}`);if(console.log(` Created: ${formatDate(p.createdAt)}`),console.log(` Tasks: ${tasks.length}`),tasks.length>0){console.log(`
|
|
3321
3330
|
By status:`);for(let[status,count]of Object.entries(byStatus).sort())console.log(` ${padRight(status,15)} ${count}`);console.log(`
|
|
3322
3331
|
By stage:`);for(let[stage,count]of Object.entries(byStage).sort())console.log(` ${padRight(stage,15)} ${count}`)}}function registerProjectCommands(program2){let project=program2.command("project").description("Project management \u2014 named task boards");project.command("list").description("List all projects").option("--all","Include archived projects").option("--json","Output as JSON").action(async(options)=>{try{let ts3=await getTaskService5(),projects=await ts3.listProjectsFiltered(options.all);if(options.json){console.log(JSON.stringify(projects,null,2));return}if(projects.length===0){console.log("No projects found. Projects are auto-created when you run `genie task create`.");return}await printProjectList(ts3,projects)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("create <name>").description("Create a new project").option("--virtual","Create a virtual project (not tied to a repo)").option("--repo <path>","Repo path for the project").option("--description <text>","Project description").action(async(name,options)=>{try{let ts3=await getTaskService5(),repoPath=options.virtual?null:options.repo??null,p=await ts3.createProject({name,repoPath,description:options.description});if(console.log(`Created project "${p.name}"`),console.log(` ID: ${p.id}`),console.log(` Type: ${p.repoPath?"repo":"virtual"}`),p.repoPath)console.log(` Path: ${p.repoPath}`);if(p.description)console.log(` Desc: ${p.description}`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("show <name>").description("Show project detail with task stats").option("--json","Output as JSON").action(async(name,options)=>{try{let ts3=await getTaskService5(),p=await ts3.getProjectByName(name);if(!p){console.error(`Error: Project not found: ${name}`),process.exit(1);return}if(options.json){console.log(JSON.stringify(p,null,2));return}let tasks=await ts3.listTasks({projectName:name,allProjects:!0});printProjectDetail(p,tasks)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("archive <name>").description("Archive a project (cascades to boards and unfinished tasks)").action(async(name)=>{try{await(await getTaskService5()).archiveProject(name),console.log(`Archived project "${name}" and cascaded to boards + unfinished tasks.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("unarchive <name>").description("Restore an archived project and its boards (tasks stay as-is)").action(async(name)=>{try{await(await getTaskService5()).unarchiveProject(name),console.log(`Unarchived project "${name}" and restored boards.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),project.command("set-default <name>").description("Set default project for when outside any repo").action(async(name)=>{try{if(!await(await getTaskService5()).getProjectByName(name))console.error(`Error: Project not found: ${name}`),process.exit(1);let{loadGenieConfig:loadGenieConfig2,saveGenieConfig:saveGenieConfig2}=await Promise.resolve().then(() => (init_genie_config2(),exports_genie_config)),config=await loadGenieConfig2();config.defaultProject=name,await saveGenieConfig2(config),console.log(`Default project set to "${name}"`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}init_agent_registry();import{readFile as readFile12,stat as stat5}from"fs/promises";import{join as join56}from"path";import{readFile as readFile10}from"fs/promises";async function parseQaSpec(filePath){let content=await readFile10(filePath,"utf-8");return parseQaSpecContent(content,filePath)}var SECTION_MAP={setup:"setup",actions:"actions",expect:"expect"};function detectSection(line){for(let[keyword,section]of Object.entries(SECTION_MAP))if(new RegExp(`^##\\s+${keyword}`,"i").test(line))return section;if(line.startsWith("## "))return"none";return null}function parseQaSpecContent(content,filePath="<inline>"){let lines=content.split(`
|
|
3323
|
-
`),name="",currentSection="none",setup=[],actions=[],expect=[],parsers2={none:()=>{},setup:(line)=>{let step=parseSetupLine(line);if(step)setup.push(step)},actions:(line)=>{let step=parseActionLine(line);if(step)actions.push(step)},expect:(line)=>{let exp=parseExpectLine(line);if(exp)expect.push(exp)}};for(let line of lines){let trimmed=line.trim();if(trimmed.startsWith("# ")&&!trimmed.startsWith("## ")){let match=trimmed.match(/^#\s+(?:Test:\s*)?(.+)$/);if(match)name=match[1].trim();continue}let section=detectSection(trimmed);if(section!==null){currentSection=section;continue}if(!trimmed||trimmed.startsWith("//")||trimmed.startsWith("<!--"))continue;parsers2[currentSection](trimmed)}if(!name)name=filePath.replace(/.*\//,"").replace(/\.md$/,"");return{name,file:filePath,setup,actions,expect}}function parseSetupLine(line){let text=stripListPrefix(line);if(!text)return null;let spawnMatch=text.match(/^spawn\s+(\S+)(?:\s+\((.+)\))?$/i);if(spawnMatch)return{kind:"spawn",target:spawnMatch[1],options:spawnMatch[2]?parseOptions2(spawnMatch[2]):{}};let followMatch=text.match(/^(?:start\s+)?follow(?:\s+(?:on\s+)?(\S+))?(?:\s+\((.+)\))?$/i);if(followMatch)return{kind:"follow",target:followMatch[1]||"team",options:followMatch[2]?parseOptions2(followMatch[2]):{}};return null}function parseActionLine(line){let text=stripListPrefix(line);if(!text)return null;let sendMatch=text.match(/^send\s+"([^"]+)"\s+to\s+(\S+)/i);if(sendMatch)return{kind:"send",message:sendMatch[1],to:sendMatch[2]};let waitMatch=text.match(/^wait\s+(?:for\s+.+?\s+)?\(?(?:max\s+)?(\d+)s\)?/i);if(waitMatch)return{kind:"wait",seconds:Number.parseInt(waitMatch[1],10)};let runMatch=text.match(/^run\s+(.+)$/i);if(runMatch)return{kind:"run",command:runMatch[1]};return null}function detectSource(text){if(/\binbox\b/i.test(text))return"inbox";if(/\blog\b/i.test(text))return"log";if(/\boutput\b/i.test(text))return"output";return"log"}function extractMatchers(text){let matchers={},matcherRegex=/(\w+)\s*([~!]?=)\s*(?:"([^"]+)"|(\S+))/g;for(let match of text.matchAll(matcherRegex)){let op=match[2]==="~="?"~":"",value=(match[3]??match[4]).trim();matchers[match[1]]=`${op}${value}`}return matchers}function parseExpectLine(line){let text=line.replace(/^[-*]\s*\[[ x]\]\s*/i,"").trim();if(!text)return null;return{description:text,source:detectSource(text),matchers:extractMatchers(text)}}function stripListPrefix(line){return line.replace(/^[-*]\s+/,"").replace(/^\d+\.\s+/,"").trim()}function parseOptions2(text){let opts={};for(let pair of text.split(",")){let[key,...rest]=pair.split(":");if(key&&rest.length>0)opts[key.trim()]=rest.join(":").trim()}return opts}import{cp as cp2,mkdir as mkdir8,rm as rm4,writeFile as writeFile5}from"fs/promises";import{tmpdir as tmpdir4}from"os";import{dirname as dirname11,join as join55,resolve as resolve11}from"path";var{$:$4}=globalThis.Bun;import{createHash as
|
|
3332
|
+
`),name="",currentSection="none",setup=[],actions=[],expect=[],parsers2={none:()=>{},setup:(line)=>{let step=parseSetupLine(line);if(step)setup.push(step)},actions:(line)=>{let step=parseActionLine(line);if(step)actions.push(step)},expect:(line)=>{let exp=parseExpectLine(line);if(exp)expect.push(exp)}};for(let line of lines){let trimmed=line.trim();if(trimmed.startsWith("# ")&&!trimmed.startsWith("## ")){let match=trimmed.match(/^#\s+(?:Test:\s*)?(.+)$/);if(match)name=match[1].trim();continue}let section=detectSection(trimmed);if(section!==null){currentSection=section;continue}if(!trimmed||trimmed.startsWith("//")||trimmed.startsWith("<!--"))continue;parsers2[currentSection](trimmed)}if(!name)name=filePath.replace(/.*\//,"").replace(/\.md$/,"");return{name,file:filePath,setup,actions,expect}}function parseSetupLine(line){let text=stripListPrefix(line);if(!text)return null;let spawnMatch=text.match(/^spawn\s+(\S+)(?:\s+\((.+)\))?$/i);if(spawnMatch)return{kind:"spawn",target:spawnMatch[1],options:spawnMatch[2]?parseOptions2(spawnMatch[2]):{}};let followMatch=text.match(/^(?:start\s+)?follow(?:\s+(?:on\s+)?(\S+))?(?:\s+\((.+)\))?$/i);if(followMatch)return{kind:"follow",target:followMatch[1]||"team",options:followMatch[2]?parseOptions2(followMatch[2]):{}};return null}function parseActionLine(line){let text=stripListPrefix(line);if(!text)return null;let sendMatch=text.match(/^send\s+"([^"]+)"\s+to\s+(\S+)/i);if(sendMatch)return{kind:"send",message:sendMatch[1],to:sendMatch[2]};let waitMatch=text.match(/^wait\s+(?:for\s+.+?\s+)?\(?(?:max\s+)?(\d+)s\)?/i);if(waitMatch)return{kind:"wait",seconds:Number.parseInt(waitMatch[1],10)};let runMatch=text.match(/^run\s+(.+)$/i);if(runMatch)return{kind:"run",command:runMatch[1]};return null}function detectSource(text){if(/\binbox\b/i.test(text))return"inbox";if(/\blog\b/i.test(text))return"log";if(/\boutput\b/i.test(text))return"output";return"log"}function extractMatchers(text){let matchers={},matcherRegex=/(\w+)\s*([~!]?=)\s*(?:"([^"]+)"|(\S+))/g;for(let match of text.matchAll(matcherRegex)){let op=match[2]==="~="?"~":"",value=(match[3]??match[4]).trim();matchers[match[1]]=`${op}${value}`}return matchers}function parseExpectLine(line){let text=line.replace(/^[-*]\s*\[[ x]\]\s*/i,"").trim();if(!text)return null;return{description:text,source:detectSource(text),matchers:extractMatchers(text)}}function stripListPrefix(line){return line.replace(/^[-*]\s+/,"").replace(/^\d+\.\s+/,"").trim()}function parseOptions2(text){let opts={};for(let pair of text.split(",")){let[key,...rest]=pair.split(":");if(key&&rest.length>0)opts[key.trim()]=rest.join(":").trim()}return opts}import{cp as cp2,mkdir as mkdir8,rm as rm4,writeFile as writeFile5}from"fs/promises";import{tmpdir as tmpdir4}from"os";import{dirname as dirname11,join as join55,resolve as resolve11}from"path";var{$:$4}=globalThis.Bun;import{createHash as createHash5}from"crypto";import{mkdir as mkdir7,readFile as readFile11,readdir as readdir6,stat as stat4,writeFile as writeFile4}from"fs/promises";import{homedir as homedir31}from"os";import{join as join54,relative as relative5,resolve as resolve10}from"path";function repoHash(repoPath){return createHash5("sha256").update(resolve10(repoPath)).digest("hex").slice(0,12)}function resultsDir(repoPath){let base=process.env.GENIE_HOME??join54(homedir31(),".genie");return join54(base,"qa",repoHash(repoPath))}function resultsPath(repoPath){return join54(resultsDir(repoPath),"results.json")}async function loadResults(repoPath){try{let raw=await readFile11(resultsPath(repoPath),"utf-8");return JSON.parse(raw)}catch{return{}}}async function saveResult(repoPath,specKey,report){let dir=resultsDir(repoPath);await mkdir7(dir,{recursive:!0});let results=await loadResults(repoPath),specHash=await hashSpecFile(report.file);results[specKey]={lastRun:new Date().toISOString(),result:report.result,durationMs:report.durationMs,specHash,expectations:report.expectations,error:report.error},await writeFile4(resultsPath(repoPath),JSON.stringify(results,null,2))}async function isStale(repoPath,specKey,specFilePath){let stored=(await loadResults(repoPath))[specKey];if(!stored)return!1;return await hashSpecFile(specFilePath)!==stored.specHash}async function listAllSpecs(specDir){let entries=[];return await walkSpecs(specDir,specDir,entries),entries.sort((a,b2)=>{if(a.domain!==b2.domain)return a.domain.localeCompare(b2.domain);return a.name.localeCompare(b2.name)})}async function hashSpecFile(filePath){try{let content=await readFile11(filePath,"utf-8");return createHash5("sha256").update(content).digest("hex").slice(0,12)}catch{return"unknown"}}async function walkSpecs(baseDir,dir,entries){let items=await readdir6(dir);for(let item of items){let fullPath=join54(dir,item);if((await stat4(fullPath)).isDirectory())await walkSpecs(baseDir,fullPath,entries);else if(item.endsWith(".md")){let rel=relative5(baseDir,fullPath),parts=rel.replace(/\.md$/,"").split("/"),domain=parts.length>1?parts.slice(0,-1).join("/"):"(root)",name=parts[parts.length-1];entries.push({key:rel.replace(/\.md$/,""),domain,name,filePath:fullPath})}}}function specKeyFromPath(specDir,filePath){return relative5(specDir,filePath).replace(/\.md$/,"")}function formatTimeAgo(isoDate){let ms=Date.now()-new Date(isoDate).getTime(),seconds=Math.floor(ms/1000);if(seconds<60)return`${seconds}s ago`;let minutes=Math.floor(seconds/60);if(minutes<60)return`${minutes}m ago`;let hours=Math.floor(minutes/60);if(hours<24)return`${hours}h ago`;return`${Math.floor(hours/24)}d ago`}init_runtime_events();init_team_manager();function emitNdjson(event){process.stdout.write(`${JSON.stringify(event)}
|
|
3324
3333
|
`)}async function publishQaEvent(repoPath,qaType,payload){let{specKey,domain,team,...rest}=payload;await publishSubjectEvent(repoPath,`genie.qa.${qaType}`,{kind:"qa",agent:"qa",team,text:`${qaType}: ${specKey}`,data:{qaType,specKey,domain,...rest},source:"hook"})}async function emitQaEvent(repoPath,qaType,payload,ndjson){if(await publishQaEvent(repoPath,qaType,payload),ndjson)emitNdjson(payload)}function parseStatusEntry(status,parts,index){if(status.startsWith("R")){let from=parts[index]??"",to=parts[index+1]??"";if(from&&to)return{op:{kind:"rename",from,to},nextIndex:index+2};return{op:null,nextIndex:index+2}}let path3=parts[index]??"";if(!path3)return{op:null,nextIndex:index+1};return{op:{kind:status.startsWith("D")?"delete":"copy",path:path3},nextIndex:index+1}}function parseNameStatusZ(output){if(!output)return[];let parts=output.split("\x00").filter(Boolean),ops=[],i2=0;while(i2<parts.length){let status=parts[i2++]??"";if(!status)break;let{op,nextIndex}=parseStatusEntry(status,parts,i2);if(i2=nextIndex,op)ops.push(op)}return ops}async function overlayDirtyWorkingTree(repoPath,worktreePath){let tracked=(await $4`git -C ${repoPath} diff --name-status --find-renames -z HEAD --`.quiet().nothrow().text()).trim(),untracked=(await $4`git -C ${repoPath} ls-files --others --exclude-standard -z`.quiet().nothrow().text()).trim(),ops=parseNameStatusZ(tracked);for(let path3 of untracked.split("\x00").filter(Boolean))ops.push({kind:"copy",path:path3});for(let op of ops){if(op.kind==="delete"){await rm4(join55(worktreePath,op.path),{recursive:!0,force:!0});continue}if(op.kind==="rename"){await rm4(join55(worktreePath,op.from),{recursive:!0,force:!0});let src2=join55(repoPath,op.to),dest2=join55(worktreePath,op.to);await mkdir8(dirname11(dest2),{recursive:!0}),await cp2(src2,dest2,{recursive:!0,force:!0});continue}let src=join55(repoPath,op.path),dest=join55(worktreePath,op.path);await mkdir8(dirname11(dest),{recursive:!0}),await cp2(src,dest,{recursive:!0,force:!0})}}async function runAllSpecs(specDir,options){let entries=await listAllSpecs(specDir);return runSpecEntries(entries,specDir,options)}async function runDomainSpecs(specDir,domain,options){let filtered=(await listAllSpecs(specDir)).filter((e)=>e.domain===domain);return runSpecEntries(filtered,specDir,options)}async function prepareTeams(entries,repoPath,ndjson){let prepared=[];console.error(`
|
|
3325
3334
|
[qa] Creating ${entries.length} teams...`);for(let entry of entries){let teamName=`qa-${Date.now().toString(36)}-${entry.name.slice(0,8)}`;try{let spec=await parseQaSpec(entry.filePath),config=await createTeam(teamName,repoPath);await overlayDirtyWorkingTree(repoPath,config.worktreePath),await hireAgent(teamName,"qa");let prompt2=buildTeamLeadPrompt(spec,teamName,repoPath),promptFile=join55(tmpdir4(),`genie-qa-${teamName}.md`);await writeFile5(promptFile,prompt2),prepared.push({entry,spec,teamName,worktreePath:config.worktreePath,promptFile}),console.error(` \u2713 ${entry.name}`),await emitQaEvent(repoPath,"team-created",{type:"qa:team-created",specKey:entry.key,domain:entry.domain,team:teamName},ndjson)}catch(err){console.error(` \u2717 ${entry.name}: ${err instanceof Error?err.message:err}`)}await new Promise((r)=>setTimeout(r,200))}return prepared}async function emitSpecDone(repoPath,specKey,domain,team,report,ndjson){await emitQaEvent(repoPath,"spec-done",{type:"qa:spec-done",specKey,domain,team,result:report.result,durationMs:report.durationMs,expectations:report.expectations,error:report.error},ndjson)}async function runSpecEntries(entries,specDir,options){let repoPath=resolve11(options?.repoPath??process.cwd()),maxConcurrency=options?.parallel??5,timeoutMs=(options?.timeout??3600)*1000,ndjson=options?.ndjson??!1,GREEN="\x1B[32m",RED="\x1B[31m",RESET2="\x1B[0m",DIM2="\x1B[90m",prepared=await prepareTeams(entries,repoPath,ndjson);if(prepared.length===0)return[];let timelineSub=await followRuntimeEvents({repoPath,teamPrefix:"qa-"},(event)=>{if(!event?.timestamp||!event?.kind)return;let team=event.team??"";if(!team.startsWith("qa-"))return;let match=prepared.find((p)=>p.teamName===team),specKey=match?.entry.key??team,specName=match?.entry.name??team,domain=match?.entry.domain??"",time=new Date(event.timestamp).toLocaleTimeString("en-US",{hour12:!1}),text=(event.text??"").slice(0,100);if(console.error(` \x1B[90m${time}\x1B[0m \x1B[90m[${event.kind}]\x1B[0m ${specName} \x1B[90m${text}\x1B[0m`),ndjson)emitNdjson({type:"qa:event",specKey,domain,team,event:{timestamp:event.timestamp,kind:event.kind,agent:event.agent??"",text:event.text??""}})},{pollIntervalMs:250});console.error(`
|
|
3326
3335
|
[qa] Running ${prepared.length} specs (max ${maxConcurrency} parallel)
|
|
@@ -3549,7 +3558,7 @@ Name: ${t.name}`),console.log(`ID: ${t.id}`),console.log(`Builti
|
|
|
3549
3558
|
${types4.length} type${types4.length===1?"":"s"}`)}function printTypePipeline(t){if(console.log(`
|
|
3550
3559
|
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(`
|
|
3551
3560
|
Stage Pipeline:`);let stages=t.stages;for(let i2=0;i2<stages.length;i2++){let s2=stages[i2],arrow=i2<stages.length-1?" \u2192":"",gate=s2.gate?` [gate: ${s2.gate}]`:"",action=s2.action?` (action: ${s2.action})`:"",auto=s2.auto_advance?" [auto]":"";console.log(` ${i2+1}. ${s2.label??s2.name}${gate}${action}${auto}${arrow}`)}console.log("")}async function handleTypeList(options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let types4=await(await getTaskService9()).listTypes();if(options.json){console.log(JSON.stringify(types4,null,2));return}printTypeTable(types4)}async function handleTypeShow(id,options){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let t=await(await getTaskService9()).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){console.warn("Warning: `genie type` is deprecated. Use `genie board` instead.");let ts3=await getTaskService9(),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 s2 of stages)if(typeof s2!=="object"||s2===null||!("name"in s2))console.error('Error: Each stage must have at least a "name" field.'),process.exit(1);let id=name.toLowerCase().replace(/\s+/g,"-"),t=await ts3.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)}})}var _T_BOOT=Date.now();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{}function parseNumericFlag2(flagName){return(value)=>{let n=Number(value);if(Number.isNaN(n))throw Error(`${flagName} must be a number, got: ${value}`);return n}}if(process.env.GENIE_PROFILE_DB)console.error(`[profile] imports=${Date.now()-_T_BOOT}ms`);var program2=new Command;program2.name("genie").description("Genie CLI - AI-assisted development").version(VERSION);program2.option("--no-interactive","Disable interactive prompts (exit 2 instead of prompting)");program2.configureHelp({sortSubcommands:!0,showGlobalOptions:!0});program2.configureOutput({outputError:(str5,write)=>{let cmd=program2.commands.find((c)=>process.argv.slice(2,6).includes(c.name())),prefix=cmd?`genie ${cmd.name()}`:"genie";write(`\x1B[31mError (${prefix}): ${str5}\x1B[0m
|
|
3552
|
-
`)}});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:spawnSync6}=await import("child_process"),result2=spawnSync6("sh",["-c",cmd],{stdio:"inherit"});if(result2.status)process.exit(result2.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").option("--fix","Auto-fix: kill zombie postgres, clean shared memory, restart daemon").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);registerServeCommands(program2);registerAppCommand(program2);registerInitCommands(program2);registerTeamNamespace(program2);registerDirNamespace(program2);registerAgentCommands(program2);registerSendInboxCommands(program2);registerStateCommands(program2);registerDispatchCommands(program2);registerHookNamespace(program2);registerDbCommands(program2);registerScheduleCommands(program2);registerDaemonCommands(program2);registerTaskCommands(program2);registerTypeCommands(program2);registerBoardCommands(program2);registerTagCommands(program2);registerReleaseCommands(program2);registerProjectCommands(program2);registerNotifyCommands(program2);registerEventsCommands(program2);registerSessionsCommands(program2);registerMetricsCommands(program2);registerExportCommands(program2);registerImportCommands(program2);registerTemplateCommands(program2);registerBrainCommands(program2);registerBriefCommands(program2);registerApprovalCommands(program2);installWorkspaceCheck(program2);var auditTimers=new Map;program2.hook("preAction",(_thisCommand,actionCommand)=>{let name=actionCommand.name();auditTimers.set(name,Date.now()),Promise.resolve().then(() => (init_db(),exports_db)).then(({isConnected:isConnected2})=>{if(!isConnected2())return;recordAuditEvent("command",name,"command_start",getActor(),{args:actionCommand.args}).catch(()=>{})}).catch(()=>{})});program2.hook("postAction",async(_thisCommand,actionCommand)=>{let name=actionCommand.name(),startMs=auditTimers.get(name),durationMs=startMs?Date.now()-startMs:void 0;auditTimers.delete(name);try{let{isConnected:isConnected2}=await Promise.resolve().then(() => (init_db(),exports_db));if(!isConnected2())return;await recordAuditEvent("command",name,"command_success",getActor(),{args:actionCommand.args,duration_ms:durationMs})}catch{}});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"
|
|
3561
|
+
`)}});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:spawnSync6}=await import("child_process"),result2=spawnSync6("sh",["-c",cmd],{stdio:"inherit"});if(result2.status)process.exit(result2.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").option("--fix","Auto-fix: kill zombie postgres, clean shared memory, restart daemon").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);registerServeCommands(program2);registerAppCommand(program2);registerInitCommands(program2);registerTeamNamespace(program2);registerDirNamespace(program2);registerAgentCommands(program2);registerSendInboxCommands(program2);registerStateCommands(program2);registerDispatchCommands(program2);registerHookNamespace(program2);registerDbCommands(program2);registerScheduleCommands(program2);registerDaemonCommands(program2);registerTaskCommands(program2);registerTypeCommands(program2);registerBoardCommands(program2);registerTagCommands(program2);registerReleaseCommands(program2);registerProjectCommands(program2);registerNotifyCommands(program2);registerEventsCommands(program2);registerSessionsCommands(program2);registerMetricsCommands(program2);registerExportCommands(program2);registerImportCommands(program2);registerTemplateCommands(program2);registerBrainCommands(program2);registerBriefCommands(program2);registerApprovalCommands(program2);installWorkspaceCheck(program2);var auditTimers=new Map;program2.hook("preAction",(_thisCommand,actionCommand)=>{let name=actionCommand.name();auditTimers.set(name,Date.now()),Promise.resolve().then(() => (init_db(),exports_db)).then(({isConnected:isConnected2})=>{if(!isConnected2())return;recordAuditEvent("command",name,"command_start",getActor(),{args:actionCommand.args}).catch(()=>{})}).catch(()=>{})});program2.hook("postAction",async(_thisCommand,actionCommand)=>{let name=actionCommand.name(),startMs=auditTimers.get(name),durationMs=startMs?Date.now()-startMs:void 0;auditTimers.delete(name);try{let{isConnected:isConnected2}=await Promise.resolve().then(() => (init_db(),exports_db));if(!isConnected2())return;await recordAuditEvent("command",name,"command_success",getActor(),{args:actionCommand.args,duration_ms:durationMs})}catch{}});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").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("--role <role>","Override role name for registration (avoids duplicate guard)").option("--new-window","Create a new tmux window instead of splitting").option("--window <target>","Tmux window to split into (e.g., genie:3)").option("--no-auto-resume","Disable auto-resume on pane death").option("--stream","Stream SDK messages to stdout in real-time (claude-sdk provider)").option("--stream-format <format>","Streaming output format: text, json, ndjson (default: text)","text").option("--sdk-max-turns <n>","SDK: max conversation turns",parseNumericFlag2("--sdk-max-turns")).option("--sdk-max-budget <usd>","SDK: max budget in USD",parseNumericFlag2("--sdk-max-budget")).option("--sdk-stream","SDK: enable streaming output (shortcut for --stream)").option("--sdk-effort <level>","SDK: reasoning effort level (low, medium, high, max)").option("--sdk-resume <session-id>","SDK: resume a previous session by ID").option("--prompt <text>","Initial prompt to send as the first user message").addHelpText("after",`
|
|
3553
3562
|
Examples:
|
|
3554
3563
|
genie spawn engineer # Spawn built-in engineer role
|
|
3555
3564
|
genie spawn researcher --model sonnet # Spawn with model override
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.260416.
|
|
3
|
+
"version": "4.260416.3",
|
|
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"
|