{"version":3,"file":"config-CwvQBVEc.cjs","names":["fs","parseDoomConfig","os","path","mergeDoomConfigs","parseYaml","createHash","configLeafKeys","configScopeOf","valueAtKeyPath"],"sources":["../src/services/config/index.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { parse as parseYaml } from 'yaml';\n\nimport type {\n  ConfigDiagnostic,\n  ConfigValueOrigin,\n  DoomConfig,\n  DoomConfigLayer,\n  DoomConfigLayers,\n  LenientParseResult,\n} from '../../types/config';\nimport { configLeafKeys, configScopeOf, mergeDoomConfigs, parseDoomConfig, valueAtKeyPath } from '../configPolicy';\n\nconst DOOM_DIR = '.doom';\nconst CONFIG_FILE = 'config.yaml';\nconst GLOBAL_CONFIG_DIR = '.pi';\nconst PLANS_DIR = 'plans';\n\nfunction readConfig(filePath: string): DoomConfig {\n  return fs.existsSync(filePath)\n    ? parseDoomConfig(fs.readFileSync(filePath, 'utf8'), filePath)\n    : { projectTrust: 'ask' };\n}\n\nasync function readConfigAsync(filePath: string): Promise<DoomConfig> {\n  try {\n    return parseDoomConfig(await fs.promises.readFile(filePath, 'utf8'), filePath);\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { projectTrust: 'ask' };\n    throw error;\n  }\n}\n\nexport function globalDoomConfigDirectory(homeDirectory = os.homedir()): string {\n  return path.join(homeDirectory, GLOBAL_CONFIG_DIR, DOOM_DIR);\n}\n\nexport function globalDoomConfigPath(homeDirectory = os.homedir()): string {\n  return path.join(globalDoomConfigDirectory(homeDirectory), CONFIG_FILE);\n}\n\nexport function resolvePlanningPlansDirectory(\n  configured: string | undefined,\n  repoRoot: string,\n  homeDirectory = os.homedir(),\n): string {\n  if (!configured) return path.join(homeDirectory, GLOBAL_CONFIG_DIR, PLANS_DIR);\n  if (configured === '~') return homeDirectory;\n  if (configured.startsWith('~/')) return path.resolve(homeDirectory, configured.slice(2));\n  if (configured.startsWith('~')) {\n    throw new Error(\"Doom planning plansDirectory supports only '~' or '~/' home aliases\");\n  }\n  return path.isAbsolute(configured) ? path.normalize(configured) : path.resolve(repoRoot, configured);\n}\n\nexport function repositoryDoomConfigPath(repoRoot: string): string {\n  return path.join(repoRoot, DOOM_DIR, CONFIG_FILE);\n}\n\nexport function loadDoomConfig(repoRoot: string, homeDirectory = os.homedir()): DoomConfig {\n  return mergeDoomConfigs(\n    readConfig(globalDoomConfigPath(homeDirectory)),\n    readConfig(repositoryDoomConfigPath(repoRoot)),\n  );\n}\n\n/**\n * Loads the merged config, tolerating unknown keys in either file.\n *\n * Only `doompi sync` uses this. Everything that runs during a session keeps\n * `loadDoomConfig`, because a key a running extension cannot understand is\n * better reported than silently ignored; a build that refuses to start over\n * the same key is not.\n */\nexport function loadDoomConfigLenient(repoRoot: string, homeDirectory = os.homedir()): LenientParseResult {\n  const read = (filePath: string): LenientParseResult =>\n    fs.existsSync(filePath)\n      ? parseDoomConfig(fs.readFileSync(filePath, 'utf8'), filePath, { lenient: true })\n      : { config: { projectTrust: 'ask' }, diagnostics: [] };\n  const globalConfig = read(globalDoomConfigPath(homeDirectory));\n  const repositoryConfig = read(repositoryDoomConfigPath(repoRoot));\n  const diagnostics: ConfigDiagnostic[] = [...globalConfig.diagnostics, ...repositoryConfig.diagnostics];\n  // mergeDoomConfigs stays strict on purpose: its errors are cross-file scope\n  // violations on known keys, which is a different fault from an unknown key.\n  return { config: mergeDoomConfigs(globalConfig.config, repositoryConfig.config), diagnostics };\n}\n\nexport async function loadDoomConfigAsync(repoRoot: string, homeDirectory = os.homedir()): Promise<DoomConfig> {\n  const globalConfig = await readConfigAsync(globalDoomConfigPath(homeDirectory));\n  const repositoryConfig = await readConfigAsync(repositoryDoomConfigPath(repoRoot));\n  return mergeDoomConfigs(globalConfig, repositoryConfig);\n}\n\n/**\n * Reads one config file as a layer: what it is, what it sets, and the bytes it\n * held. A file that does not exist is a layer that sets nothing, which is a\n * normal state rather than an error.\n */\nfunction readLayer(filePath: string): DoomConfigLayer {\n  let text: string;\n  try {\n    text = fs.readFileSync(filePath, 'utf8');\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n      return { filePath, exists: false, hash: '', keys: [] };\n    }\n    throw error;\n  }\n  // The raw document, not the parsed config: this is the one read that has to\n  // distinguish a key the file set from a key the parser defaulted.\n  const document = (parseYaml(text) ?? {}) as unknown;\n  return {\n    filePath,\n    exists: true,\n    hash: createHash('sha256').update(text, 'utf8').digest('hex'),\n    keys: configLeafKeys(document),\n  };\n}\n\n/**\n * The two config files kept apart, with the merge alongside them.\n *\n * `repoRoot` is optional because the global file is complete on its own: a\n * settings surface with no repository in view still has settings to show, and\n * demanding one would leave it with nothing to render rather than with the\n * global half.\n *\n * The origin answer respects the merge's own per-field rules rather than simply\n * preferring the repository: `editor` is read from the global side whatever the\n * repository says, and `projectTrust` from the repository side whatever the\n * global one says. Reporting a file as the source of a value the merge discards\n * is the exact confusion this exists to remove.\n */\nexport function loadDoomConfigLayers(repoRoot: string | undefined, homeDirectory = os.homedir()): DoomConfigLayers {\n  const globalLayer = readLayer(globalDoomConfigPath(homeDirectory));\n  const repositoryLayer =\n    repoRoot === undefined\n      ? { filePath: '', exists: false, hash: '', keys: [] }\n      : readLayer(repositoryDoomConfigPath(repoRoot));\n  // With no repository there is nothing to merge, so the global file stands as\n  // the effective config on its own.\n  const effective =\n    repoRoot === undefined\n      ? mergeDoomConfigs(readConfig(globalDoomConfigPath(homeDirectory)), { projectTrust: 'ask' })\n      : loadDoomConfig(repoRoot, homeDirectory);\n  const sets = (layer: DoomConfigLayer, keyPath: readonly string[]): boolean => {\n    const dotted = keyPath.join('.');\n    return layer.keys.some((key) => key === dotted || key.startsWith(`${dotted}.`));\n  };\n  return {\n    globalFile: globalLayer,\n    repositoryFile: repositoryLayer,\n    effective,\n    originOf(keyPath: readonly string[]): ConfigValueOrigin {\n      const scope = configScopeOf(keyPath);\n      if (scope !== 'global' && sets(repositoryLayer, keyPath)) return 'repository';\n      if (scope !== 'repository' && sets(globalLayer, keyPath)) return 'global';\n      return 'default';\n    },\n    valueAt: (keyPath: readonly string[]) => valueAtKeyPath(effective, keyPath),\n  };\n}\n"],"mappings":";;;;;;;;;;;AAiBA,MAAM,WAAW;AACjB,MAAM,cAAc;AACpB,MAAM,oBAAoB;AAC1B,MAAM,YAAY;AAElB,SAAS,WAAW,UAA8B;CAChD,OAAOA,QAAAA,QAAG,WAAW,QAAQ,IACzBC,qBAAAA,gBAAgBD,QAAAA,QAAG,aAAa,UAAU,MAAM,GAAG,QAAQ,IAC3D,EAAE,cAAc,MAAM;AAC5B;AAEA,eAAe,gBAAgB,UAAuC;CACpE,IAAI;EACF,OAAOC,qBAAAA,gBAAgB,MAAMD,QAAAA,QAAG,SAAS,SAAS,UAAU,MAAM,GAAG,QAAQ;CAC/E,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,UAAU,OAAO,EAAE,cAAc,MAAM;EACrF,MAAM;CACR;AACF;AAEA,SAAgB,0BAA0B,gBAAgBE,QAAAA,QAAG,QAAQ,GAAW;CAC9E,OAAOC,UAAAA,QAAK,KAAK,eAAe,mBAAmB,QAAQ;AAC7D;AAEA,SAAgB,qBAAqB,gBAAgBD,QAAAA,QAAG,QAAQ,GAAW;CACzE,OAAOC,UAAAA,QAAK,KAAK,0BAA0B,aAAa,GAAG,WAAW;AACxE;AAEA,SAAgB,8BACd,YACA,UACA,gBAAgBD,QAAAA,QAAG,QAAQ,GACnB;CACR,IAAI,CAAC,YAAY,OAAOC,UAAAA,QAAK,KAAK,eAAe,mBAAmB,SAAS;CAC7E,IAAI,eAAe,KAAK,OAAO;CAC/B,IAAI,WAAW,WAAW,IAAI,GAAG,OAAOA,UAAAA,QAAK,QAAQ,eAAe,WAAW,MAAM,CAAC,CAAC;CACvF,IAAI,WAAW,WAAW,GAAG,GAC3B,MAAM,IAAI,MAAM,qEAAqE;CAEvF,OAAOA,UAAAA,QAAK,WAAW,UAAU,IAAIA,UAAAA,QAAK,UAAU,UAAU,IAAIA,UAAAA,QAAK,QAAQ,UAAU,UAAU;AACrG;AAEA,SAAgB,yBAAyB,UAA0B;CACjE,OAAOA,UAAAA,QAAK,KAAK,UAAU,UAAU,WAAW;AAClD;AAEA,SAAgB,eAAe,UAAkB,gBAAgBD,QAAAA,QAAG,QAAQ,GAAe;CACzF,OAAOE,qBAAAA,iBACL,WAAW,qBAAqB,aAAa,CAAC,GAC9C,WAAW,yBAAyB,QAAQ,CAAC,CAC/C;AACF;;;;;;;;;AAUA,SAAgB,sBAAsB,UAAkB,gBAAgBF,QAAAA,QAAG,QAAQ,GAAuB;CACxG,MAAM,QAAQ,aACZF,QAAAA,QAAG,WAAW,QAAQ,IAClBC,qBAAAA,gBAAgBD,QAAAA,QAAG,aAAa,UAAU,MAAM,GAAG,UAAU,EAAE,SAAS,KAAK,CAAC,IAC9E;EAAE,QAAQ,EAAE,cAAc,MAAM;EAAG,aAAa,CAAC;CAAE;CACzD,MAAM,eAAe,KAAK,qBAAqB,aAAa,CAAC;CAC7D,MAAM,mBAAmB,KAAK,yBAAyB,QAAQ,CAAC;CAChE,MAAM,cAAkC,CAAC,GAAG,aAAa,aAAa,GAAG,iBAAiB,WAAW;CAGrG,OAAO;EAAE,QAAQI,qBAAAA,iBAAiB,aAAa,QAAQ,iBAAiB,MAAM;EAAG;CAAY;AAC/F;AAEA,eAAsB,oBAAoB,UAAkB,gBAAgBF,QAAAA,QAAG,QAAQ,GAAwB;CAC7G,MAAM,eAAe,MAAM,gBAAgB,qBAAqB,aAAa,CAAC;CAC9E,MAAM,mBAAmB,MAAM,gBAAgB,yBAAyB,QAAQ,CAAC;CACjF,OAAOE,qBAAAA,iBAAiB,cAAc,gBAAgB;AACxD;;;;;;AAOA,SAAS,UAAU,UAAmC;CACpD,IAAI;CACJ,IAAI;EACF,OAAOJ,QAAAA,QAAG,aAAa,UAAU,MAAM;CACzC,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,UAC5C,OAAO;GAAE;GAAU,QAAQ;GAAO,MAAM;GAAI,MAAM,CAAC;EAAE;EAEvD,MAAM;CACR;CAGA,MAAM,YAAA,GAAYK,KAAAA,MAAAA,CAAU,IAAI,KAAK,CAAC;CACtC,OAAO;EACL;EACA,QAAQ;EACR,OAAA,GAAMC,YAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,MAAM,CAAC,CAAC,OAAO,KAAK;EAC5D,MAAMC,qBAAAA,eAAe,QAAQ;CAC/B;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,qBAAqB,UAA8B,gBAAgBL,QAAAA,QAAG,QAAQ,GAAqB;CACjH,MAAM,cAAc,UAAU,qBAAqB,aAAa,CAAC;CACjE,MAAM,kBACJ,aAAa,KAAA,IACT;EAAE,UAAU;EAAI,QAAQ;EAAO,MAAM;EAAI,MAAM,CAAC;CAAE,IAClD,UAAU,yBAAyB,QAAQ,CAAC;CAGlD,MAAM,YACJ,aAAa,KAAA,IACTE,qBAAAA,iBAAiB,WAAW,qBAAqB,aAAa,CAAC,GAAG,EAAE,cAAc,MAAM,CAAC,IACzF,eAAe,UAAU,aAAa;CAC5C,MAAM,QAAQ,OAAwB,YAAwC;EAC5E,MAAM,SAAS,QAAQ,KAAK,GAAG;EAC/B,OAAO,MAAM,KAAK,MAAM,QAAQ,QAAQ,UAAU,IAAI,WAAW,GAAG,OAAO,EAAE,CAAC;CAChF;CACA,OAAO;EACL,YAAY;EACZ,gBAAgB;EAChB;EACA,SAAS,SAA+C;GACtD,MAAM,QAAQI,qBAAAA,cAAc,OAAO;GACnC,IAAI,UAAU,YAAY,KAAK,iBAAiB,OAAO,GAAG,OAAO;GACjE,IAAI,UAAU,gBAAgB,KAAK,aAAa,OAAO,GAAG,OAAO;GACjE,OAAO;EACT;EACA,UAAU,YAA+BC,qBAAAA,eAAe,WAAW,OAAO;CAC5E;AACF"}