@automagik/genie 4.260418.20 → 4.260419.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/genie.js
CHANGED
|
@@ -303,7 +303,7 @@ Next steps:`),console.log(" 1. Reload tmux: tmux source ~/.tmux.conf"),console.
|
|
|
303
303
|
DELETE FROM audit_events WHERE entity_type LIKE 'otel_%' AND created_at < now() - interval '30 days';
|
|
304
304
|
DELETE FROM genie_runtime_events WHERE created_at < now() - interval '14 days';
|
|
305
305
|
`),retentionRan=!0}catch(retErr){retentionRan=!0;let msg=retErr instanceof Error?retErr.message:String(retErr);process.stderr.write(`[genie] retention cleanup warning: ${msg}
|
|
306
|
-
`)}}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=join15(GENIE_HOME2,pidName);try{let pidStr=readFileSync11(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"){mkdirSync8(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(existsSync14(resolved))return resolved}catch{}let globalBin=join15(homedir14(),".bun","bin","pgserve");if(existsSync14(globalBin))return globalBin;try{return execSync4("which pgserve",{encoding:"utf-8",timeout:3000}).trim()}catch{return"pgserve"}}async function startPgserveOnPort(port){mkdirSync8(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{
|
|
306
|
+
`)}}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=join15(GENIE_HOME2,pidName);try{let pidStr=readFileSync11(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"){mkdirSync8(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(existsSync14(resolved))return resolved}catch{}let globalBin=join15(homedir14(),".bun","bin","pgserve");if(existsSync14(globalBin))return globalBin;try{return execSync4("which pgserve",{encoding:"utf-8",timeout:3000}).trim()}catch{return"pgserve"}}async function startPgserveOnPort(port){mkdirSync8(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{let dying=sqlClient;return sqlClient=null,activePort=null,dying.end({timeout:5}).catch(()=>{}),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;if(buildPromise)return buildPromise;buildPromise=_buildConnection();try{return await buildPromise}finally{buildPromise=null}}async function _buildConnection(){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){let dying=sqlClient;throw sqlClient=null,activePort=null,dying?.end({timeout:2}).catch(()=>{}),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,buildPromise=null,ownsLockfile=!1,exitHandlerRegistered=!1,retentionRan=!1;var init_db=__esm(()=>{init_db_migrations();init_pg_seed();GENIE_HOME2=process.env.GENIE_HOME??join15(homedir14(),".genie"),DATA_DIR=join15(GENIE_HOME2,"data","pgserve"),LOCKFILE_PATH=join15(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`
|
|
307
307
|
INSERT INTO audit_events (entity_type, entity_id, event_type, actor, details)
|
|
308
308
|
VALUES (${entityType}, ${entityId}, ${eventType}, ${actor??null}, ${sql.json(details??{})})
|
|
309
309
|
`}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
|
|
@@ -3240,7 +3240,8 @@ Columns:`);for(let i2=0;i2<sorted.length;i2++){let c=sorted[i2],count=countByCol
|
|
|
3240
3240
|
`),console.log(`Active board set to "${board.name}" (${board.id})`)}async function handleBoardExport(name,options){let bs=await getBoardService(),board=await resolveBoard(name,options.project),exported=await bs.exportBoard(board.id),json2=JSON.stringify(exported,null,2);if(options.output){let dir=dirname7(options.output);if(!existsSync32(dir))mkdirSync14(dir,{recursive:!0});writeFileSync16(options.output,`${json2}
|
|
3241
3241
|
`),console.log(`Exported board "${board.name}" to ${options.output}`)}else console.log(json2)}async function handleBoardReconcile(name,options){let{reconcileBoard:reconcileBoard2}=await Promise.resolve().then(() => (init_board_service(),exports_board_service)),board=await resolveBoard(name,options.project),result2=await reconcileBoard2(board.id);if(options.json){console.log(JSON.stringify(result2,null,2));return}if(result2.fixed===0&&result2.orphaned===0){console.log(`Board "${board.name}": all tasks have valid column_ids.`);return}if(console.log(`Board "${board.name}" reconciliation:`),console.log(` Fixed: ${result2.fixed} task${result2.fixed===1?"":"s"}`),result2.orphaned>0){let count=result2.orphaned;console.log(` Still orphaned: ${count} task${count===1?"":"s"} (stage doesn't match any column)`)}}async function handleBoardImport(options){let bs=await getBoardService(),projectId=await resolveProjectId(options.project),raw=readFileSync21(options.json,"utf-8"),data=JSON.parse(raw),board=await bs.importBoard(data,projectId);console.log(`Imported board "${board.name}" (${board.id}) with ${board.columns.length} columns`)}async function handleTemplateList(options){let templates=await(await getTemplateService()).listTemplates();if(options.json){console.log(JSON.stringify(templates,null,2));return}printTemplateTable(templates)}async function handleTemplateShow(name,options){let template=await(await getTemplateService()).getTemplate(name);if(!template)throw Error(`Template not found: ${name}`);if(options.json){console.log(JSON.stringify(template,null,2));return}if(console.log(`
|
|
3242
3242
|
Template: ${template.name} (${template.id})`),template.description)console.log(`Description: ${template.description}`);if(template.icon)console.log(`Icon: ${template.icon}`);console.log(`Built-in: ${template.isBuiltin?"yes":"no"}`),printColumnPipeline(template.columns,`Pipeline (${template.columns.length} columns)`)}async function handleTemplateCreate(name,options){let tmpl=await getTemplateService();if(options.fromBoard){let board=await(await getBoardService()).getBoard(options.fromBoard);if(!board)throw Error(`Board not found: ${options.fromBoard}`);let template2=await tmpl.snapshotFromBoard(board.id,name);console.log(`Created template "${template2.name}" (${template2.id}) from board "${board.name}" with ${template2.columns.length} columns`);return}let columns;if(options.columns)columns=options.columns.split(",").map((colName,i2)=>({id:crypto.randomUUID(),name:colName.trim(),label:colName.trim(),gate:"human",action:null,auto_advance:!1,transitions:[],roles:["*"],color:"#94a3b8",parallel:!1,on_fail:null,position:i2}));let template=await tmpl.createTemplate({name,description:options.description,columns});console.log(`Created template "${template.name}" (${template.id}) with ${template.columns.length} columns`)}async function handleTemplateEdit(name,options){let tmpl=await getTemplateService(),template=await tmpl.getTemplate(name);if(!template)throw Error(`Template not found: ${name}`);if(!options.column)console.error("Error: --column is required for template edit."),process.exit(1);let updates={};if(options.gate)updates.gate=options.gate;if(options.action)updates.action=options.action;if(options.color)updates.color=options.color;if(options.rename)updates.name=options.rename,updates.label=options.rename;if(!await tmpl.updateTemplateColumn(template.id,options.column,updates))throw Error(`Failed to update template: ${name}`);console.log(`Updated column "${options.column}" on template "${template.name}".`)}async function handleTemplateRename(oldName,newName){let tmpl=await getTemplateService(),template=await tmpl.getTemplate(oldName);if(!template)throw Error(`Template not found: ${oldName}`);let updated=await tmpl.renameTemplate(template.id,newName);if(!updated)throw Error(`Failed to rename template: ${oldName}`);console.log(`Renamed template "${oldName}" to "${updated.name}".`)}async function handleTemplateDelete(name){let tmpl=await getTemplateService(),template=await tmpl.getTemplate(name);if(!template)throw Error(`Template not found: ${name}`);if(!await tmpl.deleteTemplate(template.id))throw Error(`Failed to delete template: ${name}`);console.log(`Deleted template "${template.name}" (${template.id}).`)}function registerBoardCommands(program2){let board=program2.command("board").description("Board and pipeline management");board.command("create <name>").description("Create a new board").option("--project <project>","Project name").option("--from <template>","Create from template name").option("--columns <columns>","Comma-separated column names").option("--description <text>","Board description").action(async(name,options)=>{try{await handleBoardCreate(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("list").description("List all boards").option("--project <project>","Filter by project").option("--all","Include archived boards").option("--json","Output as JSON").action(async(options)=>{try{await handleBoardList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("show <name...>").description("Show board detail").option("--project <project>","Disambiguate by project").option("--json","Output as JSON").action(async(nameParts,options)=>{try{await handleBoardShow(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("edit <name...>").description("Edit board or column properties").option("--project <project>","Disambiguate by project").option("--column <col>","Column name to edit").option("--gate <gate>","New gate value (human|agent|human+agent)").option("--action <action>","New action skill").option("--color <color>","New color hex").option("--rename <new>","Rename the column").option("--name <new>","Rename the board itself").option("--description <text>","Update description").action(async(nameParts,options)=>{try{await handleBoardEdit(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("delete <name...>").description("Delete a board").option("--project <project>","Disambiguate by project").option("--force","Skip confirmation").action(async(nameParts,options)=>{try{await handleBoardDelete(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("columns <name...>").description("Show board column pipeline").option("--project <project>","Disambiguate by project").option("--json","Output as JSON").action(async(nameParts,options)=>{try{await handleBoardColumns(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("use <name...>").description("Set active board for current repo").option("--project <project>","Disambiguate by project").action(async(nameParts,options)=>{try{await handleBoardUse(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("export <name...>").description("Export board as JSON").option("--project <project>","Disambiguate by project").option("--output <file>","Write to file instead of stdout").option("--json","Output as JSON (default, accepted for consistency)").action(async(nameParts,options)=>{try{await handleBoardExport(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("reconcile <name...>").description("Fix orphaned column_ids by matching task stage to board columns").option("--project <project>","Disambiguate by project").option("--json","Output as JSON").action(async(nameParts,options)=>{try{await handleBoardReconcile(nameParts.join(" "),options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("archive <name...>").description("Archive a board and its unfinished tasks").option("--project <project>","Disambiguate by project").action(async(nameParts,options)=>{try{let ts3=await getTaskService3(),board2=await resolveBoard(nameParts.join(" "),options.project);await ts3.archiveBoard(board2.id),console.log(`Archived board "${board2.name}" and its unfinished tasks.`)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),board.command("import").description("Import board from JSON file").requiredOption("--json <file>","JSON file to import").requiredOption("--project <project>","Target project").action(async(options)=>{try{await handleBoardImport(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}});let template=board.command("template").description("Board template management");template.command("list").description("List all board templates").option("--json","Output as JSON").action(async(options)=>{try{await handleTemplateList(options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("show <name>").description("Show template detail with pipeline view").option("--json","Output as JSON").action(async(name,options)=>{try{await handleTemplateShow(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("create <name>").description("Create a board template").option("--from-board <board>","Create from existing board").option("--columns <columns>","Comma-separated column names").option("--description <text>","Template description").action(async(name,options)=>{try{await handleTemplateCreate(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("edit <name>").description("Edit a template column").option("--column <col>","Column name to edit").option("--gate <gate>","New gate value (human|agent|human+agent)").option("--action <action>","New action skill").option("--rename <new>","Rename the column").option("--color <color>","New color hex").action(async(name,options)=>{try{await handleTemplateEdit(name,options)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("rename <old> <new>").description("Rename a template").action(async(oldName,newName)=>{try{await handleTemplateRename(oldName,newName)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}}),template.command("delete <name>").description("Delete a template").action(async(name)=>{try{await handleTemplateDelete(name)}catch(error2){console.error(`Error: ${error2 instanceof Error?error2.message:String(error2)}`),process.exit(1)}})}import{execSync as execSync11}from"child_process";import{existsSync as existsSync33,mkdirSync as mkdirSync15,readFileSync as readFileSync22,realpathSync as realpathSync4,writeFileSync as writeFileSync17}from"fs";import{homedir as homedir27}from"os";import{dirname as dirname8,join as join42,resolve as resolve6}from"path";var BRAIN_PKG2="@khal-os/brain",BRAIN_REPO="khal-os/brain";function resolveGenieRoot(){try{let scriptDir=dirname8(realpathSync4(process.argv[1])),candidates=[resolve6(scriptDir,".."),resolve6(scriptDir,"..","..")];for(let c of candidates)if(existsSync33(join42(c,"package.json")))return c}catch{}return resolve6(import.meta.dir,"..","..")}var BRAIN_DIR2=join42(resolveGenieRoot(),"node_modules","@khal-os","brain"),CACHE_PATH=join42(homedir27(),".genie","brain-version-check.json");function compareVersions(a,b2){let partsA=a.split(".").map(Number),partsB=b2.split(".").map(Number);for(let i2=0;i2<Math.max(partsA.length,partsB.length);i2++){let diff=(partsA[i2]??0)-(partsB[i2]??0);if(diff!==0)return diff}return 0}function readLocalBrainVersion(){try{return JSON.parse(readFileSync22(join42(BRAIN_DIR2,"package.json"),"utf-8")).version??"unknown"}catch{return}}function resolveBrainBin(){let candidates=[join42(resolveGenieRoot(),"node_modules",".bin","brain"),join42(BRAIN_DIR2,"dist","cli.js")];for(let c of candidates)if(existsSync33(c))return c;return}function findBrainVault(){let candidates=[process.cwd(),join42(process.cwd(),"brain"),join42(homedir27(),"brain")];for(let dir of candidates)if(existsSync33(join42(dir,"brain.json")))return dir;return null}function readActiveBrainConfig(){try{let configPath2=join42(homedir27(),".brain","config.json");if(!existsSync33(configPath2))return null;let data=JSON.parse(readFileSync22(configPath2,"utf-8"));if(!data.pid)return null;return{pid:data.pid,pgPort:data.pgPort,brainPath:data.brainPath}}catch{return null}}function isProcessAlive2(pid){try{return process.kill(pid,0),!0}catch{return!1}}async function stopBrainDaemon(){let config=readActiveBrainConfig();if(!config?.pid||!isProcessAlive2(config.pid))return!1;let brainBin=resolveBrainBin(),brainPath=config.brainPath;if(brainBin)try{let pathArg=brainPath?` --brain-path "${brainPath}"`:"";execSync11(`"${brainBin}" serve stop${pathArg}`,{stdio:"pipe",timeout:1e4})}catch{}for(let i2=0;i2<25;i2++){if(!isProcessAlive2(config.pid))return!0;await new Promise((r)=>setTimeout(r,200))}try{process.kill(config.pid,"SIGKILL")}catch{}return!0}function startBrainDaemon(vaultPath,extraArgs){let bin=resolveBrainBin();if(!bin)return;try{let argsStr=extraArgs?.length?` ${extraArgs.join(" ")}`:"";execSync11(`"${bin}" serve --daemon --brain-path "${vaultPath}"${argsStr}`,{stdio:"inherit",timeout:15000}),console.log(" Brain daemon started.")}catch{console.log(" Daemon failed to start. Run: brain serve --daemon")}}function readSavedDaemonArgs(brainPath){try{let serverJsonPath=join42(brainPath,".brain-server.json");return JSON.parse(readFileSync22(serverJsonPath,"utf-8")).args}catch{return}}function checkForUpdates(cachePath){try{let p=cachePath??CACHE_PATH;if(!existsSync33(p))return{updateAvailable:!1};let cache=JSON.parse(readFileSync22(p,"utf-8"));if(cache.updateAvailable&&cache.latestVersion)return{updateAvailable:!0,latestVersion:cache.latestVersion};return{updateAvailable:!1}}catch{return{updateAvailable:!1}}}function refreshVersionCache(localVersion){try{let version=localVersion??readLocalBrainVersion();if(!version)return;let latestTag=execSync11(`gh release view --repo ${BRAIN_REPO} --json tagName -q .tagName`,{stdio:"pipe",encoding:"utf-8"}).trim(),latestVersion=latestTag.replace(/^v/,""),localCore=version.replace(/^\d+\./,""),latestCore=latestVersion.replace(/^\d+\./,""),updateAvailable=compareVersions(latestCore,localCore)>0,cacheDir=join42(homedir27(),".genie");mkdirSync15(cacheDir,{recursive:!0}),writeFileSync17(CACHE_PATH,JSON.stringify({checkedAt:new Date().toISOString(),localVersion:version,latestTag,latestVersion,updateAvailable},null,2))}catch{}}async function updateBrain(){if(!existsSync33(join42(BRAIN_DIR2,"package.json")))return console.log(" Brain is not installed. Run: genie brain install"),!1;let oldVersion=readLocalBrainVersion()??"unknown";console.log(" Checking for updates...");let tag;try{tag=execSync11(`gh release view --repo ${BRAIN_REPO} --json tagName -q .tagName`,{stdio:"pipe",encoding:"utf-8"}).trim()}catch{return console.error(" Failed to check latest release. Ensure: gh auth login"),!1}let newVersion=tag.replace(/^v/,"");if(compareVersions(newVersion,oldVersion)<=0)return console.log(` Already at latest version (${oldVersion}).`),!0;console.log(` Upgrading: ${oldVersion} \u2192 ${newVersion}`),console.log("");let activeConfig=readActiveBrainConfig(),savedArgs=activeConfig?.brainPath?readSavedDaemonArgs(activeConfig.brainPath):void 0,wasRunning=activeConfig?await stopBrainDaemon():!1,tmpDir=join42(homedir27(),".cache","genie-brain");mkdirSync15(tmpDir,{recursive:!0}),execSync11(`gh release download ${tag} --repo ${BRAIN_REPO} --pattern '*.tgz' --dir "${tmpDir}" --clobber`,{stdio:"inherit"}),execSync11(`rm -rf "${BRAIN_DIR2}"`,{stdio:"pipe"}),mkdirSync15(BRAIN_DIR2,{recursive:!0}),execSync11(`tar xzf "${tmpDir}/khal-os-brain-${newVersion}.tgz" -C "${BRAIN_DIR2}" --strip-components=1`,{stdio:"inherit"}),execSync11("bun install",{cwd:BRAIN_DIR2,stdio:"inherit"}),console.log(`
|
|
3243
|
-
Updated: ${oldVersion} \u2192 ${newVersion}`);try{let migrateScript=`const b = require('${BRAIN_PKG2}'); if (b.runAllMigrations) b.runAllMigrations().then(() => process.exit(0)).catch((e) => { console.error(e); process.exit(1); }); else process.exit(0);`;execSync11(`bun -e "${migrateScript}"`,{cwd:BRAIN_DIR2,stdio:"inherit"}),console.log(" Migrations applied.")}catch{console.log(" Migration skipped. Run: genie brain migrate")}if(refreshVersionCache(newVersion),wasRunning&&activeConfig?.brainPath)startBrainDaemon(activeConfig.brainPath,savedArgs);return!0}async function showVersion(){let localVersion="not installed";try{let brain=await import(BRAIN_PKG2);localVersion=brain.getVersion?.()??brain.VERSION??"unknown"}catch(err){let msg=err instanceof Error?err.message:String(err);if(isModuleNotFound(msg)){console.log(" Brain is not installed. Run: genie brain install");return}}console.log(` Local: ${localVersion}`),refreshVersionCache(localVersion);let check2=checkForUpdates();if(check2.updateAvailable&&check2.latestVersion)console.log(` Latest: ${check2.latestVersion}`),console.log(""),console.log(" Update available. Run: genie brain upgrade");else console.log(" Status: up to date")}async function installBrain(){console.log(""),console.log(" Installing brain from GitHub release (enterprise)..."),console.log(""),console.log(" Source: https://github.com/khal-os/brain"),console.log(" Requires: GitHub org membership (khal-os)"),console.log("");try{try{execSync11("gh auth token",{stdio:"pipe"})}catch{return console.error(" GitHub CLI not authenticated. Run: gh auth login"),!1}let tag=execSync11(`gh release view --repo ${BRAIN_REPO} --json tagName -q .tagName`,{stdio:"pipe",encoding:"utf-8"}).trim(),version=tag.replace(/^v/,"");console.log(` Latest release: ${tag}`),console.log("");let root=resolveGenieRoot(),brainDir=join42(root,"node_modules","@khal-os","brain"),tmpDir=join42(homedir27(),".cache","genie-brain");mkdirSync15(tmpDir,{recursive:!0}),execSync11(`gh release download ${tag} --repo ${BRAIN_REPO} --pattern '*.tgz' --dir "${tmpDir}" --clobber`,{stdio:"inherit"}),execSync11(`rm -rf "${brainDir}"`,{stdio:"pipe"}),mkdirSync15(brainDir,{recursive:!0}),execSync11(`tar xzf "${tmpDir}/khal-os-brain-${version}.tgz" -C "${brainDir}" --strip-components=1`,{stdio:"inherit"}),execSync11("bun install",{cwd:brainDir,stdio:"inherit"}),console.log(""),console.log(` Brain ${version} installed from GitHub release.`),console.log("");try{let brain=await import(BRAIN_PKG2);if(brain.runAllMigrations)console.log(" Running brain migrations..."),await brain.runAllMigrations(),console.log(" Brain tables created in Postgres.")}catch{console.log(" Auto-migration skipped. Run: genie brain migrate")}let vaultPath=findBrainVault();if(vaultPath)startBrainDaemon(vaultPath);else console.log(" No brain vault found. Create one with: brain init --name <name> --path <path>");return console.log(""),console.log(" Get started:"),console.log(" genie brain init --name my-brain --path ./brain"),console.log(""),!0}catch(err){let msg=err instanceof Error?err.message:String(err);if(msg.includes("Authentication")||msg.includes("permission")||msg.includes("404"))console.error(" Access denied. Brain is enterprise-only."),console.log(""),console.log(" You need:"),console.log(" 1. Membership in the khal-os GitHub org"),console.log(" 2. GitHub CLI authenticated: gh auth login"),console.log(""),console.log(" Manual install:"),console.log(` gh release download --repo ${BRAIN_REPO} --pattern '*.tgz'`),console.log(" tar xzf khal-os-brain-*.tgz -C node_modules/@khal-os/brain --strip-components=1"),console.log("");else console.error(` Install failed: ${msg}`),console.log(""),console.log(" Manual install:"),console.log(` gh release download --repo ${BRAIN_REPO} --pattern '*.tgz'`),console.log(" tar xzf khal-os-brain-*.tgz -C node_modules/@khal-os/brain --strip-components=1"),console.log("");return!1}}async function
|
|
3243
|
+
Updated: ${oldVersion} \u2192 ${newVersion}`);try{let migrateScript=`const b = require('${BRAIN_PKG2}'); if (b.runAllMigrations) b.runAllMigrations().then(() => process.exit(0)).catch((e) => { console.error(e); process.exit(1); }); else process.exit(0);`;execSync11(`bun -e "${migrateScript}"`,{cwd:BRAIN_DIR2,stdio:"inherit"}),console.log(" Migrations applied.")}catch{console.log(" Migration skipped. Run: genie brain migrate")}if(refreshVersionCache(newVersion),wasRunning&&activeConfig?.brainPath)startBrainDaemon(activeConfig.brainPath,savedArgs);return!0}async function showVersion(){let localVersion="not installed";try{let brain=await import(BRAIN_PKG2);localVersion=brain.getVersion?.()??brain.VERSION??"unknown"}catch(err){let msg=err instanceof Error?err.message:String(err);if(isModuleNotFound(msg)){console.log(" Brain is not installed. Run: genie brain install");return}}console.log(` Local: ${localVersion}`),refreshVersionCache(localVersion);let check2=checkForUpdates();if(check2.updateAvailable&&check2.latestVersion)console.log(` Latest: ${check2.latestVersion}`),console.log(""),console.log(" Update available. Run: genie brain upgrade");else console.log(" Status: up to date")}async function installBrain(){console.log(""),console.log(" Installing brain from GitHub release (enterprise)..."),console.log(""),console.log(" Source: https://github.com/khal-os/brain"),console.log(" Requires: GitHub org membership (khal-os)"),console.log("");try{try{execSync11("gh auth token",{stdio:"pipe"})}catch{return console.error(" GitHub CLI not authenticated. Run: gh auth login"),!1}let tag=execSync11(`gh release view --repo ${BRAIN_REPO} --json tagName -q .tagName`,{stdio:"pipe",encoding:"utf-8"}).trim(),version=tag.replace(/^v/,"");console.log(` Latest release: ${tag}`),console.log("");let root=resolveGenieRoot(),brainDir=join42(root,"node_modules","@khal-os","brain"),tmpDir=join42(homedir27(),".cache","genie-brain");mkdirSync15(tmpDir,{recursive:!0}),execSync11(`gh release download ${tag} --repo ${BRAIN_REPO} --pattern '*.tgz' --dir "${tmpDir}" --clobber`,{stdio:"inherit"}),execSync11(`rm -rf "${brainDir}"`,{stdio:"pipe"}),mkdirSync15(brainDir,{recursive:!0}),execSync11(`tar xzf "${tmpDir}/khal-os-brain-${version}.tgz" -C "${brainDir}" --strip-components=1`,{stdio:"inherit"}),execSync11("bun install",{cwd:brainDir,stdio:"inherit"}),console.log(""),console.log(` Brain ${version} installed from GitHub release.`),console.log("");try{let brain=await import(BRAIN_PKG2);if(brain.runAllMigrations)console.log(" Running brain migrations..."),await brain.runAllMigrations(),console.log(" Brain tables created in Postgres.")}catch{console.log(" Auto-migration skipped. Run: genie brain migrate")}await runBrainInstallWizard();let vaultPath=findBrainVault();if(vaultPath)startBrainDaemon(vaultPath);else console.log(" No brain vault found. Create one with: brain init --name <name> --path <path>");return console.log(""),console.log(" Get started:"),console.log(" genie brain init --name my-brain --path ./brain"),console.log(""),!0}catch(err){let msg=err instanceof Error?err.message:String(err);if(msg.includes("Authentication")||msg.includes("permission")||msg.includes("404"))console.error(" Access denied. Brain is enterprise-only."),console.log(""),console.log(" You need:"),console.log(" 1. Membership in the khal-os GitHub org"),console.log(" 2. GitHub CLI authenticated: gh auth login"),console.log(""),console.log(" Manual install:"),console.log(` gh release download --repo ${BRAIN_REPO} --pattern '*.tgz'`),console.log(" tar xzf khal-os-brain-*.tgz -C node_modules/@khal-os/brain --strip-components=1"),console.log("");else console.error(` Install failed: ${msg}`),console.log(""),console.log(" Manual install:"),console.log(` gh release download --repo ${BRAIN_REPO} --pattern '*.tgz'`),console.log(" tar xzf khal-os-brain-*.tgz -C node_modules/@khal-os/brain --strip-components=1"),console.log("");return!1}}async function runBrainInstallWizard(){try{let brain=await import(BRAIN_PKG2);if(brain.execute)console.log(""),console.log(" Running brain install wizard (rlmx + smoke test)..."),await brain.execute(["install","--apply","--yes"])}catch(err){let msg=err instanceof Error?err.message:String(err);console.log(` Install wizard skipped (${msg.split(`
|
|
3244
|
+
`)[0]}). Run manually: brain install --apply --yes`)}}async function uninstallBrain(){try{if(!existsSync33(BRAIN_DIR2)){console.log(" Brain is not installed.");return}await stopBrainDaemon(),execSync11(`rm -rf "${BRAIN_DIR2}"`,{stdio:"pipe"}),console.log(" Brain uninstalled.")}catch{console.error(` Uninstall failed. Manual: rm -rf ${BRAIN_DIR2}`)}}function isModuleNotFound(msg){return msg.includes("Cannot find")||msg.includes("not found")||msg.includes("MODULE_NOT_FOUND")}function printNotInstalledMessage(){console.log(""),console.log(" Brain is an enterprise knowledge graph engine."),console.log(" It is not installed."),console.log(""),console.log(" Quick install:"),console.log(""),console.log(" genie brain install"),console.log(""),console.log(" Requires GitHub org membership (khal-os)."),console.log("")}async function executeBrainCommand(args){try{let brain=await import(BRAIN_PKG2);if(brain.execute){await brain.execute(args);let check2=checkForUpdates();if(check2.updateAvailable&&check2.latestVersion)console.log(`
|
|
3244
3245
|
Update available (${check2.latestVersion}). Run: genie brain upgrade`)}else console.error("Brain module loaded but execute() not found."),console.error("Update: genie brain install")}catch(err){let msg=err instanceof Error?err.message:String(err);if(isModuleNotFound(msg))printNotInstalledMessage();else console.error(`Brain error: ${msg}`)}}function registerBrainCommands(program2){let brain=program2.command("brain").description("Knowledge graph engine (enterprise) \u2014 forwards unknown subcommands to @khal-os/brain").allowUnknownOption().allowExcessArguments().action(async(_options,cmd)=>{let args=cmd.args;if(args.length===0){brain.help();return}await executeBrainCommand(args)}).addHelpText("after",`
|
|
3245
3246
|
Forwarded commands (require @khal-os/brain installed):
|
|
3246
3247
|
status Show running brain server status
|
|
@@ -3322,8 +3323,9 @@ Wish: ${state.wish}`),console.log("\u2500".repeat(60));let entries=Object.entrie
|
|
|
3322
3323
|
`)}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(`
|
|
3323
3324
|
`);if(combined.length>50000)return`${combined.slice(0,50000)}
|
|
3324
3325
|
|
|
3325
|
-
... (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=join50(process.cwd(),".genie","wishes",slug,"WISH.md"),!existsSync41(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 readFile12(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)`)
|
|
3326
|
-
\u2705 Agents dispatched for ${nextWave.name} (groups: ${
|
|
3326
|
+
... (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=join50(process.cwd(),".genie","wishes",slug,"WISH.md"),!existsSync41(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 readFile12(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)`);let results=await Promise.allSettled(nextWave.groups.map(({group,agent})=>{let ref=`${slug}#${group}`;return workDispatchCommand(agent,ref)})),succeeded=[],failed=[];if(results.forEach((r,i2)=>{let groupName=nextWave.groups[i2].group;if(r.status==="fulfilled")succeeded.push(groupName);else{let reason=r.reason instanceof Error?r.reason.message:String(r.reason);failed.push({group:groupName,reason})}}),succeeded.length>0)console.log(`
|
|
3327
|
+
\u2705 Agents dispatched for ${nextWave.name} (groups: ${succeeded.join(", ")})`);if(failed.length>0){console.error(`
|
|
3328
|
+
\u274C ${failed.length} group(s) failed to dispatch in ${nextWave.name}:`);for(let{group,reason}of failed)console.error(` \u2022 Group ${group}: ${reason}`);console.error(` Check state with: genie status ${slug}`),console.error(" Some groups may have mutated state before failing \u2014 rerun genie work to retry.")}if(console.log(` Monitor: genie status ${slug}`),console.log(" Logs: genie read <agent>"),failed.length>0)process.exitCode=1}async function brainstormCommand(agentName,slug){validateSlug(slug);let draftPath=join50(process.cwd(),".genie","brainstorms",slug,"DRAFT.md");if(!existsSync41(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 readFile12(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=join50(process.cwd(),".genie","brainstorms",slug,"DESIGN.md");if(!existsSync41(designPath))console.error(`\u274C Design not found: ${designPath}`),console.error(` Run brainstorm first: genie brainstorm <agent> ${slug}`),process.exit(1);let content=await readFile12(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=join50(process.cwd(),".genie","wishes",slug,"WISH.md");if(!existsSync41(wishPath))console.error(`\u274C Wish not found: ${wishPath}`),console.error(` Create it first: genie wish <agent> ${slug}`),process.exit(1);let content=await readFile12(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.
|
|
3327
3329
|
|
|
3328
3330
|
When done:
|
|
3329
3331
|
1. Run: genie done ${slug}#${group}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.260419.2",
|
|
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"
|