{"version":3,"file":"io.mjs","names":[],"sources":["../../src/config/io.ts"],"sourcesContent":["import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { getAgentDir } from '@earendil-works/pi-coding-agent';\n\nimport {\n\ttype ConfigScope,\n\tDEFAULT_CONFIG,\n\ttype LiveCompactionConfig,\n\ttype LiveCompactionPaths,\n\tparseConfig,\n} from '@live-compaction/config/schema';\n\nconst EXTENSION_DIR = path.dirname(fileURLToPath(import.meta.url));\nexport const CONFIG_FILE_NAME = 'config.json';\nexport const COMPACTION_PROMPT_FILE_NAME = 'compaction-prompt.md';\nexport const BRANCH_SUMMARY_PROMPT_FILE_NAME = 'branch-summary-prompt.md';\n\nexport async function readTextFileIfExists(\n\tfilePath: string,\n): Promise<{ exists: boolean; text?: string }> {\n\ttry {\n\t\treturn { exists: true, text: await readFile(filePath, 'utf8') };\n\t} catch (error) {\n\t\tconst code = (error as { code?: string }).code;\n\t\tif (code === 'ENOENT') return { exists: false };\n\t\tthrow error;\n\t}\n}\n\nexport async function writeTextFile(filePath: string, text: string): Promise<void> {\n\tconst tmpPath = `${filePath}.tmp`;\n\ttry {\n\t\tawait mkdir(path.dirname(filePath), { recursive: true });\n\t\tawait writeFile(tmpPath, text, 'utf8');\n\t\tawait rename(tmpPath, filePath);\n\t} catch (error) {\n\t\tawait rm(tmpPath, { force: true }).catch(() => undefined);\n\t\tthrow error;\n\t}\n}\n\nexport function resolveLiveCompactionPaths(\n\tcwd?: string | null,\n\tagentDir = getAgentDir(),\n): LiveCompactionPaths {\n\tconst globalRoot = path.join(agentDir, 'extensions', 'live-compaction');\n\tconst projectRoot = cwd ? path.join(cwd, '.pi', 'extensions', 'live-compaction') : undefined;\n\n\treturn {\n\t\tglobal: {\n\t\t\tscope: 'global',\n\t\t\trootDir: globalRoot,\n\t\t\tconfigPath: path.join(globalRoot, CONFIG_FILE_NAME),\n\t\t\tcompactionPromptPath: path.join(globalRoot, COMPACTION_PROMPT_FILE_NAME),\n\t\t\tbranchSummaryPromptPath: path.join(globalRoot, BRANCH_SUMMARY_PROMPT_FILE_NAME),\n\t\t},\n\t\t...(projectRoot\n\t\t\t? {\n\t\t\t\t\tproject: {\n\t\t\t\t\t\tscope: 'project' as const,\n\t\t\t\t\t\trootDir: projectRoot,\n\t\t\t\t\t\tconfigPath: path.join(projectRoot, CONFIG_FILE_NAME),\n\t\t\t\t\t\tcompactionPromptPath: path.join(projectRoot, COMPACTION_PROMPT_FILE_NAME),\n\t\t\t\t\t\tbranchSummaryPromptPath: path.join(projectRoot, BRANCH_SUMMARY_PROMPT_FILE_NAME),\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t};\n}\n\nexport function getScopedPaths(paths: LiveCompactionPaths, scope: ConfigScope) {\n\tif (scope === 'project') {\n\t\tif (!paths.project) {\n\t\t\tthrow new Error('Project scope requires an active working directory');\n\t\t}\n\t\treturn paths.project;\n\t}\n\treturn paths.global;\n}\n\nexport async function loadConfig(extensionDir = EXTENSION_DIR): Promise<LiveCompactionConfig> {\n\tconst configPath = path.join(extensionDir, CONFIG_FILE_NAME);\n\ttry {\n\t\tconst raw = await readFile(configPath, 'utf8');\n\t\treturn parseConfig(JSON.parse(raw) as unknown);\n\t} catch (error) {\n\t\tconst code = (error as { code?: string }).code;\n\t\tif (code === 'ENOENT') return structuredClone(DEFAULT_CONFIG);\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthrow new Error(`Failed to load live-compaction config from ${configPath}: ${message}`);\n\t}\n}\n\nexport async function loadScopedConfig(\n\tscope: ConfigScope,\n\tcwd?: string | null,\n\tagentDir = getAgentDir(),\n): Promise<LiveCompactionConfig> {\n\tconst scopedPaths = getScopedPaths(resolveLiveCompactionPaths(cwd, agentDir), scope);\n\ttry {\n\t\tconst raw = await readFile(scopedPaths.configPath, 'utf8');\n\t\treturn parseConfig(JSON.parse(raw) as unknown);\n\t} catch (error) {\n\t\tconst code = (error as { code?: string }).code;\n\t\tif (code === 'ENOENT') return structuredClone(DEFAULT_CONFIG);\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthrow new Error(\n\t\t\t`Failed to load live-compaction config from ${scopedPaths.configPath}: ${message}`,\n\t\t);\n\t}\n}\n\nexport async function loadEditableScopedConfig(\n\tscope: ConfigScope,\n\tcwd?: string | null,\n\tagentDir = getAgentDir(),\n): Promise<LiveCompactionConfig> {\n\tconst paths = resolveLiveCompactionPaths(cwd, agentDir);\n\tconst scopedPaths = getScopedPaths(paths, scope);\n\tconst scopedConfig = await readTextFileIfExists(scopedPaths.configPath);\n\tif (scopedConfig.exists) {\n\t\treturn loadScopedConfig(scope, cwd, agentDir);\n\t}\n\tif (scope === 'project') {\n\t\treturn loadEffectiveConfig(cwd, agentDir);\n\t}\n\treturn structuredClone(DEFAULT_CONFIG);\n}\n\nexport async function saveScopedConfig(\n\tscope: ConfigScope,\n\tconfig: LiveCompactionConfig,\n\tcwd?: string | null,\n\tagentDir = getAgentDir(),\n): Promise<string> {\n\tconst scopedPaths = getScopedPaths(resolveLiveCompactionPaths(cwd, agentDir), scope);\n\tconst normalized = parseConfig(config);\n\tawait writeTextFile(scopedPaths.configPath, `${JSON.stringify(normalized, null, '\\t')}\\n`);\n\treturn scopedPaths.configPath;\n}\n\nexport async function loadEffectiveConfig(\n\tcwd?: string | null,\n\tagentDir = getAgentDir(),\n): Promise<LiveCompactionConfig> {\n\tconst paths = resolveLiveCompactionPaths(cwd, agentDir);\n\tconst projectConfig = paths.project\n\t\t? await readTextFileIfExists(paths.project.configPath)\n\t\t: { exists: false };\n\tif (projectConfig.exists) {\n\t\treturn loadScopedConfig('project', cwd, agentDir);\n\t}\n\treturn loadScopedConfig('global', cwd, agentDir);\n}\n\nexport async function scopeHasLocalOverrides(\n\tscope: ConfigScope,\n\tcwd?: string | null,\n\tagentDir = getAgentDir(),\n): Promise<boolean> {\n\tconst scopedPaths = getScopedPaths(resolveLiveCompactionPaths(cwd, agentDir), scope);\n\tconst checks = await Promise.all([\n\t\treadTextFileIfExists(scopedPaths.configPath),\n\t\treadTextFileIfExists(scopedPaths.compactionPromptPath),\n\t\treadTextFileIfExists(scopedPaths.branchSummaryPromptPath),\n\t]);\n\treturn checks.some((entry) => entry.exists);\n}\n\nexport async function resetLiveCompactionScope(\n\tscope: ConfigScope,\n\tcwd?: string | null,\n\tagentDir = getAgentDir(),\n): Promise<string[]> {\n\tconst scopedPaths = getScopedPaths(resolveLiveCompactionPaths(cwd, agentDir), scope);\n\tconst removedPaths = [\n\t\tscopedPaths.configPath,\n\t\tscopedPaths.compactionPromptPath,\n\t\tscopedPaths.branchSummaryPromptPath,\n\t];\n\tawait Promise.all(removedPaths.map((filePath) => rm(filePath, { force: true })));\n\treturn removedPaths;\n}\n"],"mappings":";;;;;;AAcA,MAAM,gBAAgB,KAAK,QAAQ,cAAc,OAAO,KAAK,GAAG,CAAC;AACjE,MAAa,mBAAmB;AAChC,MAAa,8BAA8B;AAC3C,MAAa,kCAAkC;AAE/C,eAAsB,qBACrB,UAC8C;CAC9C,IAAI;EACH,OAAO;GAAE,QAAQ;GAAM,MAAM,MAAM,SAAS,UAAU,MAAM;EAAE;CAC/D,SAAS,OAAO;EAEf,IADc,MAA4B,SAC7B,UAAU,OAAO,EAAE,QAAQ,MAAM;EAC9C,MAAM;CACP;AACD;AAEA,eAAsB,cAAc,UAAkB,MAA6B;CAClF,MAAM,UAAU,GAAG,SAAS;CAC5B,IAAI;EACH,MAAM,MAAM,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;EACvD,MAAM,UAAU,SAAS,MAAM,MAAM;EACrC,MAAM,OAAO,SAAS,QAAQ;CAC/B,SAAS,OAAO;EACf,MAAM,GAAG,SAAS,EAAE,OAAO,KAAK,CAAC,CAAC,CAAC,YAAY,KAAA,CAAS;EACxD,MAAM;CACP;AACD;AAEA,SAAgB,2BACf,KACA,WAAW,YAAY,GACD;CACtB,MAAM,aAAa,KAAK,KAAK,UAAU,cAAc,iBAAiB;CACtE,MAAM,cAAc,MAAM,KAAK,KAAK,KAAK,OAAO,cAAc,iBAAiB,IAAI,KAAA;CAEnF,OAAO;EACN,QAAQ;GACP,OAAO;GACP,SAAS;GACT,YAAY,KAAK,KAAK,YAAY,gBAAgB;GAClD,sBAAsB,KAAK,KAAK,YAAY,2BAA2B;GACvE,yBAAyB,KAAK,KAAK,YAAY,+BAA+B;EAC/E;EACA,GAAI,cACD,EACA,SAAS;GACR,OAAO;GACP,SAAS;GACT,YAAY,KAAK,KAAK,aAAa,gBAAgB;GACnD,sBAAsB,KAAK,KAAK,aAAa,2BAA2B;GACxE,yBAAyB,KAAK,KAAK,aAAa,+BAA+B;EAChF,EACD,IACC,CAAC;CACL;AACD;AAEA,SAAgB,eAAe,OAA4B,OAAoB;CAC9E,IAAI,UAAU,WAAW;EACxB,IAAI,CAAC,MAAM,SACV,MAAM,IAAI,MAAM,oDAAoD;EAErE,OAAO,MAAM;CACd;CACA,OAAO,MAAM;AACd;AAEA,eAAsB,WAAW,eAAe,eAA8C;CAC7F,MAAM,aAAa,KAAK,KAAK,cAAc,gBAAgB;CAC3D,IAAI;EACH,MAAM,MAAM,MAAM,SAAS,YAAY,MAAM;EAC7C,OAAO,YAAY,KAAK,MAAM,GAAG,CAAY;CAC9C,SAAS,OAAO;EAEf,IADc,MAA4B,SAC7B,UAAU,OAAO,gBAAgB,cAAc;EAC5D,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACrE,MAAM,IAAI,MAAM,8CAA8C,WAAW,IAAI,SAAS;CACvF;AACD;AAEA,eAAsB,iBACrB,OACA,KACA,WAAW,YAAY,GACS;CAChC,MAAM,cAAc,eAAe,2BAA2B,KAAK,QAAQ,GAAG,KAAK;CACnF,IAAI;EACH,MAAM,MAAM,MAAM,SAAS,YAAY,YAAY,MAAM;EACzD,OAAO,YAAY,KAAK,MAAM,GAAG,CAAY;CAC9C,SAAS,OAAO;EAEf,IADc,MAA4B,SAC7B,UAAU,OAAO,gBAAgB,cAAc;EAC5D,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACrE,MAAM,IAAI,MACT,8CAA8C,YAAY,WAAW,IAAI,SAC1E;CACD;AACD;AAEA,eAAsB,yBACrB,OACA,KACA,WAAW,YAAY,GACS;CAIhC,KAAI,MADuB,qBADP,eADN,2BAA2B,KAAK,QACP,GAAG,KACgB,CAAC,CAAC,UAAU,EAAA,CACrD,QAChB,OAAO,iBAAiB,OAAO,KAAK,QAAQ;CAE7C,IAAI,UAAU,WACb,OAAO,oBAAoB,KAAK,QAAQ;CAEzC,OAAO,gBAAgB,cAAc;AACtC;AAEA,eAAsB,iBACrB,OACA,QACA,KACA,WAAW,YAAY,GACL;CAClB,MAAM,cAAc,eAAe,2BAA2B,KAAK,QAAQ,GAAG,KAAK;CACnF,MAAM,aAAa,YAAY,MAAM;CACrC,MAAM,cAAc,YAAY,YAAY,GAAG,KAAK,UAAU,YAAY,MAAM,GAAI,EAAE,GAAG;CACzF,OAAO,YAAY;AACpB;AAEA,eAAsB,oBACrB,KACA,WAAW,YAAY,GACS;CAChC,MAAM,QAAQ,2BAA2B,KAAK,QAAQ;CAItD,KAHsB,MAAM,UACzB,MAAM,qBAAqB,MAAM,QAAQ,UAAU,IACnD,EAAE,QAAQ,MAAM,EAAA,CACD,QACjB,OAAO,iBAAiB,WAAW,KAAK,QAAQ;CAEjD,OAAO,iBAAiB,UAAU,KAAK,QAAQ;AAChD;AAEA,eAAsB,uBACrB,OACA,KACA,WAAW,YAAY,GACJ;CACnB,MAAM,cAAc,eAAe,2BAA2B,KAAK,QAAQ,GAAG,KAAK;CAMnF,QAAO,MALc,QAAQ,IAAI;EAChC,qBAAqB,YAAY,UAAU;EAC3C,qBAAqB,YAAY,oBAAoB;EACrD,qBAAqB,YAAY,uBAAuB;CACzD,CAAC,EAAA,CACa,MAAM,UAAU,MAAM,MAAM;AAC3C;AAEA,eAAsB,yBACrB,OACA,KACA,WAAW,YAAY,GACH;CACpB,MAAM,cAAc,eAAe,2BAA2B,KAAK,QAAQ,GAAG,KAAK;CACnF,MAAM,eAAe;EACpB,YAAY;EACZ,YAAY;EACZ,YAAY;CACb;CACA,MAAM,QAAQ,IAAI,aAAa,KAAK,aAAa,GAAG,UAAU,EAAE,OAAO,KAAK,CAAC,CAAC,CAAC;CAC/E,OAAO;AACR"}