@automagik/genie 4.260504.6 → 4.260504.7
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
|
@@ -981,7 +981,7 @@ ${errCtx.stack}`;d.reject(err)}else d.resolve(msg)}});return sub.requestSubject=
|
|
|
981
981
|
FROM session_sync
|
|
982
982
|
WHERE id = 'backfill'
|
|
983
983
|
LIMIT 1
|
|
984
|
-
`;if(rows.length===0)return{driftPct:null,detail:"no prior backfill row \u2014 first run will seed"};let row=rows[0];if(row.total_bytes<=0)return{driftPct:0,detail:"no JSONL bytes discovered yet"};let pct=Math.max(0,row.total_bytes-row.processed_bytes)/row.total_bytes*100,display=pct.toFixed(1);return{driftPct:pct,detail:`processed ${row.processed_bytes}/${row.total_bytes} bytes (drift ${display}%)`}}catch(err){return{driftPct:null,detail:`drift probe failed: ${err.message}`}}}async function defaultRunBackfillSync(){if(!await isAvailable())return{ranSync:!1,driftPct:null,detail:"pg unavailable \u2014 backfill skipped"};return(async()=>{try{let sql=await getConnection(),{startBackfill:startBackfill2}=await Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill));await startBackfill2(sql)}catch{}})(),{ranSync:!0,driftPct:null,detail:"background convergence kicked \u2014 `genie doctor --fix` to wait"}}async function defaultRunBackfillBlocking(){if(!await isAvailable())return{ranSync:!1,driftPct:null,detail:"pg unavailable \u2014 backfill skipped"};let sql=await getConnection(),{startBackfill:startBackfill2}=await Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill));await startBackfill2(sql);let after=await defaultMeasureBackfillDrift();return{ranSync:!0,driftPct:after.driftPct,detail:after.detail}}async function defaultListOrphanedZombies(){try{let{listExhaustedZombies:listExhaustedZombies2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return await listExhaustedZombies2()}catch{return[]}}function safeIsDirectory(path2){try{return statSync5(path2).isDirectory()}catch{return!1}}function summarizeInboxes(inboxesDir2){let inboxFiles=[];try{inboxFiles=readdirSync6(inboxesDir2).filter((f)=>f.endsWith(".json"))}catch{return{newestMs:null,hasContent:!1}}let newestMs=null,hasContent=!1;for(let f of inboxFiles)try{let st=statSync5(join24(inboxesDir2,f));if(newestMs===null||st.mtimeMs>newestMs)newestMs=st.mtimeMs;if(st.size>2)hasContent=!0}catch{}return{newestMs,hasContent}}function classifyTeamDir2(name,base,now){let dir=join24(base,name);if(!safeIsDirectory(dir))return null;if(existsSync19(join24(dir,"config.json")))return null;let inboxesDir2=join24(dir,"inboxes");if(!safeIsDirectory(inboxesDir2))return null;let{newestMs,hasContent}=summarizeInboxes(inboxesDir2),orphan={teamName:name,path:dir,newestInboxMs:newestMs,hasContent},active=hasContent&&newestMs!==null&&now-newestMs<ORPHAN_FRESH_WINDOW_MS;return{orphan,active}}function defaultScanTeamConfigOrphans(){let base=teamsBaseDir3(),result2={active:[],stale:[]};if(!existsSync19(base))return result2;let now=Date.now();for(let name of readdirSync6(base)){if(name.startsWith(".")||name==="_archive")continue;let classified=classifyTeamDir2(name,base,now);if(!classified)continue;(classified.active?result2.active:result2.stale).push(classified.orphan)}return result2}function defaultArchiveStaleTeamConfigs(orphans){if(orphans.length===0)return[];let archiveRoot=join24(teamsBaseDir3(),"_archive");mkdirSync9(archiveRoot,{recursive:!0});let ts3=new Date().toISOString().replace(/[:.]/g,"-"),archived=[];for(let o of orphans){let dest=join24(archiveRoot,`${o.teamName}-${ts3}`);try{renameSync4(o.path,dest),archived.push(dest)}catch{}}return archived}async function defaultRecordAudit(eventType,name,details){await recordAuditEvent("command","serve_start",eventType,"serve",{precondition:name,...details})}async function checkPartition(health,autoFix,deps){if(health.partition_health==="ok"||health.partition_health==="warn")return{name:"partition",status:"ok",detail:health.next_rotation_at?`next rotation: ${health.next_rotation_at}`:void 0};if(health.partition_health==="unknown")return{name:"partition",status:"skipped",detail:"pg unavailable \u2014 skipping partition probe"};if(!autoFix)return{name:"partition",status:"refused",detail:"today's partition is missing or rotation is overdue",fixCommand:"genie doctor --observability # then re-run; or `genie serve start` (without --no-fix)"};let result2=await deps.runPartitionMaintenance();return{name:"partition",status:"fixed",detail:`created/present ${result2.createdOrPresent}, dropped ${result2.dropped}; next rotation ${result2.nextRotationAt??"unknown"}`}}async function checkWatchdog(health,autoFix,deps){if(deps.platform!=="linux")return{name:"watchdog",status:"skipped",detail:"watchdog install is Linux/systemd only"};if(health.watchdog==="ok")return{name:"watchdog",status:"ok"};if(!autoFix)return{name:"watchdog",status:"refused",detail:health.watchdog_detail??"watchdog units missing",fixCommand:"sudo bun run packages/watchdog/src/cli.ts install"};try{let result2=await deps.installWatchdog();return{name:"watchdog",status:"fixed",detail:`wrote ${result2.filesWritten.length}, skipped ${result2.filesSkipped.length}`}}catch(err){return{name:"watchdog",status:"refused",detail:`auto-install failed: ${err.message}`,fixCommand:"sudo bun run packages/watchdog/src/cli.ts install"}}}async function checkBackfill(autoFix,deps){let drift=await deps.measureBackfillDrift();if(drift.driftPct===null)return{name:"backfill",status:"skipped",detail:drift.detail};if(drift.driftPct<BACKFILL_DRIFT_THRESHOLD_PCT)return{name:"backfill",status:"ok",detail:drift.detail};if(!autoFix)return{name:"backfill",status:"refused",detail:drift.detail,fixCommand:`genie sessions sync # drift ${drift.driftPct.toFixed(1)}% > ${BACKFILL_DRIFT_THRESHOLD_PCT}%`};return{name:"backfill",status:"fixed",detail:(await deps.runBackfillSync()).detail}}async function checkDeadPaneZombies(deps){let orphans=await deps.listOrphanedZombies();if(orphans.length===0)return{name:"dead_pane_zombies",status:"ok"};return{name:"dead_pane_zombies",status:"refused",detail:`${orphans.length} exhausted zombie(s) past TTL; visible in \`genie status\``,fixCommand:"genie prune --zombies # archive eligible rows"}}function checkTeamConfigOrphans(autoFix,deps){let scan=deps.scanTeamConfigOrphans();if(scan.active.length===0&&scan.stale.length===0)return{name:"team_config_orphans",status:"ok"};if(!autoFix){let summary=`active=${scan.active.length} stale=${scan.stale.length}`,firstActive=scan.active[0]?.teamName;return{name:"team_config_orphans",status:"refused",detail:summary,fixCommand:firstActive?`genie team repair ${firstActive} # active orphan; stale dirs archive on auto-fix`:"genie serve start # auto-fix archives stale orphans"}}let archivedPaths=deps.archiveStaleTeamConfigs(scan.stale);if(scan.active.length>0)return{name:"team_config_orphans",status:"refused",detail:`archived ${archivedPaths.length} stale; ${scan.active.length} active orphan(s) need repair`,fixCommand:`genie team repair ${scan.active[0].teamName}`};return{name:"team_config_orphans",status:"fixed",detail:`archived ${archivedPaths.length} stale orphan(s)`}}function bindDefaults(deps){return{collectHealth:deps?.collectHealth??collectObservabilityHealth,runPartitionMaintenance:deps?.runPartitionMaintenance??defaultRunPartitionMaintenance,installWatchdog:deps?.installWatchdog??defaultInstallWatchdog,runBackfillSync:deps?.runBackfillSync??defaultRunBackfillSync,measureBackfillDrift:deps?.measureBackfillDrift??defaultMeasureBackfillDrift,listOrphanedZombies:deps?.listOrphanedZombies??defaultListOrphanedZombies,scanTeamConfigOrphans:deps?.scanTeamConfigOrphans??defaultScanTeamConfigOrphans,archiveStaleTeamConfigs:deps?.archiveStaleTeamConfigs??defaultArchiveStaleTeamConfigs,platform:deps?.platform??process.platform,recordAudit:deps?.recordAudit??defaultRecordAudit,log:deps?.log??((line)=>console.log(line))}}async function ensureServeReady(opts){let deps=bindDefaults(opts.deps),health=await deps.collectHealth(),results=[];results.push(await checkPartition(health,opts.autoFix,deps)),results.push(await checkBackfill(opts.autoFix,deps)),results.push(await checkDeadPaneZombies(deps)),await emitAuditEvents(results,deps);let ok=results.every((r)=>r.status==="ok"||r.status==="fixed"||r.status==="skipped");return printReport(results,deps.log),{ok,results}}function formatDuration(ms){if(ms<1000)return`${ms}ms`;return`${(ms/1000).toFixed(1)}s`}function logMaintenanceStepStart(log,silent,label){if(!silent)log(` ${label}...`);return Date.now()}function logMaintenanceStepDone(log,silent,label,startedAt){if(!silent)log(` done ${label} (${formatDuration(Date.now()-startedAt)})`)}async function runDoctorMaintenance(opts={}){let rawRunBackfillSync=opts.deps?.runBackfillSync??defaultRunBackfillBlocking,deps=bindDefaults({...opts.deps,runBackfillSync:defaultRunBackfillBlocking,log:opts.silent?()=>{}:opts.deps?.log});deps.runBackfillSync=async()=>{let startedAt2=logMaintenanceStepStart(deps.log,opts.silent,"Running session backfill convergence (can take minutes on large transcript history)");try{return await rawRunBackfillSync()}finally{logMaintenanceStepDone(deps.log,opts.silent,"session backfill convergence",startedAt2)}};let startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Collecting observability health"),health=await deps.collectHealth();logMaintenanceStepDone(deps.log,opts.silent,"observability health",startedAt);let results=[];startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking runtime event partitions"),results.push(await checkPartition(health,!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"runtime event partitions",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking watchdog install"),results.push(await checkWatchdog(health,!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"watchdog install",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking session backfill drift"),results.push(await checkBackfill(!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"session backfill drift",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking exhausted zombie rows"),results.push(await checkDeadPaneZombies(deps)),logMaintenanceStepDone(deps.log,opts.silent,"exhausted zombie rows",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking Claude team config orphans"),results.push(checkTeamConfigOrphans(!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"Claude team config orphans",startedAt),await emitAuditEvents(results,deps);let ok=results.every((r)=>r.status==="ok"||r.status==="fixed"||r.status==="skipped");return printReport(results,deps.log),{ok,results}}async function emitAuditEvents(results,deps){for(let result2 of results)if(result2.status==="fixed")await deps.recordAudit("serve.precondition.fixed",result2.name,{detail:result2.detail??null}).catch(()=>{});else if(result2.status==="refused")await deps.recordAudit("serve.precondition.refused",result2.name,{detail:result2.detail??null,fix_command:result2.fixCommand??null}).catch(()=>{})}function printReport(results,log){log(" Preconditions:");for(let r of results){let tag=statusTag(r.status),suffix=r.detail?` \u2014 ${r.detail}`:"";if(log(` ${tag} ${r.name}${suffix}`),r.status==="refused"&&r.fixCommand)log(` \u2192 ${r.fixCommand}`)}}function statusTag(status){switch(status){case"ok":return"[ok]";case"fixed":return"[fix]";case"refused":return"[!!]";case"skipped":return"[--]"}}var BACKFILL_DRIFT_THRESHOLD_PCT=5,ORPHAN_FRESH_WINDOW_MS=86400000;var init_ensure_ready=__esm(()=>{init_observability_health();init_audit();init_db()});var exports_doctor={};__export(exports_doctor,{runPostUpdateMaintenance:()=>runPostUpdateMaintenance,findStaleGenieCandidates:()=>findStaleGenieCandidates,findBundledTmuxConfigDir:()=>findBundledTmuxConfigDir,doctorCommand:()=>doctorCommand,checkTmuxConfigs:()=>checkTmuxConfigs,checkLegacyAgentFrontmatter:()=>checkLegacyAgentFrontmatter,checkGenieAgentTemplate:()=>checkGenieAgentTemplate});import{execFileSync}from"child_process";import{copyFileSync as copyFileSync2,existsSync as existsSync20,mkdirSync as mkdirSync10,readFileSync as readFileSync13,readdirSync as readdirSync7,statSync as statSync6,unlinkSync as unlinkSync6,writeFileSync as writeFileSync7}from"fs";import{homedir as homedir20}from"os";import{dirname as dirname8,join as join25,resolve as resolve3}from"path";import{fileURLToPath as fileURLToPath3}from"url";var{$:$3}=globalThis.Bun;function printSectionHeader(title){console.log(),console.log(`\x1B[1m${title}:\x1B[0m`)}function printCheckResult(result2){let icon={pass:"\x1B[32m\u2713\x1B[0m",fail:"\x1B[31m\u2717\x1B[0m",warn:"\x1B[33m!\x1B[0m"}[result2.status],message=result2.message?` ${result2.message}`:"";if(console.log(` ${icon} ${result2.name}${message}`),result2.suggestion)console.log(` \x1B[2m${result2.suggestion}\x1B[0m`)}async function checkPrerequisites(){let results=[],tmuxCheck=await checkCommand("tmux");if(tmuxCheck.exists)results.push({name:"tmux",status:"pass",message:tmuxCheck.version||""});else results.push({name:"tmux",status:"fail",suggestion:"Install with: brew install tmux (or apt install tmux)"});let jqCheck=await checkCommand("jq");if(jqCheck.exists)results.push({name:"jq",status:"pass",message:jqCheck.version||""});else results.push({name:"jq",status:"fail",suggestion:"Install with: brew install jq (or apt install jq)"});let bunCheck=await checkCommand("bun");if(bunCheck.exists)results.push({name:"bun",status:"pass",message:bunCheck.version||""});else results.push({name:"bun",status:"fail",suggestion:"Install with: curl -fsSL https://bun.sh/install | bash"});let claudeCheck=await checkCommand("claude");if(claudeCheck.exists)results.push({name:"Claude Code",status:"pass",message:claudeCheck.version||""});else results.push({name:"Claude Code",status:"warn",suggestion:"Install with: npm install -g @anthropic-ai/claude-code"});let codexCheck=await checkCommand("codex");if(codexCheck.exists)results.push({name:"Codex CLI",status:"pass",message:codexCheck.version||""});else results.push({name:"Codex CLI",status:"warn",suggestion:"Install via OpenAI account; codex is optional unless using --provider codex"});let requiredForSpawn=["genie","bun","node","npm","git","claude","codex"];for(let bin of requiredForSpawn){let interactivePath=await resolveBinaryInteractive(bin),nonInteractivePath=await resolveBinaryNonInteractive(bin);if(interactivePath&&nonInteractivePath)results.push({name:`Non-interactive PATH: ${bin}`,status:"pass",message:nonInteractivePath});else if(interactivePath&&!nonInteractivePath)results.push({name:`Non-interactive PATH: ${bin}`,status:"warn",message:"interactive-only",suggestion:`Move PATH export from ~/.bashrc to ~/.profile so spawn-scripts can resolve ${bin}. (Or use a stable symlink in ~/.local/bin.)`});else if(!interactivePath&&bin!=="codex"){if(bin==="genie"||bin==="node"||bin==="npm"||bin==="git")results.push({name:`Non-interactive PATH: ${bin}`,status:"warn",message:"not in non-interactive PATH",suggestion:`Add ${bin} to ~/.profile (not just ~/.bashrc). Some flows (e.g. genie update) shell out to bare '${bin}' from non-interactive subprocesses.`})}}return results}async function resolveBinaryInteractive(bin){try{return(await $3`command -v ${bin}`.quiet().text()).trim()||null}catch{return null}}async function resolveBinaryNonInteractive(bin){try{return(await $3`sh -c "command -v ${bin}"`.quiet().text()).trim()||null}catch{return null}}async function checkConfiguration(){let results=[];if(genieConfigExists())results.push({name:"Genie config exists",status:"pass",message:contractClaudePath(getGenieConfigPath())});else results.push({name:"Genie config exists",status:"warn",message:"not found",suggestion:"Run: genie setup"});if(isSetupComplete())results.push({name:"Setup complete",status:"pass"});else results.push({name:"Setup complete",status:"warn",message:"not completed",suggestion:"Run: genie setup"});let claudeSettingsPath=getClaudeSettingsPath();if(existsSync20(claudeSettingsPath))results.push({name:"Claude settings exists",status:"pass",message:contractClaudePath(claudeSettingsPath)});else results.push({name:"Claude settings exists",status:"warn",message:"not found",suggestion:"Claude Code creates this on first run"});return results}async function checkTmux(){let results=[];try{if((await $3`${tmuxBin()} -L genie list-sessions 2>/dev/null`.quiet()).exitCode===0)results.push({name:"Server running",status:"pass"});else return results.push({name:"Server running",status:"warn",message:"no sessions",suggestion:"Start with: tmux new-session -d -s genie"}),results}catch{return results.push({name:"Server running",status:"warn",message:"could not check"}),results}let sessionName=(await loadGenieConfig()).session.name;try{if((await $3`${tmuxBin()} -L genie has-session -t ${`=${sessionName}`} 2>/dev/null`.quiet()).exitCode===0)results.push({name:`Session '${sessionName}' exists`,status:"pass"});else results.push({name:`Session '${sessionName}' exists`,status:"warn",suggestion:`Start with: tmux new-session -d -s ${sessionName}`})}catch{results.push({name:`Session '${sessionName}' exists`,status:"warn",message:"could not check"})}return results}async function checkWorkerProfiles(){let results=[];if(!genieConfigExists())return results.push({name:"Worker profiles",status:"warn",message:"no genie config",suggestion:"Run: genie setup"}),results;let config=await loadGenieConfig(),profiles=config.workerProfiles;if(!profiles||Object.keys(profiles).length===0)return results.push({name:"Worker profiles",status:"pass",message:"none configured (using defaults)"}),results;let totalProfiles=Object.keys(profiles).length;results.push({name:"Profiles configured",status:"pass",message:`${totalProfiles} profile${totalProfiles===1?"":"s"}`});for(let name of Object.keys(profiles))results.push({name:`Profile '${name}'`,status:"pass",message:"claude (direct)"});if(config.defaultWorkerProfile)if(profiles[config.defaultWorkerProfile])results.push({name:"Default profile",status:"pass",message:config.defaultWorkerProfile});else results.push({name:"Default profile",status:"warn",message:`'${config.defaultWorkerProfile}' not found`,suggestion:"Run: genie profiles default <profile>"});return results}async function checkBridge(){let results=[];try{let{getBridgeStatus:getBridgeStatus2,removeBridgePidfile:removeBridgePidfile2}=await Promise.resolve().then(() => (init_bridge_status(),exports_bridge_status)),res=await getBridgeStatus2(void 0,{});if(res.state==="stopped")return results.push({name:"Bridge",status:"warn",message:"stopped (no pidfile)",suggestion:"Start the bridge with: genie serve"}),results;if(res.state==="stale"){if(res.pidfile)removeBridgePidfile2();return results.push({name:"Bridge",status:"fail",message:`stale: ${res.detail}`,suggestion:"Restart the bridge with: genie serve restart"}),results}let{pong,pidfile}=res;if(!pong||!pidfile)return results.push({name:"Bridge",status:"warn",message:"running state missing pong/pidfile metadata"}),results;let uptimeSec=Math.round(pong.uptimeMs/1000);results.push({name:"Bridge running",status:"pass",message:`running (pid ${pong.pid}, uptime ${uptimeSec}s)`}),results.push({name:"NATS ping",status:"pass",message:`pong in ${res.latencyMs??0}ms (${pidfile.natsUrl})`}),results.push({name:"Subjects",status:"pass",message:pong.subjects.join(", ")})}catch(err){let detail=err instanceof Error?err.message:String(err);results.push({name:"Bridge module",status:"warn",message:`could not probe bridge: ${detail}`})}return results}function checkGenieAgentTemplate(workspaceRoot){let root=workspaceRoot??findWorkspace()?.root;if(!root)return[];let genieDir=join25(root,"agents","genie");if(!existsSync20(genieDir))return[];let agentsMd=join25(genieDir,"AGENTS.md"),agentYaml=join25(genieDir,"agent.yaml"),issues=[];if(existsSync20(agentsMd))try{if(readFileSync13(agentsMd,"utf-8").includes(STALE_GENIE_AGENTS_MD_MARKER))issues.push("AGENTS.md uses generic placeholder template")}catch{}if(existsSync20(agentYaml))try{let text=readFileSync13(agentYaml,"utf-8");if(!STALE_GENIE_AGENT_YAML_MISSING_MODEL_REGEX.test(text))issues.push("agent.yaml missing model field (TUI falls back to gray)")}catch{}if(issues.length===0)return[{name:"agents/genie scaffold up to date",status:"pass"}];return[{name:"agents/genie stale scaffold",status:"warn",message:issues.join("; "),suggestion:"Run: genie doctor --fix (re-emits genie specialist templates, preserves user edits)"}]}function findBundledTmuxConfigDir(){try{let moduleDir=dirname8(fileURLToPath3(import.meta.url));for(let i2=0;i2<6;i2+=1){let candidate=resolve3(moduleDir,"../".repeat(i2+1),"scripts","tmux");if(existsSync20(join25(candidate,"tui-tmux.conf"))&&existsSync20(join25(candidate,"genie.tmux.conf")))return candidate}}catch{}return null}function checkTmuxConfigs(){if(!findBundledTmuxConfigDir())return[{name:"tmux configs",status:"pass",message:"bundled configs unavailable (skipped)"}];let home=join25(homedir20(),".genie"),stale=[],expectedSnippet="unbind -n MouseDown3Pane";for(let file of["tui-tmux.conf","tmux.conf"]){let installedPath=join25(home,file);if(!existsSync20(installedPath))continue;try{if(!readFileSync13(installedPath,"utf-8").includes(expectedSnippet))stale.push(file)}catch{stale.push(file)}}if(stale.length===0)return[{name:"~/.genie tmux configs up to date",status:"pass"}];return[{name:"~/.genie tmux configs stale",status:"warn",message:`missing right-click unbind in: ${stale.join(", ")}`,suggestion:"Run: genie doctor --fix (refreshes ~/.genie tmux configs from bundled scripts/tmux/)"}]}function isAgentDirectory(path2){try{return statSync6(path2).isDirectory()}catch{return!1}}function hasLegacyFrontmatter(agentDir){let yamlPath=join25(agentDir,"agent.yaml"),agentsMdPath=join25(agentDir,"AGENTS.md");if(!existsSync20(yamlPath)||!existsSync20(agentsMdPath))return!1;try{return readFileSync13(agentsMdPath,"utf-8").slice(0,4).startsWith("---")}catch{return!1}}function checkLegacyAgentFrontmatter(workspaceRoot){let results=[],root=workspaceRoot??findWorkspace()?.root;if(!root)return[];let agentsDir=join25(root,"agents");if(!existsSync20(agentsDir))return[];let entries;try{entries=readdirSync7(agentsDir)}catch{return[]}for(let name of entries){let agentDir=join25(agentsDir,name);if(!isAgentDirectory(agentDir))continue;if(!hasLegacyFrontmatter(agentDir))continue;results.push({name:`agents/${name}/AGENTS.md`,status:"warn",message:"legacy frontmatter detected (ignored by sync)",suggestion:`Move config into agents/${name}/agent.yaml \u2014 AGENTS.md is prompt-only post-migration.`})}if(results.length===0)results.push({name:"No legacy frontmatter in agents/*/AGENTS.md",status:"pass"});return results}async function checkPgserveCanonical(){let results=[],canonicalPort=null;try{let out=execFileSync("pgserve",["port"],{encoding:"utf8",timeout:3000,stdio:["ignore","pipe","ignore"]}),parsed=Number.parseInt(out.trim(),10);if(Number.isFinite(parsed)&&parsed>0&&parsed<=65535)canonicalPort=parsed}catch{}if(canonicalPort===null)return results.push({name:"pgserve binary",status:"warn",message:"not on PATH (or `pgserve port` failed)",suggestion:"Install canonical pgserve: bun add -g pgserve@^2.1.0 (then run `pgserve install` to register under pm2)"}),results;results.push({name:"pgserve binary",status:"pass",message:`canonical port ${canonicalPort}`});try{let status=execFileSync("pgserve",["status","--json"],{encoding:"utf8",timeout:3000,stdio:["ignore","pipe","ignore"]}),parsed=JSON.parse(status);if(parsed.installed===!0&&parsed.status==="online")results.push({name:"pgserve under pm2",status:"pass",message:`online \u2014 shared backbone for genie-serve + omni-api on :${canonicalPort}`});else if(parsed.installed===!0)results.push({name:"pgserve under pm2",status:"warn",message:`registered but status=${parsed.status??"unknown"}`,suggestion:"Recover with: pm2 restart pgserve (logs: ~/.pgserve/logs/)"});else results.push({name:"pgserve under pm2",status:"warn",message:"binary present but not registered under pm2",suggestion:"Register canonical pgserve: pgserve install"})}catch{results.push({name:"pgserve under pm2",status:"warn",message:"`pgserve status` failed (pm2 unreachable?)",suggestion:"Verify pm2: pm2 list | Re-register: pgserve install"})}return results}function runCheckSection(label,results,counts){printSectionHeader(label);for(let result2 of results){if(printCheckResult(result2),result2.status==="fail")counts.errors=!0;if(result2.status==="warn")counts.warnings=!0}}async function doctorCommand(options){if(options?.fix){await doctorFix();return}if(options?.fixTeamOrphans){await runFixTeamOrphans({dryRun:Boolean(options.dryRun),json:Boolean(options.json)});return}if(options?.observability){await runObservabilityCheck(Boolean(options.json));return}if(options?.perf){let{runPerfCheck:runPerfCheck2}=await Promise.resolve().then(() => (init_perf_check(),exports_perf_check));await runPerfCheck2(Boolean(options.json));return}console.log(),console.log("\x1B[1mGenie Doctor\x1B[0m"),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m`);let counts={errors:!1,warnings:!1};if(runCheckSection("Prerequisites",await checkPrerequisites(),counts),runCheckSection("Installer Resolution",await collectInstallerResolution(),counts),runCheckSection("Configuration",await checkConfiguration(),counts),runCheckSection("Tmux",await checkTmux(),counts),runCheckSection("Tmux Configs",checkTmuxConfigs(),counts),runCheckSection("Worker Profiles",await checkWorkerProfiles(),counts),runCheckSection("Pgserve (canonical backbone)",await checkPgserveCanonical(),counts),runCheckSection("Omni Bridge",await checkBridge(),counts),runCheckSection("Agent Config",checkLegacyAgentFrontmatter(),counts),runCheckSection("Genie Specialist",checkGenieAgentTemplate(),counts),printDoctorSummary(counts),counts.errors)process.exit(1)}function printObservabilityReport(report){if(console.log(),console.log("\x1B[1mObservability Health\x1B[0m"),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m`),console.log(` partition_health: ${report.partition_health}`),console.log(` partition_count: ${report.partition_count}`),console.log(` next_rotation_at: ${report.next_rotation_at??"n/a"}`),console.log(` oldest_partition: ${report.oldest_partition??"n/a"}`),console.log(` newest_partition: ${report.newest_partition??"n/a"}`),console.log(` GENIE_WIDE_EMIT: ${report.wide_emit_flag}`),report.message)console.log(` note: ${report.message}`);console.log()}async function runObservabilityCheck(json2){let report=await collectObservabilityHealth();if(json2)console.log(JSON.stringify(report,null,2));else printObservabilityReport(report);if(report.partition_health==="fail")process.exit(1)}async function runFixTeamOrphans(opts){let{archiveOrphanTeamConfigs:archiveOrphanTeamConfigs2}=await Promise.resolve().then(() => (init_archive_orphan_team_configs(),exports_archive_orphan_team_configs)),decisions=archiveOrphanTeamConfigs2({dryRun:opts.dryRun});if(opts.json){console.log(JSON.stringify({dryRun:opts.dryRun,decisions},null,2));return}if(decisions.length===0){console.log(" no team config dirs found \u2014 nothing to do");return}for(let d of decisions){let tag=d.classification==="stale"?opts.dryRun?"WOULD ARCHIVE":"ARCHIVED":d.classification.toUpperCase(),tail=d.archivedTo?` \u2192 ${d.archivedTo}`:"";console.log(` [${tag}] ${d.team} \u2014 ${d.reason}${tail}`)}let stale=decisions.filter((d)=>d.classification==="stale").length,active=decisions.filter((d)=>d.classification==="active").length;console.log(`
|
|
984
|
+
`;if(rows.length===0)return{driftPct:null,detail:"no prior backfill row \u2014 first run will seed"};let row=rows[0];if(row.total_bytes<=0)return{driftPct:0,detail:"no JSONL bytes discovered yet"};let pct=Math.max(0,row.total_bytes-row.processed_bytes)/row.total_bytes*100,display=pct.toFixed(1);return{driftPct:pct,detail:`processed ${row.processed_bytes}/${row.total_bytes} bytes (drift ${display}%)`}}catch(err){return{driftPct:null,detail:`drift probe failed: ${err.message}`}}}async function defaultRunBackfillSync(){if(!await isAvailable())return{ranSync:!1,driftPct:null,detail:"pg unavailable \u2014 backfill skipped"};return(async()=>{try{let sql=await getConnection(),{startBackfill:startBackfill2}=await Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill));await startBackfill2(sql)}catch{}})(),{ranSync:!0,driftPct:null,detail:"background convergence kicked \u2014 `genie doctor --fix` to wait"}}async function defaultRunBackfillBlocking(){if(!await isAvailable())return{ranSync:!1,driftPct:null,detail:"pg unavailable \u2014 backfill skipped"};let sql=await getConnection(),{startBackfill:startBackfill2}=await Promise.resolve().then(() => (init_session_backfill(),exports_session_backfill));await startBackfill2(sql);let after=await defaultMeasureBackfillDrift();return{ranSync:!0,driftPct:after.driftPct,detail:after.detail}}async function defaultListOrphanedZombies(){try{let{listExhaustedZombies:listExhaustedZombies2}=await Promise.resolve().then(() => (init_agent_registry(),exports_agent_registry));return await listExhaustedZombies2()}catch{return[]}}function safeIsDirectory(path2){try{return statSync5(path2).isDirectory()}catch{return!1}}function summarizeInboxes(inboxesDir2){let inboxFiles=[];try{inboxFiles=readdirSync6(inboxesDir2).filter((f)=>f.endsWith(".json"))}catch{return{newestMs:null,hasContent:!1}}let newestMs=null,hasContent=!1;for(let f of inboxFiles)try{let st=statSync5(join24(inboxesDir2,f));if(newestMs===null||st.mtimeMs>newestMs)newestMs=st.mtimeMs;if(st.size>2)hasContent=!0}catch{}return{newestMs,hasContent}}function classifyTeamDir2(name,base,now){let dir=join24(base,name);if(!safeIsDirectory(dir))return null;if(existsSync19(join24(dir,"config.json")))return null;let inboxesDir2=join24(dir,"inboxes");if(!safeIsDirectory(inboxesDir2))return null;let{newestMs,hasContent}=summarizeInboxes(inboxesDir2),orphan={teamName:name,path:dir,newestInboxMs:newestMs,hasContent},active=hasContent&&newestMs!==null&&now-newestMs<ORPHAN_FRESH_WINDOW_MS;return{orphan,active}}function defaultScanTeamConfigOrphans(){let base=teamsBaseDir3(),result2={active:[],stale:[]};if(!existsSync19(base))return result2;let now=Date.now();for(let name of readdirSync6(base)){if(name.startsWith(".")||name==="_archive")continue;let classified=classifyTeamDir2(name,base,now);if(!classified)continue;(classified.active?result2.active:result2.stale).push(classified.orphan)}return result2}function defaultArchiveStaleTeamConfigs(orphans){if(orphans.length===0)return[];let archiveRoot=join24(teamsBaseDir3(),"_archive");mkdirSync9(archiveRoot,{recursive:!0});let ts3=new Date().toISOString().replace(/[:.]/g,"-"),archived=[];for(let o of orphans){let dest=join24(archiveRoot,`${o.teamName}-${ts3}`);try{renameSync4(o.path,dest),archived.push(dest)}catch{}}return archived}async function defaultRecordAudit(eventType,name,details){await recordAuditEvent("command","serve_start",eventType,"serve",{precondition:name,...details})}async function checkPartition(health,autoFix,deps){if(health.partition_health==="ok"||health.partition_health==="warn")return{name:"partition",status:"ok",detail:health.next_rotation_at?`next rotation: ${health.next_rotation_at}`:void 0};if(health.partition_health==="unknown")return{name:"partition",status:"skipped",detail:"pg unavailable \u2014 skipping partition probe"};if(!autoFix)return{name:"partition",status:"refused",detail:"today's partition is missing or rotation is overdue",fixCommand:"genie doctor --observability # then re-run; or `genie serve start` (without --no-fix)"};let result2=await deps.runPartitionMaintenance();return{name:"partition",status:"fixed",detail:`created/present ${result2.createdOrPresent}, dropped ${result2.dropped}; next rotation ${result2.nextRotationAt??"unknown"}`}}async function checkWatchdog(health,autoFix,deps){if(deps.platform!=="linux")return{name:"watchdog",status:"skipped",detail:"watchdog install is Linux/systemd only"};if(process.env.GENIE_WATCHDOG_SKIP==="1")return{name:"watchdog",status:"skipped",detail:"GENIE_WATCHDOG_SKIP=1"};if(health.watchdog==="ok")return{name:"watchdog",status:"ok"};if(!process.env.GENIE_WATCHDOG_INSTALL_CMD&&!resolveWatchdogCliPath())return{name:"watchdog",status:"skipped",detail:"watchdog optional in this install \u2014 set GENIE_WATCHDOG_SKIP=1 to silence, or run from source repo to enable"};if(!autoFix)return{name:"watchdog",status:"refused",detail:health.watchdog_detail??"watchdog units missing",fixCommand:"sudo bun run packages/watchdog/src/cli.ts install"};try{let result2=await deps.installWatchdog();return{name:"watchdog",status:"fixed",detail:`wrote ${result2.filesWritten.length}, skipped ${result2.filesSkipped.length}`}}catch(err){return{name:"watchdog",status:"refused",detail:`auto-install failed: ${err.message}`,fixCommand:"sudo bun run packages/watchdog/src/cli.ts install"}}}async function checkBackfill(autoFix,deps){let drift=await deps.measureBackfillDrift();if(drift.driftPct===null)return{name:"backfill",status:"skipped",detail:drift.detail};if(drift.driftPct<BACKFILL_DRIFT_THRESHOLD_PCT)return{name:"backfill",status:"ok",detail:drift.detail};if(!autoFix)return{name:"backfill",status:"refused",detail:drift.detail,fixCommand:`genie sessions sync # drift ${drift.driftPct.toFixed(1)}% > ${BACKFILL_DRIFT_THRESHOLD_PCT}%`};return{name:"backfill",status:"fixed",detail:(await deps.runBackfillSync()).detail}}async function checkDeadPaneZombies(deps){let orphans=await deps.listOrphanedZombies();if(orphans.length===0)return{name:"dead_pane_zombies",status:"ok"};return{name:"dead_pane_zombies",status:"refused",detail:`${orphans.length} exhausted zombie(s) past TTL; visible in \`genie status\``,fixCommand:"genie prune --zombies # archive eligible rows"}}function checkTeamConfigOrphans(autoFix,deps){let scan=deps.scanTeamConfigOrphans();if(scan.active.length===0&&scan.stale.length===0)return{name:"team_config_orphans",status:"ok"};if(!autoFix){let summary=`active=${scan.active.length} stale=${scan.stale.length}`,firstActive=scan.active[0]?.teamName;return{name:"team_config_orphans",status:"refused",detail:summary,fixCommand:firstActive?`genie team repair ${firstActive} # active orphan; stale dirs archive on auto-fix`:"genie serve start # auto-fix archives stale orphans"}}let archivedPaths=deps.archiveStaleTeamConfigs(scan.stale);if(scan.active.length>0)return{name:"team_config_orphans",status:"refused",detail:`archived ${archivedPaths.length} stale; ${scan.active.length} active orphan(s) need repair`,fixCommand:`genie team repair ${scan.active[0].teamName}`};return{name:"team_config_orphans",status:"fixed",detail:`archived ${archivedPaths.length} stale orphan(s)`}}function bindDefaults(deps){return{collectHealth:deps?.collectHealth??collectObservabilityHealth,runPartitionMaintenance:deps?.runPartitionMaintenance??defaultRunPartitionMaintenance,installWatchdog:deps?.installWatchdog??defaultInstallWatchdog,runBackfillSync:deps?.runBackfillSync??defaultRunBackfillSync,measureBackfillDrift:deps?.measureBackfillDrift??defaultMeasureBackfillDrift,listOrphanedZombies:deps?.listOrphanedZombies??defaultListOrphanedZombies,scanTeamConfigOrphans:deps?.scanTeamConfigOrphans??defaultScanTeamConfigOrphans,archiveStaleTeamConfigs:deps?.archiveStaleTeamConfigs??defaultArchiveStaleTeamConfigs,platform:deps?.platform??process.platform,recordAudit:deps?.recordAudit??defaultRecordAudit,log:deps?.log??((line)=>console.log(line))}}async function ensureServeReady(opts){let deps=bindDefaults(opts.deps),health=await deps.collectHealth(),results=[];results.push(await checkPartition(health,opts.autoFix,deps)),results.push(await checkBackfill(opts.autoFix,deps)),results.push(await checkDeadPaneZombies(deps)),await emitAuditEvents(results,deps);let ok=results.every((r)=>r.status==="ok"||r.status==="fixed"||r.status==="skipped");return printReport(results,deps.log),{ok,results}}function formatDuration(ms){if(ms<1000)return`${ms}ms`;return`${(ms/1000).toFixed(1)}s`}function logMaintenanceStepStart(log,silent,label){if(!silent)log(` ${label}...`);return Date.now()}function logMaintenanceStepDone(log,silent,label,startedAt){if(!silent)log(` done ${label} (${formatDuration(Date.now()-startedAt)})`)}async function runDoctorMaintenance(opts={}){let rawRunBackfillSync=opts.deps?.runBackfillSync??defaultRunBackfillBlocking,deps=bindDefaults({...opts.deps,runBackfillSync:defaultRunBackfillBlocking,log:opts.silent?()=>{}:opts.deps?.log});deps.runBackfillSync=async()=>{let startedAt2=logMaintenanceStepStart(deps.log,opts.silent,"Running session backfill convergence (can take minutes on large transcript history)");try{return await rawRunBackfillSync()}finally{logMaintenanceStepDone(deps.log,opts.silent,"session backfill convergence",startedAt2)}};let startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Collecting observability health"),health=await deps.collectHealth();logMaintenanceStepDone(deps.log,opts.silent,"observability health",startedAt);let results=[];startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking runtime event partitions"),results.push(await checkPartition(health,!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"runtime event partitions",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking watchdog install"),results.push(await checkWatchdog(health,!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"watchdog install",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking session backfill drift"),results.push(await checkBackfill(!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"session backfill drift",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking exhausted zombie rows"),results.push(await checkDeadPaneZombies(deps)),logMaintenanceStepDone(deps.log,opts.silent,"exhausted zombie rows",startedAt),startedAt=logMaintenanceStepStart(deps.log,opts.silent,"Checking Claude team config orphans"),results.push(checkTeamConfigOrphans(!0,deps)),logMaintenanceStepDone(deps.log,opts.silent,"Claude team config orphans",startedAt),await emitAuditEvents(results,deps);let ok=results.every((r)=>r.status==="ok"||r.status==="fixed"||r.status==="skipped");return printReport(results,deps.log),{ok,results}}async function emitAuditEvents(results,deps){for(let result2 of results)if(result2.status==="fixed")await deps.recordAudit("serve.precondition.fixed",result2.name,{detail:result2.detail??null}).catch(()=>{});else if(result2.status==="refused")await deps.recordAudit("serve.precondition.refused",result2.name,{detail:result2.detail??null,fix_command:result2.fixCommand??null}).catch(()=>{})}function printReport(results,log){log(" Preconditions:");for(let r of results){let tag=statusTag(r.status),suffix=r.detail?` \u2014 ${r.detail}`:"";if(log(` ${tag} ${r.name}${suffix}`),r.status==="refused"&&r.fixCommand)log(` \u2192 ${r.fixCommand}`)}}function statusTag(status){switch(status){case"ok":return"[ok]";case"fixed":return"[fix]";case"refused":return"[!!]";case"skipped":return"[--]"}}var BACKFILL_DRIFT_THRESHOLD_PCT=5,ORPHAN_FRESH_WINDOW_MS=86400000;var init_ensure_ready=__esm(()=>{init_observability_health();init_audit();init_db()});var exports_doctor={};__export(exports_doctor,{runPostUpdateMaintenance:()=>runPostUpdateMaintenance,findStaleGenieCandidates:()=>findStaleGenieCandidates,findBundledTmuxConfigDir:()=>findBundledTmuxConfigDir,doctorCommand:()=>doctorCommand,checkTmuxConfigs:()=>checkTmuxConfigs,checkLegacyAgentFrontmatter:()=>checkLegacyAgentFrontmatter,checkGenieAgentTemplate:()=>checkGenieAgentTemplate});import{execFileSync}from"child_process";import{copyFileSync as copyFileSync2,existsSync as existsSync20,mkdirSync as mkdirSync10,readFileSync as readFileSync13,readdirSync as readdirSync7,statSync as statSync6,unlinkSync as unlinkSync6,writeFileSync as writeFileSync7}from"fs";import{homedir as homedir20}from"os";import{dirname as dirname8,join as join25,resolve as resolve3}from"path";import{fileURLToPath as fileURLToPath3}from"url";var{$:$3}=globalThis.Bun;function printSectionHeader(title){console.log(),console.log(`\x1B[1m${title}:\x1B[0m`)}function printCheckResult(result2){let icon={pass:"\x1B[32m\u2713\x1B[0m",fail:"\x1B[31m\u2717\x1B[0m",warn:"\x1B[33m!\x1B[0m"}[result2.status],message=result2.message?` ${result2.message}`:"";if(console.log(` ${icon} ${result2.name}${message}`),result2.suggestion)console.log(` \x1B[2m${result2.suggestion}\x1B[0m`)}async function checkPrerequisites(){let results=[],tmuxCheck=await checkCommand("tmux");if(tmuxCheck.exists)results.push({name:"tmux",status:"pass",message:tmuxCheck.version||""});else results.push({name:"tmux",status:"fail",suggestion:"Install with: brew install tmux (or apt install tmux)"});let jqCheck=await checkCommand("jq");if(jqCheck.exists)results.push({name:"jq",status:"pass",message:jqCheck.version||""});else results.push({name:"jq",status:"fail",suggestion:"Install with: brew install jq (or apt install jq)"});let bunCheck=await checkCommand("bun");if(bunCheck.exists)results.push({name:"bun",status:"pass",message:bunCheck.version||""});else results.push({name:"bun",status:"fail",suggestion:"Install with: curl -fsSL https://bun.sh/install | bash"});let claudeCheck=await checkCommand("claude");if(claudeCheck.exists)results.push({name:"Claude Code",status:"pass",message:claudeCheck.version||""});else results.push({name:"Claude Code",status:"warn",suggestion:"Install with: npm install -g @anthropic-ai/claude-code"});let codexCheck=await checkCommand("codex");if(codexCheck.exists)results.push({name:"Codex CLI",status:"pass",message:codexCheck.version||""});else results.push({name:"Codex CLI",status:"warn",suggestion:"Install via OpenAI account; codex is optional unless using --provider codex"});let requiredForSpawn=["genie","bun","node","npm","git","claude","codex"];for(let bin of requiredForSpawn){let interactivePath=await resolveBinaryInteractive(bin),nonInteractivePath=await resolveBinaryNonInteractive(bin);if(interactivePath&&nonInteractivePath)results.push({name:`Non-interactive PATH: ${bin}`,status:"pass",message:nonInteractivePath});else if(interactivePath&&!nonInteractivePath)results.push({name:`Non-interactive PATH: ${bin}`,status:"warn",message:"interactive-only",suggestion:`Move PATH export from ~/.bashrc to ~/.profile so spawn-scripts can resolve ${bin}. (Or use a stable symlink in ~/.local/bin.)`});else if(!interactivePath&&bin!=="codex"){if(bin==="genie"||bin==="node"||bin==="npm"||bin==="git")results.push({name:`Non-interactive PATH: ${bin}`,status:"warn",message:"not in non-interactive PATH",suggestion:`Add ${bin} to ~/.profile (not just ~/.bashrc). Some flows (e.g. genie update) shell out to bare '${bin}' from non-interactive subprocesses.`})}}return results}async function resolveBinaryInteractive(bin){try{return(await $3`command -v ${bin}`.quiet().text()).trim()||null}catch{return null}}async function resolveBinaryNonInteractive(bin){try{return(await $3`sh -c "command -v ${bin}"`.quiet().text()).trim()||null}catch{return null}}async function checkConfiguration(){let results=[];if(genieConfigExists())results.push({name:"Genie config exists",status:"pass",message:contractClaudePath(getGenieConfigPath())});else results.push({name:"Genie config exists",status:"warn",message:"not found",suggestion:"Run: genie setup"});if(isSetupComplete())results.push({name:"Setup complete",status:"pass"});else results.push({name:"Setup complete",status:"warn",message:"not completed",suggestion:"Run: genie setup"});let claudeSettingsPath=getClaudeSettingsPath();if(existsSync20(claudeSettingsPath))results.push({name:"Claude settings exists",status:"pass",message:contractClaudePath(claudeSettingsPath)});else results.push({name:"Claude settings exists",status:"warn",message:"not found",suggestion:"Claude Code creates this on first run"});return results}async function checkTmux(){let results=[];try{if((await $3`${tmuxBin()} -L genie list-sessions 2>/dev/null`.quiet()).exitCode===0)results.push({name:"Server running",status:"pass"});else return results.push({name:"Server running",status:"warn",message:"no sessions",suggestion:"Start with: tmux new-session -d -s genie"}),results}catch{return results.push({name:"Server running",status:"warn",message:"could not check"}),results}let sessionName=(await loadGenieConfig()).session.name;try{if((await $3`${tmuxBin()} -L genie has-session -t ${`=${sessionName}`} 2>/dev/null`.quiet()).exitCode===0)results.push({name:`Session '${sessionName}' exists`,status:"pass"});else results.push({name:`Session '${sessionName}' exists`,status:"warn",suggestion:`Start with: tmux new-session -d -s ${sessionName}`})}catch{results.push({name:`Session '${sessionName}' exists`,status:"warn",message:"could not check"})}return results}async function checkWorkerProfiles(){let results=[];if(!genieConfigExists())return results.push({name:"Worker profiles",status:"warn",message:"no genie config",suggestion:"Run: genie setup"}),results;let config=await loadGenieConfig(),profiles=config.workerProfiles;if(!profiles||Object.keys(profiles).length===0)return results.push({name:"Worker profiles",status:"pass",message:"none configured (using defaults)"}),results;let totalProfiles=Object.keys(profiles).length;results.push({name:"Profiles configured",status:"pass",message:`${totalProfiles} profile${totalProfiles===1?"":"s"}`});for(let name of Object.keys(profiles))results.push({name:`Profile '${name}'`,status:"pass",message:"claude (direct)"});if(config.defaultWorkerProfile)if(profiles[config.defaultWorkerProfile])results.push({name:"Default profile",status:"pass",message:config.defaultWorkerProfile});else results.push({name:"Default profile",status:"warn",message:`'${config.defaultWorkerProfile}' not found`,suggestion:"Run: genie profiles default <profile>"});return results}async function checkBridge(){let results=[];try{let{getBridgeStatus:getBridgeStatus2,removeBridgePidfile:removeBridgePidfile2}=await Promise.resolve().then(() => (init_bridge_status(),exports_bridge_status)),res=await getBridgeStatus2(void 0,{});if(res.state==="stopped")return results.push({name:"Bridge",status:"warn",message:"stopped (no pidfile)",suggestion:"Start the bridge with: genie serve"}),results;if(res.state==="stale"){if(res.pidfile)removeBridgePidfile2();return results.push({name:"Bridge",status:"fail",message:`stale: ${res.detail}`,suggestion:"Restart the bridge with: genie serve restart"}),results}let{pong,pidfile}=res;if(!pong||!pidfile)return results.push({name:"Bridge",status:"warn",message:"running state missing pong/pidfile metadata"}),results;let uptimeSec=Math.round(pong.uptimeMs/1000);results.push({name:"Bridge running",status:"pass",message:`running (pid ${pong.pid}, uptime ${uptimeSec}s)`}),results.push({name:"NATS ping",status:"pass",message:`pong in ${res.latencyMs??0}ms (${pidfile.natsUrl})`}),results.push({name:"Subjects",status:"pass",message:pong.subjects.join(", ")})}catch(err){let detail=err instanceof Error?err.message:String(err);results.push({name:"Bridge module",status:"warn",message:`could not probe bridge: ${detail}`})}return results}function checkGenieAgentTemplate(workspaceRoot){let root=workspaceRoot??findWorkspace()?.root;if(!root)return[];let genieDir=join25(root,"agents","genie");if(!existsSync20(genieDir))return[];let agentsMd=join25(genieDir,"AGENTS.md"),agentYaml=join25(genieDir,"agent.yaml"),issues=[];if(existsSync20(agentsMd))try{if(readFileSync13(agentsMd,"utf-8").includes(STALE_GENIE_AGENTS_MD_MARKER))issues.push("AGENTS.md uses generic placeholder template")}catch{}if(existsSync20(agentYaml))try{let text=readFileSync13(agentYaml,"utf-8");if(!STALE_GENIE_AGENT_YAML_MISSING_MODEL_REGEX.test(text))issues.push("agent.yaml missing model field (TUI falls back to gray)")}catch{}if(issues.length===0)return[{name:"agents/genie scaffold up to date",status:"pass"}];return[{name:"agents/genie stale scaffold",status:"warn",message:issues.join("; "),suggestion:"Run: genie doctor --fix (re-emits genie specialist templates, preserves user edits)"}]}function findBundledTmuxConfigDir(){try{let moduleDir=dirname8(fileURLToPath3(import.meta.url));for(let i2=0;i2<6;i2+=1){let candidate=resolve3(moduleDir,"../".repeat(i2+1),"scripts","tmux");if(existsSync20(join25(candidate,"tui-tmux.conf"))&&existsSync20(join25(candidate,"genie.tmux.conf")))return candidate}}catch{}return null}function checkTmuxConfigs(){if(!findBundledTmuxConfigDir())return[{name:"tmux configs",status:"pass",message:"bundled configs unavailable (skipped)"}];let home=join25(homedir20(),".genie"),stale=[],expectedSnippet="unbind -n MouseDown3Pane";for(let file of["tui-tmux.conf","tmux.conf"]){let installedPath=join25(home,file);if(!existsSync20(installedPath))continue;try{if(!readFileSync13(installedPath,"utf-8").includes(expectedSnippet))stale.push(file)}catch{stale.push(file)}}if(stale.length===0)return[{name:"~/.genie tmux configs up to date",status:"pass"}];return[{name:"~/.genie tmux configs stale",status:"warn",message:`missing right-click unbind in: ${stale.join(", ")}`,suggestion:"Run: genie doctor --fix (refreshes ~/.genie tmux configs from bundled scripts/tmux/)"}]}function isAgentDirectory(path2){try{return statSync6(path2).isDirectory()}catch{return!1}}function hasLegacyFrontmatter(agentDir){let yamlPath=join25(agentDir,"agent.yaml"),agentsMdPath=join25(agentDir,"AGENTS.md");if(!existsSync20(yamlPath)||!existsSync20(agentsMdPath))return!1;try{return readFileSync13(agentsMdPath,"utf-8").slice(0,4).startsWith("---")}catch{return!1}}function checkLegacyAgentFrontmatter(workspaceRoot){let results=[],root=workspaceRoot??findWorkspace()?.root;if(!root)return[];let agentsDir=join25(root,"agents");if(!existsSync20(agentsDir))return[];let entries;try{entries=readdirSync7(agentsDir)}catch{return[]}for(let name of entries){let agentDir=join25(agentsDir,name);if(!isAgentDirectory(agentDir))continue;if(!hasLegacyFrontmatter(agentDir))continue;results.push({name:`agents/${name}/AGENTS.md`,status:"warn",message:"legacy frontmatter detected (ignored by sync)",suggestion:`Move config into agents/${name}/agent.yaml \u2014 AGENTS.md is prompt-only post-migration.`})}if(results.length===0)results.push({name:"No legacy frontmatter in agents/*/AGENTS.md",status:"pass"});return results}async function checkPgserveCanonical(){let results=[],canonicalPort=null;try{let out=execFileSync("pgserve",["port"],{encoding:"utf8",timeout:3000,stdio:["ignore","pipe","ignore"]}),parsed=Number.parseInt(out.trim(),10);if(Number.isFinite(parsed)&&parsed>0&&parsed<=65535)canonicalPort=parsed}catch{}if(canonicalPort===null)return results.push({name:"pgserve binary",status:"warn",message:"not on PATH (or `pgserve port` failed)",suggestion:"Install canonical pgserve: bun add -g pgserve@^2.1.0 (then run `pgserve install` to register under pm2)"}),results;results.push({name:"pgserve binary",status:"pass",message:`canonical port ${canonicalPort}`});try{let status=execFileSync("pgserve",["status","--json"],{encoding:"utf8",timeout:3000,stdio:["ignore","pipe","ignore"]}),parsed=JSON.parse(status);if(parsed.installed===!0&&parsed.status==="online")results.push({name:"pgserve under pm2",status:"pass",message:`online \u2014 shared backbone for genie-serve + omni-api on :${canonicalPort}`});else if(parsed.installed===!0)results.push({name:"pgserve under pm2",status:"warn",message:`registered but status=${parsed.status??"unknown"}`,suggestion:"Recover with: pm2 restart pgserve (logs: ~/.pgserve/logs/)"});else results.push({name:"pgserve under pm2",status:"warn",message:"binary present but not registered under pm2",suggestion:"Register canonical pgserve: pgserve install"})}catch{results.push({name:"pgserve under pm2",status:"warn",message:"`pgserve status` failed (pm2 unreachable?)",suggestion:"Verify pm2: pm2 list | Re-register: pgserve install"})}return results}function runCheckSection(label,results,counts){printSectionHeader(label);for(let result2 of results){if(printCheckResult(result2),result2.status==="fail")counts.errors=!0;if(result2.status==="warn")counts.warnings=!0}}async function doctorCommand(options){if(options?.fix){await doctorFix();return}if(options?.fixTeamOrphans){await runFixTeamOrphans({dryRun:Boolean(options.dryRun),json:Boolean(options.json)});return}if(options?.observability){await runObservabilityCheck(Boolean(options.json));return}if(options?.perf){let{runPerfCheck:runPerfCheck2}=await Promise.resolve().then(() => (init_perf_check(),exports_perf_check));await runPerfCheck2(Boolean(options.json));return}console.log(),console.log("\x1B[1mGenie Doctor\x1B[0m"),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m`);let counts={errors:!1,warnings:!1};if(runCheckSection("Prerequisites",await checkPrerequisites(),counts),runCheckSection("Installer Resolution",await collectInstallerResolution(),counts),runCheckSection("Configuration",await checkConfiguration(),counts),runCheckSection("Tmux",await checkTmux(),counts),runCheckSection("Tmux Configs",checkTmuxConfigs(),counts),runCheckSection("Worker Profiles",await checkWorkerProfiles(),counts),runCheckSection("Pgserve (canonical backbone)",await checkPgserveCanonical(),counts),runCheckSection("Omni Bridge",await checkBridge(),counts),runCheckSection("Agent Config",checkLegacyAgentFrontmatter(),counts),runCheckSection("Genie Specialist",checkGenieAgentTemplate(),counts),printDoctorSummary(counts),counts.errors)process.exit(1)}function printObservabilityReport(report){if(console.log(),console.log("\x1B[1mObservability Health\x1B[0m"),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m`),console.log(` partition_health: ${report.partition_health}`),console.log(` partition_count: ${report.partition_count}`),console.log(` next_rotation_at: ${report.next_rotation_at??"n/a"}`),console.log(` oldest_partition: ${report.oldest_partition??"n/a"}`),console.log(` newest_partition: ${report.newest_partition??"n/a"}`),console.log(` GENIE_WIDE_EMIT: ${report.wide_emit_flag}`),report.message)console.log(` note: ${report.message}`);console.log()}async function runObservabilityCheck(json2){let report=await collectObservabilityHealth();if(json2)console.log(JSON.stringify(report,null,2));else printObservabilityReport(report);if(report.partition_health==="fail")process.exit(1)}async function runFixTeamOrphans(opts){let{archiveOrphanTeamConfigs:archiveOrphanTeamConfigs2}=await Promise.resolve().then(() => (init_archive_orphan_team_configs(),exports_archive_orphan_team_configs)),decisions=archiveOrphanTeamConfigs2({dryRun:opts.dryRun});if(opts.json){console.log(JSON.stringify({dryRun:opts.dryRun,decisions},null,2));return}if(decisions.length===0){console.log(" no team config dirs found \u2014 nothing to do");return}for(let d of decisions){let tag=d.classification==="stale"?opts.dryRun?"WOULD ARCHIVE":"ARCHIVED":d.classification.toUpperCase(),tail=d.archivedTo?` \u2192 ${d.archivedTo}`:"";console.log(` [${tag}] ${d.team} \u2014 ${d.reason}${tail}`)}let stale=decisions.filter((d)=>d.classification==="stale").length,active=decisions.filter((d)=>d.classification==="active").length;console.log(`
|
|
985
985
|
${decisions.length} dirs inspected, ${stale} archived, ${active} flagged active.`)}function printDoctorSummary(counts){if(console.log(),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m`),counts.errors)console.log("\x1B[31mSome checks failed.\x1B[0m Run \x1B[36mgenie setup\x1B[0m to fix.");else if(counts.warnings)console.log("\x1B[33mSome warnings detected.\x1B[0m Everything should still work.");else console.log("\x1B[32mAll checks passed!\x1B[0m");console.log()}function legacyPgserveRepairEnabled(){return process.env.GENIE_PG_FORCE_TCP==="1"||process.env.GENIE_DOCTOR_FIX_LEGACY_PGSERVE==="1"}function printPgserveRecoveryHint(){console.log(" \x1B[33m[!!] pgserve unreachable \u2014 canonical daemon may not be running.\x1B[0m"),console.log(" Recovery (run as the operator, not the doctor):"),console.log(" pm2 status # is pgserve registered?"),console.log(" pm2 restart pgserve # OR: autopg restart"),console.log(" pgserve install # if not registered yet"),console.log(" See https://github.com/automagik-dev/genie/blob/main/docs/install.md")}async function cleanSharedMemory(){console.log(" Cleaning shared memory...");try{let{execSync:execSync3}=await import("child_process");if(process.platform==="darwin")execSync3("ipcs -m 2>/dev/null | awk '/^m/ {print $2}' | xargs -I{} ipcrm -m {} 2>/dev/null || true",{stdio:"ignore",timeout:5000});else execSync3("ipcs -m 2>/dev/null | awk '$6 == 0 {print $2}' | xargs -I{} ipcrm -m {} 2>/dev/null || true",{stdio:"ignore",timeout:5000});console.log(" \x1B[32m\u2713\x1B[0m Shared memory cleaned")}catch{console.log(" \x1B[32m\u2713\x1B[0m No stale shared memory")}}async function stopExistingDaemon(pidFile){try{let{readFileSync:readFileSync14}=await import("fs"),pid=Number.parseInt(readFileSync14(pidFile,"utf-8").trim(),10);if(!Number.isNaN(pid)&&pid>0)try{process.kill(pid,"SIGTERM"),console.log(` \x1B[32m\u2713\x1B[0m Stopped existing daemon (PID ${pid})`),await new Promise((r)=>setTimeout(r,1000))}catch{}}catch{}}function removeStaleFiles(genieHome3,pidFile){let files=[pidFile];if(legacyPgserveRepairEnabled())files.push(join25(genieHome3,"pgserve.port"),join25(genieHome3,"data","pgserve","postmaster.pid"));else console.log(" Leaving legacy pgserve v1 port/data files untouched (v1/v2 coexistence)");for(let file of files)if(existsSync20(file))try{unlinkSync6(file),console.log(` \x1B[32m\u2713\x1B[0m Removed ${file}`)}catch{console.log(` \x1B[33m!\x1B[0m Could not remove ${file}`)}}async function restartDaemon(){console.log(" Restarting daemon...");try{let{spawn:spawn2}=await import("child_process"),bunPath=process.execPath??"bun",genieBin=process.argv[1]??"genie";spawn2(bunPath,[genieBin,"daemon","start"],{detached:!0,stdio:"ignore"}).unref(),await new Promise((resolve4)=>setTimeout(resolve4,2000)),console.log(" \x1B[32m\u2713\x1B[0m Daemon restart initiated")}catch{console.log(" \x1B[33m!\x1B[0m Could not restart daemon \u2014 run: genie daemon start")}}function isAgentsMdUserEdited(target,fresh){if(!existsSync20(target))return!1;try{let current=readFileSync13(target,"utf-8");if(current.trim().length===0)return!1;if(current.includes(STALE_GENIE_AGENTS_MD_MARKER))return!1;return current!==fresh}catch{return!1}}function writeGenieTemplate(targetDir,name,content){let target=join25(targetDir,name),userEdited=name==="AGENTS.md"&&isAgentsMdUserEdited(target,content),writeTo=userEdited?`${target}.new`:target;try{writeFileSync7(writeTo,content);let marker=userEdited?`wrote ${name}.new (user edits preserved \u2014 merge manually)`:`wrote ${name}`;console.log(` \x1B[32m\u2713\x1B[0m ${marker}`)}catch(err){let detail=err instanceof Error?err.message:String(err);console.log(` \x1B[31m\u2717\x1B[0m ${name}: ${detail}`)}}function fixGenieAgentTemplate(workspaceRoot){let root=workspaceRoot??findWorkspace()?.root;if(!root){console.log(" \x1B[33m!\x1B[0m No workspace detected \u2014 skipping genie-template repair");return}let genieDir=join25(root,"agents","genie");if(!existsSync20(genieDir)){console.log(" \x1B[2m\xB7\x1B[0m No agents/genie directory \u2014 skipping");return}console.log(" Refreshing agents/genie scaffold..."),writeGenieTemplate(genieDir,"AGENTS.md",GENIE_AGENTS_TEMPLATE),writeGenieTemplate(genieDir,"SOUL.md",GENIE_SOUL_TEMPLATE),writeGenieTemplate(genieDir,"HEARTBEAT.md",GENIE_HEARTBEAT_TEMPLATE)}function ensureGenieHomeDir(home){if(existsSync20(home))return!0;try{return mkdirSync10(home,{recursive:!0}),!0}catch{return console.log(` \x1B[31m\u2717\x1B[0m Could not create ${home}`),!1}}function refreshTmuxConfFile(bundledDir,home,srcFile,dstFile){let src=join25(bundledDir,srcFile),dst=join25(home,dstFile);if(!existsSync20(src))return;if(existsSync20(dst))try{copyFileSync2(dst,`${dst}.bak`)}catch{}try{copyFileSync2(src,dst),console.log(` \x1B[32m\u2713\x1B[0m wrote ${dstFile} (previous saved as ${dstFile}.bak)`)}catch(err){let detail=err instanceof Error?err.message:String(err);console.log(` \x1B[31m\u2717\x1B[0m ${dstFile}: ${detail}`)}}function fixTmuxConfigs(){let bundledDir=findBundledTmuxConfigDir();if(!bundledDir){console.log(" \x1B[33m!\x1B[0m Bundled tmux configs not found \u2014 skipping");return}let home=join25(homedir20(),".genie");if(!ensureGenieHomeDir(home))return;console.log(" Refreshing ~/.genie tmux configs..."),refreshTmuxConfFile(bundledDir,home,"tui-tmux.conf","tui-tmux.conf"),refreshTmuxConfFile(bundledDir,home,"genie.tmux.conf","tmux.conf")}async function runMaintenancePreconditions(silent=!1,log){try{let{runDoctorMaintenance:runDoctorMaintenance2}=await Promise.resolve().then(() => (init_ensure_ready(),exports_ensure_ready));await runDoctorMaintenance2({silent,deps:log?{log}:void 0})}catch(err){let msg=err instanceof Error?err.message:String(err);if(!silent)console.warn(` Maintenance preconditions skipped: ${msg}`)}}async function doctorFix(){console.log(`
|
|
986
986
|
\x1B[1mGenie Doctor \u2014 Auto Fix\x1B[0m`),console.log(`\x1B[2m${"\u2500".repeat(40)}\x1B[0m
|
|
987
987
|
`);let genieHome3=process.env.GENIE_HOME??join25(homedir20(),".genie");printPgserveRecoveryHint(),await cleanSharedMemory();let pidFile=join25(genieHome3,"scheduler.pid");await stopExistingDaemon(pidFile),removeStaleFiles(genieHome3,pidFile),fixGenieAgentTemplate(),fixTmuxConfigs(),await runMaintenancePreconditions(),await restartDaemon(),console.log(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automagik/genie",
|
|
3
|
-
"version": "4.260504.
|
|
3
|
+
"version": "4.260504.7",
|
|
4
4
|
"description": "Collaborative terminal toolkit for human + AI workflows. NOTE: the npm distribution is being soft-deprecated — the canonical install is `curl -fsSL https://get.automagik.dev/genie | bash` (cosign + SLSA verified). See https://automagik.dev/genie/security/distribution-sovereignty",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genie",
|
|
3
|
-
"version": "4.260504.
|
|
3
|
+
"version": "4.260504.7",
|
|
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"
|