@agimon-ai/doompi-config 0.0.1-alpha.39 → 0.0.1-alpha.40

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.
Files changed (47) hide show
  1. package/dist/adapters/config.cjs +1 -1
  2. package/dist/adapters/config.cjs.map +1 -1
  3. package/dist/adapters/config.d.cts +17 -2
  4. package/dist/adapters/config.d.cts.map +1 -1
  5. package/dist/adapters/config.d.mts +17 -2
  6. package/dist/adapters/config.d.mts.map +1 -1
  7. package/dist/adapters/config.mjs +1 -1
  8. package/dist/adapters/config.mjs.map +1 -1
  9. package/dist/adapters/configWriter.cjs +1 -1
  10. package/dist/adapters/configWriter.cjs.map +1 -1
  11. package/dist/adapters/configWriter.d.cts +11 -3
  12. package/dist/adapters/configWriter.d.cts.map +1 -1
  13. package/dist/adapters/configWriter.d.mts +11 -3
  14. package/dist/adapters/configWriter.d.mts.map +1 -1
  15. package/dist/adapters/configWriter.mjs +1 -1
  16. package/dist/adapters/configWriter.mjs.map +1 -1
  17. package/dist/adapters/pi/configExtension.cjs +1 -1
  18. package/dist/adapters/pi/configExtension.cjs.map +1 -1
  19. package/dist/adapters/pi/configExtension.d.cts.map +1 -1
  20. package/dist/adapters/pi/configExtension.d.mts.map +1 -1
  21. package/dist/adapters/pi/configExtension.mjs +1 -1
  22. package/dist/adapters/pi/configExtension.mjs.map +1 -1
  23. package/dist/config.cjs +1 -1
  24. package/dist/config.d.cts +4 -3
  25. package/dist/config.d.mts +4 -3
  26. package/dist/config.mjs +1 -1
  27. package/dist/configWriter.d.cts +2 -2
  28. package/dist/configWriter.d.mts +2 -2
  29. package/dist/index.cjs +1 -1
  30. package/dist/index.d.cts +5 -5
  31. package/dist/index.d.mts +5 -5
  32. package/dist/index.mjs +1 -1
  33. package/dist/services/configPolicy.cjs +1 -1
  34. package/dist/services/configPolicy.cjs.map +1 -1
  35. package/dist/services/configPolicy.d.cts +38 -1
  36. package/dist/services/configPolicy.d.cts.map +1 -1
  37. package/dist/services/configPolicy.d.mts +38 -1
  38. package/dist/services/configPolicy.d.mts.map +1 -1
  39. package/dist/services/configPolicy.mjs +1 -1
  40. package/dist/services/configPolicy.mjs.map +1 -1
  41. package/dist/types/config.d.cts +34 -1
  42. package/dist/types/config.d.cts.map +1 -1
  43. package/dist/types/config.d.mts +34 -1
  44. package/dist/types/config.d.mts.map +1 -1
  45. package/dist/types.d.cts +2 -2
  46. package/dist/types.d.mts +2 -2
  47. package/package.json +3 -3
@@ -1,2 +1,2 @@
1
- const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../services/configPolicy.cjs");let n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);let i=require("node:os");i=e.__toESM(i,1);const a=`.doom`,o=`config.yaml`;function s(e){return n.default.existsSync(e)?t.parseDoomConfig(n.default.readFileSync(e,`utf8`),e):{projectTrust:`ask`}}async function c(e){try{return t.parseDoomConfig(await n.default.promises.readFile(e,`utf8`),e)}catch(e){if(e.code===`ENOENT`)return{projectTrust:`ask`};throw e}}function l(e=i.default.homedir()){return r.default.join(e,`.pi`,a)}function u(e=i.default.homedir()){return r.default.join(l(e),o)}function d(e,t,n=i.default.homedir()){if(!e)return r.default.join(n,`.pi`,`plans`);if(e===`~`)return n;if(e.startsWith(`~/`))return r.default.resolve(n,e.slice(2));if(e.startsWith(`~`))throw Error(`Doom planning plansDirectory supports only '~' or '~/' home aliases`);return r.default.isAbsolute(e)?r.default.normalize(e):r.default.resolve(t,e)}function f(e){return r.default.join(e,a,o)}function p(e,n=i.default.homedir()){return t.mergeDoomConfigs(s(u(n)),s(f(e)))}async function m(e,n=i.default.homedir()){let r=await c(u(n)),a=await c(f(e));return t.mergeDoomConfigs(r,a)}exports.globalDoomConfigDirectory=l,exports.globalDoomConfigPath=u,exports.loadDoomConfig=p,exports.loadDoomConfigAsync=m,exports.repositoryDoomConfigPath=f,exports.resolvePlanningPlansDirectory=d;
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../services/configPolicy.cjs");let n=require("node:crypto"),r=require("node:fs");r=e.__toESM(r,1);let i=require("node:path");i=e.__toESM(i,1);let a=require("node:os");a=e.__toESM(a,1);let o=require("yaml");const s=`.doom`,c=`config.yaml`;function l(e){return r.default.existsSync(e)?t.parseDoomConfig(r.default.readFileSync(e,`utf8`),e):{projectTrust:`ask`}}async function u(e){try{return t.parseDoomConfig(await r.default.promises.readFile(e,`utf8`),e)}catch(e){if(e.code===`ENOENT`)return{projectTrust:`ask`};throw e}}function d(e=a.default.homedir()){return i.default.join(e,`.pi`,s)}function f(e=a.default.homedir()){return i.default.join(d(e),c)}function p(e,t,n=a.default.homedir()){if(!e)return i.default.join(n,`.pi`,`plans`);if(e===`~`)return n;if(e.startsWith(`~/`))return i.default.resolve(n,e.slice(2));if(e.startsWith(`~`))throw Error(`Doom planning plansDirectory supports only '~' or '~/' home aliases`);return i.default.isAbsolute(e)?i.default.normalize(e):i.default.resolve(t,e)}function m(e){return i.default.join(e,s,c)}function h(e,n=a.default.homedir()){return t.mergeDoomConfigs(l(f(n)),l(m(e)))}async function g(e,n=a.default.homedir()){let r=await u(f(n)),i=await u(m(e));return t.mergeDoomConfigs(r,i)}function _(e){let i;try{i=r.default.readFileSync(e,`utf8`)}catch(t){if(t.code===`ENOENT`)return{filePath:e,exists:!1,hash:``,keys:[]};throw t}let a=(0,o.parse)(i)??{};return{filePath:e,exists:!0,hash:(0,n.createHash)(`sha256`).update(i,`utf8`).digest(`hex`),keys:t.configLeafKeys(a)}}function v(e,n=a.default.homedir()){let r=_(f(n)),i=e===void 0?{filePath:``,exists:!1,hash:``,keys:[]}:_(m(e)),o=e===void 0?t.mergeDoomConfigs(l(f(n)),{projectTrust:`ask`}):h(e,n),s=(e,t)=>{let n=t.join(`.`);return e.keys.some(e=>e===n||e.startsWith(`${n}.`))};return{globalFile:r,repositoryFile:i,effective:o,originOf(e){let n=t.configScopeOf(e);return n!==`global`&&s(i,e)?`repository`:n!==`repository`&&s(r,e)?`global`:`default`},valueAt:e=>t.valueAtKeyPath(o,e)}}exports.globalDoomConfigDirectory=d,exports.globalDoomConfigPath=f,exports.loadDoomConfig=h,exports.loadDoomConfigAsync=g,exports.loadDoomConfigLayers=v,exports.repositoryDoomConfigPath=m,exports.resolvePlanningPlansDirectory=p;
2
2
  //# sourceMappingURL=config.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.cjs","names":["fs","parseDoomConfig","os","path","mergeDoomConfigs"],"sources":["../../src/adapters/config.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { mergeDoomConfigs, parseDoomConfig } from '../services/configPolicy.ts';\nimport type { DoomConfig } from '../types/config.ts';\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\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"],"mappings":"+NAMA,MAAM,EAAW,QACX,EAAc,cAIpB,SAAS,EAAW,EAA8B,CAChD,OAAOA,EAAAA,QAAG,WAAW,CAAQ,EACzBC,EAAAA,gBAAgBD,EAAAA,QAAG,aAAa,EAAU,MAAM,EAAG,CAAQ,EAC3D,CAAE,aAAc,KAAM,CAC5B,CAEA,eAAe,EAAgB,EAAuC,CACpE,GAAI,CACF,OAAOC,EAAAA,gBAAgB,MAAMD,EAAAA,QAAG,SAAS,SAAS,EAAU,MAAM,EAAG,CAAQ,CAC/E,OAAS,EAAO,CACd,GAAK,EAAgC,OAAS,SAAU,MAAO,CAAE,aAAc,KAAM,EACrF,MAAM,CACR,CACF,CAEA,SAAgB,EAA0B,EAAgBE,EAAAA,QAAG,QAAQ,EAAW,CAC9E,OAAOC,EAAAA,QAAK,KAAK,EAAe,MAAmB,CAAQ,CAC7D,CAEA,SAAgB,EAAqB,EAAgBD,EAAAA,QAAG,QAAQ,EAAW,CACzE,OAAOC,EAAAA,QAAK,KAAK,EAA0B,CAAa,EAAG,CAAW,CACxE,CAEA,SAAgB,EACd,EACA,EACA,EAAgBD,EAAAA,QAAG,QAAQ,EACnB,CACR,GAAI,CAAC,EAAY,OAAOC,EAAAA,QAAK,KAAK,EAAe,MAAmB,OAAS,EAC7E,GAAI,IAAe,IAAK,OAAO,EAC/B,GAAI,EAAW,WAAW,IAAI,EAAG,OAAOA,EAAAA,QAAK,QAAQ,EAAe,EAAW,MAAM,CAAC,CAAC,EACvF,GAAI,EAAW,WAAW,GAAG,EAC3B,MAAU,MAAM,qEAAqE,EAEvF,OAAOA,EAAAA,QAAK,WAAW,CAAU,EAAIA,EAAAA,QAAK,UAAU,CAAU,EAAIA,EAAAA,QAAK,QAAQ,EAAU,CAAU,CACrG,CAEA,SAAgB,EAAyB,EAA0B,CACjE,OAAOA,EAAAA,QAAK,KAAK,EAAU,EAAU,CAAW,CAClD,CAEA,SAAgB,EAAe,EAAkB,EAAgBD,EAAAA,QAAG,QAAQ,EAAe,CACzF,OAAOE,EAAAA,iBACL,EAAW,EAAqB,CAAa,CAAC,EAC9C,EAAW,EAAyB,CAAQ,CAAC,CAC/C,CACF,CAEA,eAAsB,EAAoB,EAAkB,EAAgBF,EAAAA,QAAG,QAAQ,EAAwB,CAC7G,IAAM,EAAe,MAAM,EAAgB,EAAqB,CAAa,CAAC,EACxE,EAAmB,MAAM,EAAgB,EAAyB,CAAQ,CAAC,EACjF,OAAOE,EAAAA,iBAAiB,EAAc,CAAgB,CACxD"}
1
+ {"version":3,"file":"config.cjs","names":["fs","parseDoomConfig","os","path","mergeDoomConfigs","parseYaml","createHash","configLeafKeys","configScopeOf","valueAtKeyPath"],"sources":["../../src/adapters/config.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport {\n configLeafKeys,\n configScopeOf,\n mergeDoomConfigs,\n parseDoomConfig,\n valueAtKeyPath,\n} from '../services/configPolicy.ts';\nimport type { DoomConfig } from '../types/config.ts';\nimport type { ConfigValueOrigin, DoomConfigLayer, DoomConfigLayers } from '../types/config.ts';\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\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":"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"}
@@ -1,4 +1,4 @@
1
- import { DoomConfig } from "../types/config.cjs";
1
+ import { DoomConfig, DoomConfigLayers } from "../types/config.cjs";
2
2
  //#region src/adapters/config.d.ts
3
3
  declare function globalDoomConfigDirectory(homeDirectory?: string): string;
4
4
  declare function globalDoomConfigPath(homeDirectory?: string): string;
@@ -6,6 +6,21 @@ declare function resolvePlanningPlansDirectory(configured: string | undefined, r
6
6
  declare function repositoryDoomConfigPath(repoRoot: string): string;
7
7
  declare function loadDoomConfig(repoRoot: string, homeDirectory?: string): DoomConfig;
8
8
  declare function loadDoomConfigAsync(repoRoot: string, homeDirectory?: string): Promise<DoomConfig>;
9
+ /**
10
+ * The two config files kept apart, with the merge alongside them.
11
+ *
12
+ * `repoRoot` is optional because the global file is complete on its own: a
13
+ * settings surface with no repository in view still has settings to show, and
14
+ * demanding one would leave it with nothing to render rather than with the
15
+ * global half.
16
+ *
17
+ * The origin answer respects the merge's own per-field rules rather than simply
18
+ * preferring the repository: `editor` is read from the global side whatever the
19
+ * repository says, and `projectTrust` from the repository side whatever the
20
+ * global one says. Reporting a file as the source of a value the merge discards
21
+ * is the exact confusion this exists to remove.
22
+ */
23
+ declare function loadDoomConfigLayers(repoRoot: string | undefined, homeDirectory?: string): DoomConfigLayers;
9
24
  //#endregion
10
- export { globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, loadDoomConfigAsync, repositoryDoomConfigPath, resolvePlanningPlansDirectory };
25
+ export { globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, loadDoomConfigAsync, loadDoomConfigLayers, repositoryDoomConfigPath, resolvePlanningPlansDirectory };
11
26
  //# sourceMappingURL=config.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.cts","names":[],"sources":["../../src/adapters/config.ts"],"mappings":";;iBA0BgB,0BAA0B;iBAI1B,qBAAqB;iBAIrB,8BACd,gCACA,kBACA;iBAWc,yBAAyB;iBAIzB,eAAe,kBAAkB,yBAA+B;iBAO1D,oBAAoB,kBAAkB,yBAA+B,QAAQ"}
1
+ {"version":3,"file":"config.d.cts","names":[],"sources":["../../src/adapters/config.ts"],"mappings":";;iBAmCgB,0BAA0B;iBAI1B,qBAAqB;iBAIrB,8BACd,gCACA,kBACA;iBAWc,yBAAyB;iBAIzB,eAAe,kBAAkB,yBAA+B;iBAO1D,oBAAoB,kBAAkB,yBAA+B,QAAQ;;;;;;;;;;;;;;;iBA8CnF,qBAAqB,8BAA8B,yBAA+B"}
@@ -1,4 +1,4 @@
1
- import { DoomConfig } from "../types/config.mjs";
1
+ import { DoomConfig, DoomConfigLayers } from "../types/config.mjs";
2
2
  //#region src/adapters/config.d.ts
3
3
  declare function globalDoomConfigDirectory(homeDirectory?: string): string;
4
4
  declare function globalDoomConfigPath(homeDirectory?: string): string;
@@ -6,6 +6,21 @@ declare function resolvePlanningPlansDirectory(configured: string | undefined, r
6
6
  declare function repositoryDoomConfigPath(repoRoot: string): string;
7
7
  declare function loadDoomConfig(repoRoot: string, homeDirectory?: string): DoomConfig;
8
8
  declare function loadDoomConfigAsync(repoRoot: string, homeDirectory?: string): Promise<DoomConfig>;
9
+ /**
10
+ * The two config files kept apart, with the merge alongside them.
11
+ *
12
+ * `repoRoot` is optional because the global file is complete on its own: a
13
+ * settings surface with no repository in view still has settings to show, and
14
+ * demanding one would leave it with nothing to render rather than with the
15
+ * global half.
16
+ *
17
+ * The origin answer respects the merge's own per-field rules rather than simply
18
+ * preferring the repository: `editor` is read from the global side whatever the
19
+ * repository says, and `projectTrust` from the repository side whatever the
20
+ * global one says. Reporting a file as the source of a value the merge discards
21
+ * is the exact confusion this exists to remove.
22
+ */
23
+ declare function loadDoomConfigLayers(repoRoot: string | undefined, homeDirectory?: string): DoomConfigLayers;
9
24
  //#endregion
10
- export { globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, loadDoomConfigAsync, repositoryDoomConfigPath, resolvePlanningPlansDirectory };
25
+ export { globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, loadDoomConfigAsync, loadDoomConfigLayers, repositoryDoomConfigPath, resolvePlanningPlansDirectory };
11
26
  //# sourceMappingURL=config.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.mts","names":[],"sources":["../../src/adapters/config.ts"],"mappings":";;iBA0BgB,0BAA0B;iBAI1B,qBAAqB;iBAIrB,8BACd,gCACA,kBACA;iBAWc,yBAAyB;iBAIzB,eAAe,kBAAkB,yBAA+B;iBAO1D,oBAAoB,kBAAkB,yBAA+B,QAAQ"}
1
+ {"version":3,"file":"config.d.mts","names":[],"sources":["../../src/adapters/config.ts"],"mappings":";;iBAmCgB,0BAA0B;iBAI1B,qBAAqB;iBAIrB,8BACd,gCACA,kBACA;iBAWc,yBAAyB;iBAIzB,eAAe,kBAAkB,yBAA+B;iBAO1D,oBAAoB,kBAAkB,yBAA+B,QAAQ;;;;;;;;;;;;;;;iBA8CnF,qBAAqB,8BAA8B,yBAA+B"}
@@ -1,2 +1,2 @@
1
- import{mergeDoomConfigs as e,parseDoomConfig as t}from"../services/configPolicy.mjs";import n from"node:fs";import r from"node:path";import i from"node:os";const a=`.doom`,o=`config.yaml`;function s(e){return n.existsSync(e)?t(n.readFileSync(e,`utf8`),e):{projectTrust:`ask`}}async function c(e){try{return t(await n.promises.readFile(e,`utf8`),e)}catch(e){if(e.code===`ENOENT`)return{projectTrust:`ask`};throw e}}function l(e=i.homedir()){return r.join(e,`.pi`,a)}function u(e=i.homedir()){return r.join(l(e),o)}function d(e,t,n=i.homedir()){if(!e)return r.join(n,`.pi`,`plans`);if(e===`~`)return n;if(e.startsWith(`~/`))return r.resolve(n,e.slice(2));if(e.startsWith(`~`))throw Error(`Doom planning plansDirectory supports only '~' or '~/' home aliases`);return r.isAbsolute(e)?r.normalize(e):r.resolve(t,e)}function f(e){return r.join(e,a,o)}function p(t,n=i.homedir()){return e(s(u(n)),s(f(t)))}async function m(t,n=i.homedir()){let r=await c(u(n)),a=await c(f(t));return e(r,a)}export{l as globalDoomConfigDirectory,u as globalDoomConfigPath,p as loadDoomConfig,m as loadDoomConfigAsync,f as repositoryDoomConfigPath,d as resolvePlanningPlansDirectory};
1
+ import{configLeafKeys as e,configScopeOf as t,mergeDoomConfigs as n,parseDoomConfig as r,valueAtKeyPath as i}from"../services/configPolicy.mjs";import{createHash as a}from"node:crypto";import o from"node:fs";import s from"node:path";import c from"node:os";import{parse as l}from"yaml";const u=`.doom`,d=`config.yaml`;function f(e){return o.existsSync(e)?r(o.readFileSync(e,`utf8`),e):{projectTrust:`ask`}}async function p(e){try{return r(await o.promises.readFile(e,`utf8`),e)}catch(e){if(e.code===`ENOENT`)return{projectTrust:`ask`};throw e}}function m(e=c.homedir()){return s.join(e,`.pi`,u)}function h(e=c.homedir()){return s.join(m(e),d)}function g(e,t,n=c.homedir()){if(!e)return s.join(n,`.pi`,`plans`);if(e===`~`)return n;if(e.startsWith(`~/`))return s.resolve(n,e.slice(2));if(e.startsWith(`~`))throw Error(`Doom planning plansDirectory supports only '~' or '~/' home aliases`);return s.isAbsolute(e)?s.normalize(e):s.resolve(t,e)}function _(e){return s.join(e,u,d)}function v(e,t=c.homedir()){return n(f(h(t)),f(_(e)))}async function y(e,t=c.homedir()){let r=await p(h(t)),i=await p(_(e));return n(r,i)}function b(t){let n;try{n=o.readFileSync(t,`utf8`)}catch(e){if(e.code===`ENOENT`)return{filePath:t,exists:!1,hash:``,keys:[]};throw e}let r=l(n)??{};return{filePath:t,exists:!0,hash:a(`sha256`).update(n,`utf8`).digest(`hex`),keys:e(r)}}function x(e,r=c.homedir()){let a=b(h(r)),o=e===void 0?{filePath:``,exists:!1,hash:``,keys:[]}:b(_(e)),s=e===void 0?n(f(h(r)),{projectTrust:`ask`}):v(e,r),l=(e,t)=>{let n=t.join(`.`);return e.keys.some(e=>e===n||e.startsWith(`${n}.`))};return{globalFile:a,repositoryFile:o,effective:s,originOf(e){let n=t(e);return n!==`global`&&l(o,e)?`repository`:n!==`repository`&&l(a,e)?`global`:`default`},valueAt:e=>i(s,e)}}export{m as globalDoomConfigDirectory,h as globalDoomConfigPath,v as loadDoomConfig,y as loadDoomConfigAsync,x as loadDoomConfigLayers,_ as repositoryDoomConfigPath,g as resolvePlanningPlansDirectory};
2
2
  //# sourceMappingURL=config.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.mjs","names":[],"sources":["../../src/adapters/config.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { mergeDoomConfigs, parseDoomConfig } from '../services/configPolicy.ts';\nimport type { DoomConfig } from '../types/config.ts';\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\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"],"mappings":"4JAMA,MAAM,EAAW,QACX,EAAc,cAIpB,SAAS,EAAW,EAA8B,CAChD,OAAO,EAAG,WAAW,CAAQ,EACzB,EAAgB,EAAG,aAAa,EAAU,MAAM,EAAG,CAAQ,EAC3D,CAAE,aAAc,KAAM,CAC5B,CAEA,eAAe,EAAgB,EAAuC,CACpE,GAAI,CACF,OAAO,EAAgB,MAAM,EAAG,SAAS,SAAS,EAAU,MAAM,EAAG,CAAQ,CAC/E,OAAS,EAAO,CACd,GAAK,EAAgC,OAAS,SAAU,MAAO,CAAE,aAAc,KAAM,EACrF,MAAM,CACR,CACF,CAEA,SAAgB,EAA0B,EAAgB,EAAG,QAAQ,EAAW,CAC9E,OAAO,EAAK,KAAK,EAAe,MAAmB,CAAQ,CAC7D,CAEA,SAAgB,EAAqB,EAAgB,EAAG,QAAQ,EAAW,CACzE,OAAO,EAAK,KAAK,EAA0B,CAAa,EAAG,CAAW,CACxE,CAEA,SAAgB,EACd,EACA,EACA,EAAgB,EAAG,QAAQ,EACnB,CACR,GAAI,CAAC,EAAY,OAAO,EAAK,KAAK,EAAe,MAAmB,OAAS,EAC7E,GAAI,IAAe,IAAK,OAAO,EAC/B,GAAI,EAAW,WAAW,IAAI,EAAG,OAAO,EAAK,QAAQ,EAAe,EAAW,MAAM,CAAC,CAAC,EACvF,GAAI,EAAW,WAAW,GAAG,EAC3B,MAAU,MAAM,qEAAqE,EAEvF,OAAO,EAAK,WAAW,CAAU,EAAI,EAAK,UAAU,CAAU,EAAI,EAAK,QAAQ,EAAU,CAAU,CACrG,CAEA,SAAgB,EAAyB,EAA0B,CACjE,OAAO,EAAK,KAAK,EAAU,EAAU,CAAW,CAClD,CAEA,SAAgB,EAAe,EAAkB,EAAgB,EAAG,QAAQ,EAAe,CACzF,OAAO,EACL,EAAW,EAAqB,CAAa,CAAC,EAC9C,EAAW,EAAyB,CAAQ,CAAC,CAC/C,CACF,CAEA,eAAsB,EAAoB,EAAkB,EAAgB,EAAG,QAAQ,EAAwB,CAC7G,IAAM,EAAe,MAAM,EAAgB,EAAqB,CAAa,CAAC,EACxE,EAAmB,MAAM,EAAgB,EAAyB,CAAQ,CAAC,EACjF,OAAO,EAAiB,EAAc,CAAgB,CACxD"}
1
+ {"version":3,"file":"config.mjs","names":["parseYaml"],"sources":["../../src/adapters/config.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport {\n configLeafKeys,\n configScopeOf,\n mergeDoomConfigs,\n parseDoomConfig,\n valueAtKeyPath,\n} from '../services/configPolicy.ts';\nimport type { DoomConfig } from '../types/config.ts';\nimport type { ConfigValueOrigin, DoomConfigLayer, DoomConfigLayers } from '../types/config.ts';\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\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":"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"}
@@ -1,2 +1,2 @@
1
- const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../services/configPolicy.cjs");let n=require("node:crypto"),r=require("node:path");r=e.__toESM(r,1);let i=require("yaml"),a=require("node:fs/promises");a=e.__toESM(a,1);async function o(e){try{return await a.default.readFile(e,`utf8`)}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return``;throw e}}function s(e){if(typeof e!=`object`||!e)return!1;let t=e.items;return Array.isArray(t)&&t.length===0}function c(e,t){for(let n=t.length-1;n>0;--n){let r=t.slice(0,n);if(e.hasIn(r)){if(!s(e.getIn(r,!0)))return;e.deleteIn(r)}}}async function l(e,s,l={}){if(s.length===0)return;let u=await o(e),d=(0,i.parseDocument)(u,{keepSourceTokens:!0});if(d.errors.length>0)throw Error(`Could not parse Doom config at ${e}`);for(let e of s){let t=[...e.keyPath];if(e.value!==void 0){d.setIn(t,e.value);continue}d.hasIn(t)&&(d.deleteIn(t),l.prune!==!1&&c(d,t))}let f=d.toString();t.parseDoomConfig(f,e),await a.default.mkdir(r.default.dirname(e),{mode:448,recursive:!0});let p=`${e}.${process.pid}.${(0,n.randomUUID)()}.tmp`;try{await a.default.writeFile(p,f,{encoding:`utf8`,mode:384}),await a.default.rename(p,e)}catch(e){throw await a.default.rm(p,{force:!0}),e}}async function u(e,t,n){await l(e,[{keyPath:t,value:n}])}async function d(e,t){await l(e,[{keyPath:t}])}exports.setDoomConfigValue=u,exports.unsetDoomConfigValue=d,exports.writeDoomConfigValues=l;
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../services/configPolicy.cjs");let n=require("node:crypto"),r=require("node:path");r=e.__toESM(r,1);let i=require("yaml"),a=require("node:fs/promises");a=e.__toESM(a,1);function o(e){return e===`repository`?{file:420,directory:493}:{file:384,directory:448}}async function s(e){try{return await a.default.readFile(e,`utf8`)}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return``;throw e}}function c(e){if(typeof e!=`object`||!e)return!1;let t=e.items;return Array.isArray(t)&&t.length===0}function l(e,t){for(let n=t.length-1;n>0;--n){let r=t.slice(0,n);if(e.hasIn(r)){if(!c(e.getIn(r,!0)))return;e.deleteIn(r)}}}async function u(e,c,u={}){if(c.length===0)return;let d=await s(e),f=(0,i.parseDocument)(d,{keepSourceTokens:!0});if(f.errors.length>0)throw Error(`Could not parse Doom config at ${e}`);for(let e of c){let t=[...e.keyPath];if(e.value!==void 0){f.setIn(t,e.value);continue}f.hasIn(t)&&(f.deleteIn(t),u.prune!==!1&&l(f,t))}let p=f.toString();t.parseDoomConfig(p,e);let m=o(u.scope??`global`);await a.default.mkdir(r.default.dirname(e),{mode:m.directory,recursive:!0});let h=`${e}.${process.pid}.${(0,n.randomUUID)()}.tmp`;try{await a.default.writeFile(h,p,{encoding:`utf8`,mode:m.file}),await a.default.rename(h,e)}catch(e){throw await a.default.rm(h,{force:!0}),e}}async function d(e,t,n,r={}){await u(e,[{keyPath:t,value:n}],r)}async function f(e,t,n={}){await u(e,[{keyPath:t}],n)}exports.setDoomConfigValue=d,exports.unsetDoomConfigValue=f,exports.writeDoomConfigValues=u;
2
2
  //# sourceMappingURL=configWriter.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"configWriter.cjs","names":["fs","parseDocument","path","randomUUID"],"sources":["../../src/adapters/configWriter.ts"],"sourcesContent":["/**\n * Writes into the global Doom config file, one key path at a time.\n *\n * Extracted from doom-file-edit, which owned the only writer in the repo and had\n * it hard-coded to `editor.command`. Several extensions now edit their own\n * settings from the config panel, and every one of them needs the same four\n * properties: keep the user's comments, never publish a file the parser would\n * reject, never leave a half-written file behind, and apply a set of related\n * edits together.\n *\n * That last one is not a convenience. `voice.adapters.<engine>.model` accepts a\n * path or an id but not both, so switching between them is a set and an unset\n * that must land in the same write, or the file is briefly invalid.\n *\n * Atomicity is the same rename trick as `atomicJson.ts` next door, and carries\n * the same caveat: it prevents torn reads, not lost updates. Two sessions with\n * the panel open still clobber each other, because the rename only publishes\n * what the caller already computed.\n *\n * AVOID:\n * - Treating this as a lock\n * - Writing an empty string; the parser rejects blank values, so clearing a key\n * means removing it\n */\n\nimport { randomUUID } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { parseDocument } from 'yaml';\nimport { parseDoomConfig } from '../services/configPolicy.ts';\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\n\nexport interface DoomConfigEdit {\n /** Key path into the document, e.g. `['voice', 'adapters', 'mlx-whisper', 'model', 'id']`. */\n readonly keyPath: readonly string[];\n /**\n * The value to write. `undefined` removes the key.\n *\n * Not only strings: the config holds numbers (`utteranceIdleMs`) and lists\n * (`startPhrases`), and a panel that edits them as text has to hand back the\n * parsed value or the next load rejects what it wrote.\n */\n readonly value?: string | number | boolean | readonly string[];\n}\n\nexport interface WriteDoomConfigOptions {\n /**\n * Remove ancestors left holding nothing. Defaults to true, and is not\n * cosmetic: an adapter left as `{}` fails validation, because the parser\n * requires a model once the adapter exists at all.\n */\n readonly prune?: boolean;\n}\n\nasync function readIfPresent(filePath: string): Promise<string> {\n try {\n return await fs.readFile(filePath, 'utf8');\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return '';\n throw error;\n }\n}\n\nfunction isEmptyCollection(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const items = (value as { items?: unknown }).items;\n return Array.isArray(items) && items.length === 0;\n}\n\n/** Drops ancestors of a removed key that are now empty, deepest first. */\nfunction pruneEmptyAncestors(document: ReturnType<typeof parseDocument>, keyPath: readonly string[]): void {\n for (let depth = keyPath.length - 1; depth > 0; depth -= 1) {\n const ancestor = keyPath.slice(0, depth);\n if (!document.hasIn(ancestor)) continue;\n if (!isEmptyCollection(document.getIn(ancestor, true))) return;\n document.deleteIn(ancestor);\n }\n}\n\n/**\n * Applies every edit as one document mutation, or throws having written nothing.\n *\n * Validation runs against the serialized result rather than each edit, so a pair\n * of edits only valid together is accepted, and a single edit that breaks an\n * invariant elsewhere is still caught.\n */\nexport async function writeDoomConfigValues(\n filePath: string,\n edits: readonly DoomConfigEdit[],\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n if (edits.length === 0) return;\n const content = await readIfPresent(filePath);\n // Not `content || '{}\\n'`: seeding an absent file with a flow map makes yaml\n // keep that style, so the first write produces `{ editor: { command: ... } }`\n // and every later one grows that one line. An empty source starts a block map.\n const document = parseDocument(content, { keepSourceTokens: true });\n if (document.errors.length > 0) throw new Error(`Could not parse Doom config at ${filePath}`);\n\n for (const edit of edits) {\n const keyPath = [...edit.keyPath];\n if (edit.value !== undefined) {\n document.setIn(keyPath, edit.value);\n continue;\n }\n // deleteIn throws when an ancestor is missing rather than treating the key as\n // already absent, and clearing an unset key is worth supporting.\n if (!document.hasIn(keyPath)) continue;\n document.deleteIn(keyPath);\n if (options.prune !== false) pruneEmptyAncestors(document, keyPath);\n }\n\n const output = document.toString();\n parseDoomConfig(output, filePath);\n\n await fs.mkdir(path.dirname(filePath), { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await fs.writeFile(temporaryPath, output, { encoding: 'utf8', mode: PRIVATE_FILE_MODE });\n await fs.rename(temporaryPath, filePath);\n } catch (error) {\n await fs.rm(temporaryPath, { force: true });\n throw error;\n }\n}\n\nexport async function setDoomConfigValue(filePath: string, keyPath: readonly string[], value: string): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath, value }]);\n}\n\nexport async function unsetDoomConfigValue(filePath: string, keyPath: readonly string[]): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath }]);\n}\n"],"mappings":"yOAwDA,eAAe,EAAc,EAAmC,CAC9D,GAAI,CACF,OAAO,MAAMA,EAAAA,QAAG,SAAS,EAAU,MAAM,CAC3C,OAAS,EAAO,CACd,GAAI,aAAiB,OAAS,SAAU,GAAS,EAAM,OAAS,SAAU,MAAO,GACjF,MAAM,CACR,CACF,CAEA,SAAS,EAAkB,EAAyB,CAClD,GAAsB,OAAO,GAAU,WAAnC,EAA6C,MAAO,GACxD,IAAM,EAAS,EAA8B,MAC7C,OAAO,MAAM,QAAQ,CAAK,GAAK,EAAM,SAAW,CAClD,CAGA,SAAS,EAAoB,EAA4C,EAAkC,CACzG,IAAK,IAAI,EAAQ,EAAQ,OAAS,EAAG,EAAQ,EAAG,IAAY,CAC1D,IAAM,EAAW,EAAQ,MAAM,EAAG,CAAK,EAClC,KAAS,MAAM,CAAQ,EAC5B,IAAI,CAAC,EAAkB,EAAS,MAAM,EAAU,EAAI,CAAC,EAAG,OACxD,EAAS,SAAS,CAAQ,CAD8B,CAE1D,CACF,CASA,eAAsB,EACpB,EACA,EACA,EAAkC,CAAC,EACpB,CACf,GAAI,EAAM,SAAW,EAAG,OACxB,IAAM,EAAU,MAAM,EAAc,CAAQ,EAItC,GAAA,EAAWC,EAAAA,cAAAA,CAAc,EAAS,CAAE,iBAAkB,EAAK,CAAC,EAClE,GAAI,EAAS,OAAO,OAAS,EAAG,MAAU,MAAM,kCAAkC,GAAU,EAE5F,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAU,CAAC,GAAG,EAAK,OAAO,EAChC,GAAI,EAAK,QAAU,IAAA,GAAW,CAC5B,EAAS,MAAM,EAAS,EAAK,KAAK,EAClC,QACF,CAGK,EAAS,MAAM,CAAO,IAC3B,EAAS,SAAS,CAAO,EACrB,EAAQ,QAAU,IAAO,EAAoB,EAAU,CAAO,EACpE,CAEA,IAAM,EAAS,EAAS,SAAS,EACjC,EAAA,gBAAgB,EAAQ,CAAQ,EAEhC,MAAMD,EAAAA,QAAG,MAAME,EAAAA,QAAK,QAAQ,CAAQ,EAAG,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EACxF,IAAM,EAAgB,GAAG,EAAS,GAAG,QAAQ,IAAI,IAAA,EAAGC,EAAAA,WAAAA,CAAW,EAAE,MACjE,GAAI,CACF,MAAMH,EAAAA,QAAG,UAAU,EAAe,EAAQ,CAAE,SAAU,OAAQ,KAAM,GAAkB,CAAC,EACvF,MAAMA,EAAAA,QAAG,OAAO,EAAe,CAAQ,CACzC,OAAS,EAAO,CAEd,MADA,MAAMA,EAAAA,QAAG,GAAG,EAAe,CAAE,MAAO,EAAK,CAAC,EACpC,CACR,CACF,CAEA,eAAsB,EAAmB,EAAkB,EAA4B,EAA8B,CACnH,MAAM,EAAsB,EAAU,CAAC,CAAE,UAAS,OAAM,CAAC,CAAC,CAC5D,CAEA,eAAsB,EAAqB,EAAkB,EAA2C,CACtG,MAAM,EAAsB,EAAU,CAAC,CAAE,SAAQ,CAAC,CAAC,CACrD"}
1
+ {"version":3,"file":"configWriter.cjs","names":["fs","parseDocument","path","randomUUID"],"sources":["../../src/adapters/configWriter.ts"],"sourcesContent":["/**\n * Writes into the global Doom config file, one key path at a time.\n *\n * Extracted from doom-file-edit, which owned the only writer in the repo and had\n * it hard-coded to `editor.command`. Several extensions now edit their own\n * settings from the config panel, and every one of them needs the same four\n * properties: keep the user's comments, never publish a file the parser would\n * reject, never leave a half-written file behind, and apply a set of related\n * edits together.\n *\n * That last one is not a convenience. `voice.adapters.<engine>.model` accepts a\n * path or an id but not both, so switching between them is a set and an unset\n * that must land in the same write, or the file is briefly invalid.\n *\n * Atomicity is the same rename trick as `atomicJson.ts` next door, and carries\n * the same caveat: it prevents torn reads, not lost updates. Two sessions with\n * the panel open still clobber each other, because the rename only publishes\n * what the caller already computed.\n *\n * AVOID:\n * - Treating this as a lock\n * - Writing an empty string; the parser rejects blank values, so clearing a key\n * means removing it\n */\n\nimport { randomUUID } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { parseDocument } from 'yaml';\nimport { parseDoomConfig } from '../services/configPolicy.ts';\n\n/**\n * The global file holds machine-wide credentials-adjacent settings and lives in\n * the user's home, so it stays private.\n */\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\n\n/**\n * The repository file is committed to git and read by everyone who checks the\n * project out, so it takes the modes `init.ts` already seeds it with. Writing it\n * private would produce a 0600 file in a working tree, which is both wrong and\n * invisible until someone else clones.\n */\nconst REPOSITORY_FILE_MODE = 0o644;\nconst REPOSITORY_DIRECTORY_MODE = 0o755;\n\n/** Which file a write is aimed at; the modes follow from it. */\nexport type DoomConfigScope = 'global' | 'repository';\n\n/** The permissions a scope's file and its parent directory are created with. */\nfunction modesForScope(scope: DoomConfigScope): { file: number; directory: number } {\n return scope === 'repository'\n ? { file: REPOSITORY_FILE_MODE, directory: REPOSITORY_DIRECTORY_MODE }\n : { file: PRIVATE_FILE_MODE, directory: PRIVATE_DIRECTORY_MODE };\n}\n\nexport interface DoomConfigEdit {\n /** Key path into the document, e.g. `['voice', 'adapters', 'mlx-whisper', 'model', 'id']`. */\n readonly keyPath: readonly string[];\n /**\n * The value to write. `undefined` removes the key.\n *\n * Not only strings: the config holds numbers (`utteranceIdleMs`) and lists\n * (`startPhrases`), and a panel that edits them as text has to hand back the\n * parsed value or the next load rejects what it wrote.\n */\n readonly value?: string | number | boolean | readonly string[];\n}\n\nexport interface WriteDoomConfigOptions {\n /**\n * Remove ancestors left holding nothing. Defaults to true, and is not\n * cosmetic: an adapter left as `{}` fails validation, because the parser\n * requires a model once the adapter exists at all.\n */\n readonly prune?: boolean;\n /**\n * Which file this is, so the write lands with the right permissions.\n * Defaults to `global`, which is what every caller wrote before the\n * repository file was writable at all.\n */\n readonly scope?: DoomConfigScope;\n}\n\nasync function readIfPresent(filePath: string): Promise<string> {\n try {\n return await fs.readFile(filePath, 'utf8');\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return '';\n throw error;\n }\n}\n\nfunction isEmptyCollection(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const items = (value as { items?: unknown }).items;\n return Array.isArray(items) && items.length === 0;\n}\n\n/** Drops ancestors of a removed key that are now empty, deepest first. */\nfunction pruneEmptyAncestors(document: ReturnType<typeof parseDocument>, keyPath: readonly string[]): void {\n for (let depth = keyPath.length - 1; depth > 0; depth -= 1) {\n const ancestor = keyPath.slice(0, depth);\n if (!document.hasIn(ancestor)) continue;\n if (!isEmptyCollection(document.getIn(ancestor, true))) return;\n document.deleteIn(ancestor);\n }\n}\n\n/**\n * Applies every edit as one document mutation, or throws having written nothing.\n *\n * Validation runs against the serialized result rather than each edit, so a pair\n * of edits only valid together is accepted, and a single edit that breaks an\n * invariant elsewhere is still caught.\n */\nexport async function writeDoomConfigValues(\n filePath: string,\n edits: readonly DoomConfigEdit[],\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n if (edits.length === 0) return;\n const content = await readIfPresent(filePath);\n // Not `content || '{}\\n'`: seeding an absent file with a flow map makes yaml\n // keep that style, so the first write produces `{ editor: { command: ... } }`\n // and every later one grows that one line. An empty source starts a block map.\n const document = parseDocument(content, { keepSourceTokens: true });\n if (document.errors.length > 0) throw new Error(`Could not parse Doom config at ${filePath}`);\n\n for (const edit of edits) {\n const keyPath = [...edit.keyPath];\n if (edit.value !== undefined) {\n document.setIn(keyPath, edit.value);\n continue;\n }\n // deleteIn throws when an ancestor is missing rather than treating the key as\n // already absent, and clearing an unset key is worth supporting.\n if (!document.hasIn(keyPath)) continue;\n document.deleteIn(keyPath);\n if (options.prune !== false) pruneEmptyAncestors(document, keyPath);\n }\n\n const output = document.toString();\n parseDoomConfig(output, filePath);\n\n const modes = modesForScope(options.scope ?? 'global');\n await fs.mkdir(path.dirname(filePath), { mode: modes.directory, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await fs.writeFile(temporaryPath, output, { encoding: 'utf8', mode: modes.file });\n await fs.rename(temporaryPath, filePath);\n } catch (error) {\n await fs.rm(temporaryPath, { force: true });\n throw error;\n }\n}\n\nexport async function setDoomConfigValue(\n filePath: string,\n keyPath: readonly string[],\n value: string,\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath, value }], options);\n}\n\nexport async function unsetDoomConfigValue(\n filePath: string,\n keyPath: readonly string[],\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath }], options);\n}\n"],"mappings":"yOAmDA,SAAS,EAAc,EAA6D,CAClF,OAAO,IAAU,aACb,CAAE,KAAM,IAAsB,UAAW,GAA0B,EACnE,CAAE,KAAM,IAAmB,UAAW,GAAuB,CACnE,CA8BA,eAAe,EAAc,EAAmC,CAC9D,GAAI,CACF,OAAO,MAAMA,EAAAA,QAAG,SAAS,EAAU,MAAM,CAC3C,OAAS,EAAO,CACd,GAAI,aAAiB,OAAS,SAAU,GAAS,EAAM,OAAS,SAAU,MAAO,GACjF,MAAM,CACR,CACF,CAEA,SAAS,EAAkB,EAAyB,CAClD,GAAsB,OAAO,GAAU,WAAnC,EAA6C,MAAO,GACxD,IAAM,EAAS,EAA8B,MAC7C,OAAO,MAAM,QAAQ,CAAK,GAAK,EAAM,SAAW,CAClD,CAGA,SAAS,EAAoB,EAA4C,EAAkC,CACzG,IAAK,IAAI,EAAQ,EAAQ,OAAS,EAAG,EAAQ,EAAG,IAAY,CAC1D,IAAM,EAAW,EAAQ,MAAM,EAAG,CAAK,EAClC,KAAS,MAAM,CAAQ,EAC5B,IAAI,CAAC,EAAkB,EAAS,MAAM,EAAU,EAAI,CAAC,EAAG,OACxD,EAAS,SAAS,CAAQ,CAD8B,CAE1D,CACF,CASA,eAAsB,EACpB,EACA,EACA,EAAkC,CAAC,EACpB,CACf,GAAI,EAAM,SAAW,EAAG,OACxB,IAAM,EAAU,MAAM,EAAc,CAAQ,EAItC,GAAA,EAAWC,EAAAA,cAAAA,CAAc,EAAS,CAAE,iBAAkB,EAAK,CAAC,EAClE,GAAI,EAAS,OAAO,OAAS,EAAG,MAAU,MAAM,kCAAkC,GAAU,EAE5F,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAU,CAAC,GAAG,EAAK,OAAO,EAChC,GAAI,EAAK,QAAU,IAAA,GAAW,CAC5B,EAAS,MAAM,EAAS,EAAK,KAAK,EAClC,QACF,CAGK,EAAS,MAAM,CAAO,IAC3B,EAAS,SAAS,CAAO,EACrB,EAAQ,QAAU,IAAO,EAAoB,EAAU,CAAO,EACpE,CAEA,IAAM,EAAS,EAAS,SAAS,EACjC,EAAA,gBAAgB,EAAQ,CAAQ,EAEhC,IAAM,EAAQ,EAAc,EAAQ,OAAS,QAAQ,EACrD,MAAMD,EAAAA,QAAG,MAAME,EAAAA,QAAK,QAAQ,CAAQ,EAAG,CAAE,KAAM,EAAM,UAAW,UAAW,EAAK,CAAC,EACjF,IAAM,EAAgB,GAAG,EAAS,GAAG,QAAQ,IAAI,IAAA,EAAGC,EAAAA,WAAAA,CAAW,EAAE,MACjE,GAAI,CACF,MAAMH,EAAAA,QAAG,UAAU,EAAe,EAAQ,CAAE,SAAU,OAAQ,KAAM,EAAM,IAAK,CAAC,EAChF,MAAMA,EAAAA,QAAG,OAAO,EAAe,CAAQ,CACzC,OAAS,EAAO,CAEd,MADA,MAAMA,EAAAA,QAAG,GAAG,EAAe,CAAE,MAAO,EAAK,CAAC,EACpC,CACR,CACF,CAEA,eAAsB,EACpB,EACA,EACA,EACA,EAAkC,CAAC,EACpB,CACf,MAAM,EAAsB,EAAU,CAAC,CAAE,UAAS,OAAM,CAAC,EAAG,CAAO,CACrE,CAEA,eAAsB,EACpB,EACA,EACA,EAAkC,CAAC,EACpB,CACf,MAAM,EAAsB,EAAU,CAAC,CAAE,SAAQ,CAAC,EAAG,CAAO,CAC9D"}
@@ -23,6 +23,8 @@
23
23
  * - Writing an empty string; the parser rejects blank values, so clearing a key
24
24
  * means removing it
25
25
  */
26
+ /** Which file a write is aimed at; the modes follow from it. */
27
+ type DoomConfigScope = 'global' | 'repository';
26
28
  interface DoomConfigEdit {
27
29
  /** Key path into the document, e.g. `['voice', 'adapters', 'mlx-whisper', 'model', 'id']`. */
28
30
  readonly keyPath: readonly string[];
@@ -42,6 +44,12 @@ interface WriteDoomConfigOptions {
42
44
  * requires a model once the adapter exists at all.
43
45
  */
44
46
  readonly prune?: boolean;
47
+ /**
48
+ * Which file this is, so the write lands with the right permissions.
49
+ * Defaults to `global`, which is what every caller wrote before the
50
+ * repository file was writable at all.
51
+ */
52
+ readonly scope?: DoomConfigScope;
45
53
  }
46
54
  /**
47
55
  * Applies every edit as one document mutation, or throws having written nothing.
@@ -51,8 +59,8 @@ interface WriteDoomConfigOptions {
51
59
  * invariant elsewhere is still caught.
52
60
  */
53
61
  declare function writeDoomConfigValues(filePath: string, edits: readonly DoomConfigEdit[], options?: WriteDoomConfigOptions): Promise<void>;
54
- declare function setDoomConfigValue(filePath: string, keyPath: readonly string[], value: string): Promise<void>;
55
- declare function unsetDoomConfigValue(filePath: string, keyPath: readonly string[]): Promise<void>;
62
+ declare function setDoomConfigValue(filePath: string, keyPath: readonly string[], value: string, options?: WriteDoomConfigOptions): Promise<void>;
63
+ declare function unsetDoomConfigValue(filePath: string, keyPath: readonly string[], options?: WriteDoomConfigOptions): Promise<void>;
56
64
  //#endregion
57
- export { DoomConfigEdit, WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues };
65
+ export { DoomConfigEdit, DoomConfigScope, WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues };
58
66
  //# sourceMappingURL=configWriter.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"configWriter.d.cts","names":[],"sources":["../../src/adapters/configWriter.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;UAkCiB;;WAEN;;;;;;;;WAQA;;UAGM;;;;;;WAMN;;;;;;;;;iBAmCW,sBACpB,kBACA,gBAAgB,kBAChB,UAAS,yBACR;iBAoCmB,mBAAmB,kBAAkB,4BAA4B,gBAAgB;iBAIjF,qBAAqB,kBAAkB,6BAA6B"}
1
+ {"version":3,"file":"configWriter.d.cts","names":[],"sources":["../../src/adapters/configWriter.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;KAgDY;UASK;;WAEN;;;;;;;;WAQA;;UAGM;;;;;;WAMN;;;;;;WAMA,QAAQ;;;;;;;;;iBAmCG,sBACpB,kBACA,gBAAgB,kBAChB,UAAS,yBACR;iBAqCmB,mBACpB,kBACA,4BACA,eACA,UAAS,yBACR;iBAImB,qBACpB,kBACA,4BACA,UAAS,yBACR"}
@@ -23,6 +23,8 @@
23
23
  * - Writing an empty string; the parser rejects blank values, so clearing a key
24
24
  * means removing it
25
25
  */
26
+ /** Which file a write is aimed at; the modes follow from it. */
27
+ type DoomConfigScope = 'global' | 'repository';
26
28
  interface DoomConfigEdit {
27
29
  /** Key path into the document, e.g. `['voice', 'adapters', 'mlx-whisper', 'model', 'id']`. */
28
30
  readonly keyPath: readonly string[];
@@ -42,6 +44,12 @@ interface WriteDoomConfigOptions {
42
44
  * requires a model once the adapter exists at all.
43
45
  */
44
46
  readonly prune?: boolean;
47
+ /**
48
+ * Which file this is, so the write lands with the right permissions.
49
+ * Defaults to `global`, which is what every caller wrote before the
50
+ * repository file was writable at all.
51
+ */
52
+ readonly scope?: DoomConfigScope;
45
53
  }
46
54
  /**
47
55
  * Applies every edit as one document mutation, or throws having written nothing.
@@ -51,8 +59,8 @@ interface WriteDoomConfigOptions {
51
59
  * invariant elsewhere is still caught.
52
60
  */
53
61
  declare function writeDoomConfigValues(filePath: string, edits: readonly DoomConfigEdit[], options?: WriteDoomConfigOptions): Promise<void>;
54
- declare function setDoomConfigValue(filePath: string, keyPath: readonly string[], value: string): Promise<void>;
55
- declare function unsetDoomConfigValue(filePath: string, keyPath: readonly string[]): Promise<void>;
62
+ declare function setDoomConfigValue(filePath: string, keyPath: readonly string[], value: string, options?: WriteDoomConfigOptions): Promise<void>;
63
+ declare function unsetDoomConfigValue(filePath: string, keyPath: readonly string[], options?: WriteDoomConfigOptions): Promise<void>;
56
64
  //#endregion
57
- export { DoomConfigEdit, WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues };
65
+ export { DoomConfigEdit, DoomConfigScope, WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues };
58
66
  //# sourceMappingURL=configWriter.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"configWriter.d.mts","names":[],"sources":["../../src/adapters/configWriter.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;UAkCiB;;WAEN;;;;;;;;WAQA;;UAGM;;;;;;WAMN;;;;;;;;;iBAmCW,sBACpB,kBACA,gBAAgB,kBAChB,UAAS,yBACR;iBAoCmB,mBAAmB,kBAAkB,4BAA4B,gBAAgB;iBAIjF,qBAAqB,kBAAkB,6BAA6B"}
1
+ {"version":3,"file":"configWriter.d.mts","names":[],"sources":["../../src/adapters/configWriter.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;KAgDY;UASK;;WAEN;;;;;;;;WAQA;;UAGM;;;;;;WAMN;;;;;;WAMA,QAAQ;;;;;;;;;iBAmCG,sBACpB,kBACA,gBAAgB,kBAChB,UAAS,yBACR;iBAqCmB,mBACpB,kBACA,4BACA,eACA,UAAS,yBACR;iBAImB,qBACpB,kBACA,4BACA,UAAS,yBACR"}
@@ -1,2 +1,2 @@
1
- import{parseDoomConfig as e}from"../services/configPolicy.mjs";import{randomUUID as t}from"node:crypto";import n from"node:path";import{parseDocument as r}from"yaml";import i from"node:fs/promises";async function a(e){try{return await i.readFile(e,`utf8`)}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return``;throw e}}function o(e){if(typeof e!=`object`||!e)return!1;let t=e.items;return Array.isArray(t)&&t.length===0}function s(e,t){for(let n=t.length-1;n>0;--n){let r=t.slice(0,n);if(e.hasIn(r)){if(!o(e.getIn(r,!0)))return;e.deleteIn(r)}}}async function c(o,c,l={}){if(c.length===0)return;let u=await a(o),d=r(u,{keepSourceTokens:!0});if(d.errors.length>0)throw Error(`Could not parse Doom config at ${o}`);for(let e of c){let t=[...e.keyPath];if(e.value!==void 0){d.setIn(t,e.value);continue}d.hasIn(t)&&(d.deleteIn(t),l.prune!==!1&&s(d,t))}let f=d.toString();e(f,o),await i.mkdir(n.dirname(o),{mode:448,recursive:!0});let p=`${o}.${process.pid}.${t()}.tmp`;try{await i.writeFile(p,f,{encoding:`utf8`,mode:384}),await i.rename(p,o)}catch(e){throw await i.rm(p,{force:!0}),e}}async function l(e,t,n){await c(e,[{keyPath:t,value:n}])}async function u(e,t){await c(e,[{keyPath:t}])}export{l as setDoomConfigValue,u as unsetDoomConfigValue,c as writeDoomConfigValues};
1
+ import{parseDoomConfig as e}from"../services/configPolicy.mjs";import{randomUUID as t}from"node:crypto";import n from"node:path";import{parseDocument as r}from"yaml";import i from"node:fs/promises";function a(e){return e===`repository`?{file:420,directory:493}:{file:384,directory:448}}async function o(e){try{return await i.readFile(e,`utf8`)}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return``;throw e}}function s(e){if(typeof e!=`object`||!e)return!1;let t=e.items;return Array.isArray(t)&&t.length===0}function c(e,t){for(let n=t.length-1;n>0;--n){let r=t.slice(0,n);if(e.hasIn(r)){if(!s(e.getIn(r,!0)))return;e.deleteIn(r)}}}async function l(s,l,u={}){if(l.length===0)return;let d=await o(s),f=r(d,{keepSourceTokens:!0});if(f.errors.length>0)throw Error(`Could not parse Doom config at ${s}`);for(let e of l){let t=[...e.keyPath];if(e.value!==void 0){f.setIn(t,e.value);continue}f.hasIn(t)&&(f.deleteIn(t),u.prune!==!1&&c(f,t))}let p=f.toString();e(p,s);let m=a(u.scope??`global`);await i.mkdir(n.dirname(s),{mode:m.directory,recursive:!0});let h=`${s}.${process.pid}.${t()}.tmp`;try{await i.writeFile(h,p,{encoding:`utf8`,mode:m.file}),await i.rename(h,s)}catch(e){throw await i.rm(h,{force:!0}),e}}async function u(e,t,n,r={}){await l(e,[{keyPath:t,value:n}],r)}async function d(e,t,n={}){await l(e,[{keyPath:t}],n)}export{u as setDoomConfigValue,d as unsetDoomConfigValue,l as writeDoomConfigValues};
2
2
  //# sourceMappingURL=configWriter.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"configWriter.mjs","names":[],"sources":["../../src/adapters/configWriter.ts"],"sourcesContent":["/**\n * Writes into the global Doom config file, one key path at a time.\n *\n * Extracted from doom-file-edit, which owned the only writer in the repo and had\n * it hard-coded to `editor.command`. Several extensions now edit their own\n * settings from the config panel, and every one of them needs the same four\n * properties: keep the user's comments, never publish a file the parser would\n * reject, never leave a half-written file behind, and apply a set of related\n * edits together.\n *\n * That last one is not a convenience. `voice.adapters.<engine>.model` accepts a\n * path or an id but not both, so switching between them is a set and an unset\n * that must land in the same write, or the file is briefly invalid.\n *\n * Atomicity is the same rename trick as `atomicJson.ts` next door, and carries\n * the same caveat: it prevents torn reads, not lost updates. Two sessions with\n * the panel open still clobber each other, because the rename only publishes\n * what the caller already computed.\n *\n * AVOID:\n * - Treating this as a lock\n * - Writing an empty string; the parser rejects blank values, so clearing a key\n * means removing it\n */\n\nimport { randomUUID } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { parseDocument } from 'yaml';\nimport { parseDoomConfig } from '../services/configPolicy.ts';\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\n\nexport interface DoomConfigEdit {\n /** Key path into the document, e.g. `['voice', 'adapters', 'mlx-whisper', 'model', 'id']`. */\n readonly keyPath: readonly string[];\n /**\n * The value to write. `undefined` removes the key.\n *\n * Not only strings: the config holds numbers (`utteranceIdleMs`) and lists\n * (`startPhrases`), and a panel that edits them as text has to hand back the\n * parsed value or the next load rejects what it wrote.\n */\n readonly value?: string | number | boolean | readonly string[];\n}\n\nexport interface WriteDoomConfigOptions {\n /**\n * Remove ancestors left holding nothing. Defaults to true, and is not\n * cosmetic: an adapter left as `{}` fails validation, because the parser\n * requires a model once the adapter exists at all.\n */\n readonly prune?: boolean;\n}\n\nasync function readIfPresent(filePath: string): Promise<string> {\n try {\n return await fs.readFile(filePath, 'utf8');\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return '';\n throw error;\n }\n}\n\nfunction isEmptyCollection(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const items = (value as { items?: unknown }).items;\n return Array.isArray(items) && items.length === 0;\n}\n\n/** Drops ancestors of a removed key that are now empty, deepest first. */\nfunction pruneEmptyAncestors(document: ReturnType<typeof parseDocument>, keyPath: readonly string[]): void {\n for (let depth = keyPath.length - 1; depth > 0; depth -= 1) {\n const ancestor = keyPath.slice(0, depth);\n if (!document.hasIn(ancestor)) continue;\n if (!isEmptyCollection(document.getIn(ancestor, true))) return;\n document.deleteIn(ancestor);\n }\n}\n\n/**\n * Applies every edit as one document mutation, or throws having written nothing.\n *\n * Validation runs against the serialized result rather than each edit, so a pair\n * of edits only valid together is accepted, and a single edit that breaks an\n * invariant elsewhere is still caught.\n */\nexport async function writeDoomConfigValues(\n filePath: string,\n edits: readonly DoomConfigEdit[],\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n if (edits.length === 0) return;\n const content = await readIfPresent(filePath);\n // Not `content || '{}\\n'`: seeding an absent file with a flow map makes yaml\n // keep that style, so the first write produces `{ editor: { command: ... } }`\n // and every later one grows that one line. An empty source starts a block map.\n const document = parseDocument(content, { keepSourceTokens: true });\n if (document.errors.length > 0) throw new Error(`Could not parse Doom config at ${filePath}`);\n\n for (const edit of edits) {\n const keyPath = [...edit.keyPath];\n if (edit.value !== undefined) {\n document.setIn(keyPath, edit.value);\n continue;\n }\n // deleteIn throws when an ancestor is missing rather than treating the key as\n // already absent, and clearing an unset key is worth supporting.\n if (!document.hasIn(keyPath)) continue;\n document.deleteIn(keyPath);\n if (options.prune !== false) pruneEmptyAncestors(document, keyPath);\n }\n\n const output = document.toString();\n parseDoomConfig(output, filePath);\n\n await fs.mkdir(path.dirname(filePath), { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await fs.writeFile(temporaryPath, output, { encoding: 'utf8', mode: PRIVATE_FILE_MODE });\n await fs.rename(temporaryPath, filePath);\n } catch (error) {\n await fs.rm(temporaryPath, { force: true });\n throw error;\n }\n}\n\nexport async function setDoomConfigValue(filePath: string, keyPath: readonly string[], value: string): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath, value }]);\n}\n\nexport async function unsetDoomConfigValue(filePath: string, keyPath: readonly string[]): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath }]);\n}\n"],"mappings":"sMAwDA,eAAe,EAAc,EAAmC,CAC9D,GAAI,CACF,OAAO,MAAM,EAAG,SAAS,EAAU,MAAM,CAC3C,OAAS,EAAO,CACd,GAAI,aAAiB,OAAS,SAAU,GAAS,EAAM,OAAS,SAAU,MAAO,GACjF,MAAM,CACR,CACF,CAEA,SAAS,EAAkB,EAAyB,CAClD,GAAsB,OAAO,GAAU,WAAnC,EAA6C,MAAO,GACxD,IAAM,EAAS,EAA8B,MAC7C,OAAO,MAAM,QAAQ,CAAK,GAAK,EAAM,SAAW,CAClD,CAGA,SAAS,EAAoB,EAA4C,EAAkC,CACzG,IAAK,IAAI,EAAQ,EAAQ,OAAS,EAAG,EAAQ,EAAG,IAAY,CAC1D,IAAM,EAAW,EAAQ,MAAM,EAAG,CAAK,EAClC,KAAS,MAAM,CAAQ,EAC5B,IAAI,CAAC,EAAkB,EAAS,MAAM,EAAU,EAAI,CAAC,EAAG,OACxD,EAAS,SAAS,CAAQ,CAD8B,CAE1D,CACF,CASA,eAAsB,EACpB,EACA,EACA,EAAkC,CAAC,EACpB,CACf,GAAI,EAAM,SAAW,EAAG,OACxB,IAAM,EAAU,MAAM,EAAc,CAAQ,EAItC,EAAW,EAAc,EAAS,CAAE,iBAAkB,EAAK,CAAC,EAClE,GAAI,EAAS,OAAO,OAAS,EAAG,MAAU,MAAM,kCAAkC,GAAU,EAE5F,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAU,CAAC,GAAG,EAAK,OAAO,EAChC,GAAI,EAAK,QAAU,IAAA,GAAW,CAC5B,EAAS,MAAM,EAAS,EAAK,KAAK,EAClC,QACF,CAGK,EAAS,MAAM,CAAO,IAC3B,EAAS,SAAS,CAAO,EACrB,EAAQ,QAAU,IAAO,EAAoB,EAAU,CAAO,EACpE,CAEA,IAAM,EAAS,EAAS,SAAS,EACjC,EAAgB,EAAQ,CAAQ,EAEhC,MAAM,EAAG,MAAM,EAAK,QAAQ,CAAQ,EAAG,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EACxF,IAAM,EAAgB,GAAG,EAAS,GAAG,QAAQ,IAAI,GAAG,EAAW,EAAE,MACjE,GAAI,CACF,MAAM,EAAG,UAAU,EAAe,EAAQ,CAAE,SAAU,OAAQ,KAAM,GAAkB,CAAC,EACvF,MAAM,EAAG,OAAO,EAAe,CAAQ,CACzC,OAAS,EAAO,CAEd,MADA,MAAM,EAAG,GAAG,EAAe,CAAE,MAAO,EAAK,CAAC,EACpC,CACR,CACF,CAEA,eAAsB,EAAmB,EAAkB,EAA4B,EAA8B,CACnH,MAAM,EAAsB,EAAU,CAAC,CAAE,UAAS,OAAM,CAAC,CAAC,CAC5D,CAEA,eAAsB,EAAqB,EAAkB,EAA2C,CACtG,MAAM,EAAsB,EAAU,CAAC,CAAE,SAAQ,CAAC,CAAC,CACrD"}
1
+ {"version":3,"file":"configWriter.mjs","names":[],"sources":["../../src/adapters/configWriter.ts"],"sourcesContent":["/**\n * Writes into the global Doom config file, one key path at a time.\n *\n * Extracted from doom-file-edit, which owned the only writer in the repo and had\n * it hard-coded to `editor.command`. Several extensions now edit their own\n * settings from the config panel, and every one of them needs the same four\n * properties: keep the user's comments, never publish a file the parser would\n * reject, never leave a half-written file behind, and apply a set of related\n * edits together.\n *\n * That last one is not a convenience. `voice.adapters.<engine>.model` accepts a\n * path or an id but not both, so switching between them is a set and an unset\n * that must land in the same write, or the file is briefly invalid.\n *\n * Atomicity is the same rename trick as `atomicJson.ts` next door, and carries\n * the same caveat: it prevents torn reads, not lost updates. Two sessions with\n * the panel open still clobber each other, because the rename only publishes\n * what the caller already computed.\n *\n * AVOID:\n * - Treating this as a lock\n * - Writing an empty string; the parser rejects blank values, so clearing a key\n * means removing it\n */\n\nimport { randomUUID } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { parseDocument } from 'yaml';\nimport { parseDoomConfig } from '../services/configPolicy.ts';\n\n/**\n * The global file holds machine-wide credentials-adjacent settings and lives in\n * the user's home, so it stays private.\n */\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\n\n/**\n * The repository file is committed to git and read by everyone who checks the\n * project out, so it takes the modes `init.ts` already seeds it with. Writing it\n * private would produce a 0600 file in a working tree, which is both wrong and\n * invisible until someone else clones.\n */\nconst REPOSITORY_FILE_MODE = 0o644;\nconst REPOSITORY_DIRECTORY_MODE = 0o755;\n\n/** Which file a write is aimed at; the modes follow from it. */\nexport type DoomConfigScope = 'global' | 'repository';\n\n/** The permissions a scope's file and its parent directory are created with. */\nfunction modesForScope(scope: DoomConfigScope): { file: number; directory: number } {\n return scope === 'repository'\n ? { file: REPOSITORY_FILE_MODE, directory: REPOSITORY_DIRECTORY_MODE }\n : { file: PRIVATE_FILE_MODE, directory: PRIVATE_DIRECTORY_MODE };\n}\n\nexport interface DoomConfigEdit {\n /** Key path into the document, e.g. `['voice', 'adapters', 'mlx-whisper', 'model', 'id']`. */\n readonly keyPath: readonly string[];\n /**\n * The value to write. `undefined` removes the key.\n *\n * Not only strings: the config holds numbers (`utteranceIdleMs`) and lists\n * (`startPhrases`), and a panel that edits them as text has to hand back the\n * parsed value or the next load rejects what it wrote.\n */\n readonly value?: string | number | boolean | readonly string[];\n}\n\nexport interface WriteDoomConfigOptions {\n /**\n * Remove ancestors left holding nothing. Defaults to true, and is not\n * cosmetic: an adapter left as `{}` fails validation, because the parser\n * requires a model once the adapter exists at all.\n */\n readonly prune?: boolean;\n /**\n * Which file this is, so the write lands with the right permissions.\n * Defaults to `global`, which is what every caller wrote before the\n * repository file was writable at all.\n */\n readonly scope?: DoomConfigScope;\n}\n\nasync function readIfPresent(filePath: string): Promise<string> {\n try {\n return await fs.readFile(filePath, 'utf8');\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return '';\n throw error;\n }\n}\n\nfunction isEmptyCollection(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const items = (value as { items?: unknown }).items;\n return Array.isArray(items) && items.length === 0;\n}\n\n/** Drops ancestors of a removed key that are now empty, deepest first. */\nfunction pruneEmptyAncestors(document: ReturnType<typeof parseDocument>, keyPath: readonly string[]): void {\n for (let depth = keyPath.length - 1; depth > 0; depth -= 1) {\n const ancestor = keyPath.slice(0, depth);\n if (!document.hasIn(ancestor)) continue;\n if (!isEmptyCollection(document.getIn(ancestor, true))) return;\n document.deleteIn(ancestor);\n }\n}\n\n/**\n * Applies every edit as one document mutation, or throws having written nothing.\n *\n * Validation runs against the serialized result rather than each edit, so a pair\n * of edits only valid together is accepted, and a single edit that breaks an\n * invariant elsewhere is still caught.\n */\nexport async function writeDoomConfigValues(\n filePath: string,\n edits: readonly DoomConfigEdit[],\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n if (edits.length === 0) return;\n const content = await readIfPresent(filePath);\n // Not `content || '{}\\n'`: seeding an absent file with a flow map makes yaml\n // keep that style, so the first write produces `{ editor: { command: ... } }`\n // and every later one grows that one line. An empty source starts a block map.\n const document = parseDocument(content, { keepSourceTokens: true });\n if (document.errors.length > 0) throw new Error(`Could not parse Doom config at ${filePath}`);\n\n for (const edit of edits) {\n const keyPath = [...edit.keyPath];\n if (edit.value !== undefined) {\n document.setIn(keyPath, edit.value);\n continue;\n }\n // deleteIn throws when an ancestor is missing rather than treating the key as\n // already absent, and clearing an unset key is worth supporting.\n if (!document.hasIn(keyPath)) continue;\n document.deleteIn(keyPath);\n if (options.prune !== false) pruneEmptyAncestors(document, keyPath);\n }\n\n const output = document.toString();\n parseDoomConfig(output, filePath);\n\n const modes = modesForScope(options.scope ?? 'global');\n await fs.mkdir(path.dirname(filePath), { mode: modes.directory, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await fs.writeFile(temporaryPath, output, { encoding: 'utf8', mode: modes.file });\n await fs.rename(temporaryPath, filePath);\n } catch (error) {\n await fs.rm(temporaryPath, { force: true });\n throw error;\n }\n}\n\nexport async function setDoomConfigValue(\n filePath: string,\n keyPath: readonly string[],\n value: string,\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath, value }], options);\n}\n\nexport async function unsetDoomConfigValue(\n filePath: string,\n keyPath: readonly string[],\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath }], options);\n}\n"],"mappings":"sMAmDA,SAAS,EAAc,EAA6D,CAClF,OAAO,IAAU,aACb,CAAE,KAAM,IAAsB,UAAW,GAA0B,EACnE,CAAE,KAAM,IAAmB,UAAW,GAAuB,CACnE,CA8BA,eAAe,EAAc,EAAmC,CAC9D,GAAI,CACF,OAAO,MAAM,EAAG,SAAS,EAAU,MAAM,CAC3C,OAAS,EAAO,CACd,GAAI,aAAiB,OAAS,SAAU,GAAS,EAAM,OAAS,SAAU,MAAO,GACjF,MAAM,CACR,CACF,CAEA,SAAS,EAAkB,EAAyB,CAClD,GAAsB,OAAO,GAAU,WAAnC,EAA6C,MAAO,GACxD,IAAM,EAAS,EAA8B,MAC7C,OAAO,MAAM,QAAQ,CAAK,GAAK,EAAM,SAAW,CAClD,CAGA,SAAS,EAAoB,EAA4C,EAAkC,CACzG,IAAK,IAAI,EAAQ,EAAQ,OAAS,EAAG,EAAQ,EAAG,IAAY,CAC1D,IAAM,EAAW,EAAQ,MAAM,EAAG,CAAK,EAClC,KAAS,MAAM,CAAQ,EAC5B,IAAI,CAAC,EAAkB,EAAS,MAAM,EAAU,EAAI,CAAC,EAAG,OACxD,EAAS,SAAS,CAAQ,CAD8B,CAE1D,CACF,CASA,eAAsB,EACpB,EACA,EACA,EAAkC,CAAC,EACpB,CACf,GAAI,EAAM,SAAW,EAAG,OACxB,IAAM,EAAU,MAAM,EAAc,CAAQ,EAItC,EAAW,EAAc,EAAS,CAAE,iBAAkB,EAAK,CAAC,EAClE,GAAI,EAAS,OAAO,OAAS,EAAG,MAAU,MAAM,kCAAkC,GAAU,EAE5F,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAU,CAAC,GAAG,EAAK,OAAO,EAChC,GAAI,EAAK,QAAU,IAAA,GAAW,CAC5B,EAAS,MAAM,EAAS,EAAK,KAAK,EAClC,QACF,CAGK,EAAS,MAAM,CAAO,IAC3B,EAAS,SAAS,CAAO,EACrB,EAAQ,QAAU,IAAO,EAAoB,EAAU,CAAO,EACpE,CAEA,IAAM,EAAS,EAAS,SAAS,EACjC,EAAgB,EAAQ,CAAQ,EAEhC,IAAM,EAAQ,EAAc,EAAQ,OAAS,QAAQ,EACrD,MAAM,EAAG,MAAM,EAAK,QAAQ,CAAQ,EAAG,CAAE,KAAM,EAAM,UAAW,UAAW,EAAK,CAAC,EACjF,IAAM,EAAgB,GAAG,EAAS,GAAG,QAAQ,IAAI,GAAG,EAAW,EAAE,MACjE,GAAI,CACF,MAAM,EAAG,UAAU,EAAe,EAAQ,CAAE,SAAU,OAAQ,KAAM,EAAM,IAAK,CAAC,EAChF,MAAM,EAAG,OAAO,EAAe,CAAQ,CACzC,OAAS,EAAO,CAEd,MADA,MAAM,EAAG,GAAG,EAAe,CAAE,MAAO,EAAK,CAAC,EACpC,CACR,CACF,CAEA,eAAsB,EACpB,EACA,EACA,EACA,EAAkC,CAAC,EACpB,CACf,MAAM,EAAsB,EAAU,CAAC,CAAE,UAAS,OAAM,CAAC,EAAG,CAAO,CACrE,CAEA,eAAsB,EACpB,EACA,EACA,EAAkC,CAAC,EACpB,CACf,MAAM,EAAsB,EAAU,CAAC,CAAE,SAAQ,CAAC,EAAG,CAAO,CAC9D"}
@@ -1,2 +1,2 @@
1
- const e=require("../harnessStore.cjs"),t=require("./piContext.cjs"),n=require("./helpContribution.cjs");let r=require("@agimon-ai/doompi-extension-contracts/mcp-projection"),i=require("@agimon-ai/doompi-extension-contracts/cordis-host"),a=require("@agimon-ai/doompi-extension-contracts/readiness"),o=require("@agimon-ai/doompi-extension-contracts/help"),s=require("@agimon-ai/doompi-telemetry");const c=`@agimon-ai/doompi-config`;async function l(e){let t=await(0,i.connectDoomCordisHost)(e,c),n=t.root.plugin(u,{pi:e});try{await n}catch(e){throw await n.dispose(),await t.dispose(),e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function u(l,{pi:u}){l.inject([o.DOOM_HELP_SERVICE],e=>{let t=n.registerDoomConfigHelp((0,o.requireDoomHelpService)(e));return()=>t.dispose()});let d,f=l.inject([i.DOOM_CORDIS_SESSION_SERVICE],n=>{let o=n.get(i.DOOM_CORDIS_SESSION_SERVICE),l=o.context,f=Symbol(o.generation),p=!0,m=Promise.resolve(),h,g=(0,a.createDoomReadinessCoordinator)({notify(e){let t=e.diagnostics.join(`; `),n=(e.error?.message??t)||`Initialization did not complete.`,r=`${e.packageId} initialization ${e.state}: ${n}`;l.hasUI?l.ui.notify(r,`warning`):process.emitWarning(r)}});n.effect(()=>async()=>{p=!1,d?.token===f&&(d=void 0),await Promise.allSettled([g.dispose(),m]),await h?.shutdown()},`${c}/session`),n.provide(a.DOOM_READINESS_SERVICE,g);let _=(0,s.createDoomTelemetry)({serviceName:`doom-config`,packageName:c,cwd:l.cwd,env:process.env,enableLogs:!0,enableTraces:!0});h=_;let v=g.start(c,o.generation,async i=>{t.acknowledgeDoomConfigTransition(u,l,e.getHarnessState());let a=await t.createDoomConfigContextAsync(l);if(i.aborted)throw i.reason;if(!p||d?.token!==f)throw Error(`Doom Config initialization was superseded: ${o.generation}.`);t.provideDoomConfigContext(n,a,`${o.generation}:config`);let s=a.harness.root??l.cwd,c=a.harness.temporaryDirectory??l.cwd,m=a.harness.mcp&&(0,r.isDoomMcpProjection)(a.harness.mcpProjection)&&a.harness.mcpProjection.repoRoot===s?a.harness.mcpProjection:(0,r.createDisabledDoomMcpProjection)({repoRoot:s,stagingDirectory:c});return n.provide(r.DOOM_MCP_PROJECTION_SERVICE,(0,r.createDoomMcpProjectionService)({sessionId:o.sessionId,generation:`${o.generation}:mcp-projection`,projection:m})),Promise.allSettled([_.recordEvent(`doom_config.context_bound`,{"config.major_mode":a.harness.majorMode,"config.domain_count":a.harness.domains.length,"config.has_profile":a.harness.profile!==void 0,"config.requires_relaunch":a.requiresRelaunch})]),{value:void 0}});d={token:f,sessionManager:l.sessionManager,handle:v},m=v.wait(),Promise.allSettled([m])});u.on(`session_start`,async(e,t)=>{await f.await();let n=d;if(!n||n.sessionManager!==t.sessionManager)throw Error(`Doom Config initialization is unavailable for the active session.`);if(await n.handle.wait(),d?.token!==n.token)throw Error(`Doom Config initialization was superseded before the session barrier completed.`)})}exports.default=l,exports.registerConfigExtension=l;
1
+ const e=require("../harnessStore.cjs"),t=require("./piContext.cjs"),n=require("./helpContribution.cjs");let r=require("@agimon-ai/doompi-extension-contracts/mcp-projection"),i=require("@agimon-ai/doompi-extension-contracts/cordis-host"),a=require("@agimon-ai/doompi-extension-contracts/readiness"),o=require("@agimon-ai/doompi-extension-contracts/help"),s=require("@agimon-ai/doompi-extension-contracts/notification"),c=require("@agimon-ai/doompi-telemetry");const l=`@agimon-ai/doompi-config`;function u(e,t,n,r){let i=(0,s.readDoomNotificationService)(e);if(i){try{Promise.resolve(i.request({body:n,level:r})).catch(()=>void 0)}catch{}return}t.hasUI?t.ui.notify(n,r):process.emitWarning(n)}async function d(e){let t=await(0,i.connectDoomCordisHost)(e,l),n=t.root.plugin(f,{pi:e});try{await n}catch(e){throw await n.dispose(),await t.dispose(),e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function f(s,{pi:d}){s.inject([o.DOOM_HELP_SERVICE],e=>{let t=n.registerDoomConfigHelp((0,o.requireDoomHelpService)(e));return()=>t.dispose()});let f,p=s.inject([i.DOOM_CORDIS_SESSION_SERVICE],n=>{let o=n.get(i.DOOM_CORDIS_SESSION_SERVICE),s=o.context,p=Symbol(o.generation),m=!0,h=Promise.resolve(),g,_=(0,a.createDoomReadinessCoordinator)({notify(e){let t=e.diagnostics.join(`; `),r=(e.error?.message??t)||`Initialization did not complete.`,i=`${e.packageId} initialization ${e.state}: ${r}`;u(n,s,i,`warning`)}});n.effect(()=>async()=>{m=!1,f?.token===p&&(f=void 0),await Promise.allSettled([_.dispose(),h]),await g?.shutdown()},`${l}/session`),n.provide(a.DOOM_READINESS_SERVICE,_);let v=(0,c.createDoomTelemetry)({serviceName:`doom-config`,packageName:l,cwd:s.cwd,env:process.env,enableLogs:!0,enableTraces:!0});g=v;let y=_.start(l,o.generation,async i=>{t.acknowledgeDoomConfigTransition(d,s,e.getHarnessState());let a=await t.createDoomConfigContextAsync(s);if(i.aborted)throw i.reason;if(!m||f?.token!==p)throw Error(`Doom Config initialization was superseded: ${o.generation}.`);t.provideDoomConfigContext(n,a,`${o.generation}:config`);let c=a.harness.root??s.cwd,l=a.harness.temporaryDirectory??s.cwd,u=a.harness.mcp&&(0,r.isDoomMcpProjection)(a.harness.mcpProjection)&&a.harness.mcpProjection.repoRoot===c?a.harness.mcpProjection:(0,r.createDisabledDoomMcpProjection)({repoRoot:c,stagingDirectory:l});return n.provide(r.DOOM_MCP_PROJECTION_SERVICE,(0,r.createDoomMcpProjectionService)({sessionId:o.sessionId,generation:`${o.generation}:mcp-projection`,projection:u})),Promise.allSettled([v.recordEvent(`doom_config.context_bound`,{"config.major_mode":a.harness.majorMode,"config.domain_count":a.harness.domains.length,"config.has_profile":a.harness.profile!==void 0,"config.requires_relaunch":a.requiresRelaunch})]),{value:void 0}});f={token:p,sessionManager:s.sessionManager,handle:y},h=y.wait(),Promise.allSettled([h])});d.on(`session_start`,async(e,t)=>{await p.await();let n=f;if(!n||n.sessionManager!==t.sessionManager)throw Error(`Doom Config initialization is unavailable for the active session.`);if(await n.handle.wait(),f?.token!==n.token)throw Error(`Doom Config initialization was superseded before the session barrier completed.`)})}exports.default=d,exports.registerConfigExtension=d;
2
2
  //# sourceMappingURL=configExtension.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"configExtension.cjs","names":["connectDoomCordisHost","DOOM_HELP_SERVICE","registerDoomConfigHelp","requireDoomHelpService","DOOM_CORDIS_SESSION_SERVICE","createDoomReadinessCoordinator","DOOM_READINESS_SERVICE","createDoomTelemetry","getHarnessState","createDoomConfigContextAsync","isDoomMcpProjection","createDisabledDoomMcpProjection","DOOM_MCP_PROJECTION_SERVICE","createDoomMcpProjectionService"],"sources":["../../../src/adapters/pi/configExtension.ts"],"sourcesContent":["import {\n createDisabledDoomMcpProjection,\n createDoomMcpProjectionService,\n DOOM_MCP_PROJECTION_SERVICE,\n isDoomMcpProjection,\n} from '@agimon-ai/doompi-extension-contracts/mcp-projection';\nimport {\n connectDoomCordisHost,\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport {\n createDoomReadinessCoordinator,\n DOOM_READINESS_SERVICE,\n type DoomReadinessHandle,\n type DoomReadinessNotification,\n} from '@agimon-ai/doompi-extension-contracts/readiness';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { getHarnessState } from '../harnessStore.ts';\nimport {\n acknowledgeDoomConfigTransition,\n createDoomConfigContextAsync,\n provideDoomConfigContext,\n} from './piContext.ts';\nimport { registerDoomConfigHelp } from './helpContribution.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-config';\n\n/** The fixed Config core's single standard Pi factory. */\nexport async function registerConfigExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(configPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n await fiber.dispose();\n await connection.dispose();\n throw error;\n }\n\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface ConfigPluginOptions {\n readonly pi: ExtensionAPI;\n}\n\ninterface ConfigInitialization {\n readonly token: symbol;\n readonly sessionManager: ExtensionContext['sessionManager'];\n readonly handle: DoomReadinessHandle<void>;\n}\n\nfunction configPlugin(cordis: Context, { pi }: ConfigPluginOptions): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const help = registerDoomConfigHelp(requireDoomHelpService(helpContext));\n return () => help.dispose();\n });\n\n let initialization: ConfigInitialization | undefined;\n const sessionInjection = cordis.inject([DOOM_CORDIS_SESSION_SERVICE], (sessionContext) => {\n const session = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const context = session.context;\n const token = Symbol(session.generation);\n let active = true;\n let initializationTask: Promise<void> = Promise.resolve();\n let telemetry: DoomTelemetry | undefined;\n const readiness = createDoomReadinessCoordinator({\n notify(notification: DoomReadinessNotification) {\n const diagnostics = notification.diagnostics.join('; ');\n const detail = (notification.error?.message ?? diagnostics) || 'Initialization did not complete.';\n const message = `${notification.packageId} initialization ${notification.state}: ${detail}`;\n if (context.hasUI) context.ui.notify(message, 'warning');\n else process.emitWarning(message);\n },\n });\n sessionContext.effect(\n () => async () => {\n active = false;\n if (initialization?.token === token) initialization = undefined;\n await Promise.allSettled([readiness.dispose(), initializationTask]);\n await telemetry?.shutdown();\n },\n `${PACKAGE_SOURCE}/session`,\n );\n\n // Consumers can resolve and register their own work as soon as the host's\n // session fiber activates. Config file I/O continues behind the barrier\n // below so Cordis activation itself stays synchronous.\n sessionContext.provide(DOOM_READINESS_SERVICE, readiness);\n const sessionTelemetry = createDoomTelemetry({\n serviceName: 'doom-config',\n packageName: PACKAGE_SOURCE,\n cwd: context.cwd,\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n telemetry = sessionTelemetry;\n const handle = readiness.start<void>(PACKAGE_SOURCE, session.generation, async (signal) => {\n acknowledgeDoomConfigTransition(pi, context, getHarnessState());\n const config = await createDoomConfigContextAsync(context);\n if (signal.aborted) throw signal.reason;\n if (!active || initialization?.token !== token) {\n throw new Error(`Doom Config initialization was superseded: ${session.generation}.`);\n }\n\n provideDoomConfigContext(sessionContext, config, `${session.generation}:config`);\n const repoRoot = config.harness.root ?? context.cwd;\n const stagingDirectory = config.harness.temporaryDirectory ?? context.cwd;\n const projection =\n config.harness.mcp &&\n isDoomMcpProjection(config.harness.mcpProjection) &&\n config.harness.mcpProjection.repoRoot === repoRoot\n ? config.harness.mcpProjection\n : createDisabledDoomMcpProjection({ repoRoot, stagingDirectory });\n sessionContext.provide(\n DOOM_MCP_PROJECTION_SERVICE,\n createDoomMcpProjectionService({\n sessionId: session.sessionId,\n generation: `${session.generation}:mcp-projection`,\n projection,\n }),\n );\n void Promise.allSettled([\n sessionTelemetry.recordEvent('doom_config.context_bound', {\n 'config.major_mode': config.harness.majorMode,\n 'config.domain_count': config.harness.domains.length,\n 'config.has_profile': config.harness.profile !== undefined,\n 'config.requires_relaunch': config.requiresRelaunch,\n }),\n ]);\n return { value: undefined };\n });\n initialization = { token, sessionManager: context.sessionManager, handle };\n initializationTask = handle.wait();\n // The Pi barrier observes this same promise. Attaching a rejection handler\n // now also covers hosts that dispose before Pi reaches that handler.\n void Promise.allSettled([initializationTask]);\n });\n\n // Pi invokes session_start handlers in registration order. The Cordis host\n // runs first and commits readiness; this Config-owned barrier then preserves\n // the historical guarantee that later package handlers see bound config.\n pi.on('session_start', async (_event, context) => {\n await sessionInjection.await();\n const current = initialization;\n if (!current || current.sessionManager !== context.sessionManager) {\n throw new Error('Doom Config initialization is unavailable for the active session.');\n }\n await current.handle.wait();\n if (initialization?.token !== current.token) {\n throw new Error('Doom Config initialization was superseded before the session barrier completed.');\n }\n });\n}\n\nexport default registerConfigExtension;\n"],"mappings":"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"}
1
+ {"version":3,"file":"configExtension.cjs","names":["readDoomNotificationService","connectDoomCordisHost","DOOM_HELP_SERVICE","registerDoomConfigHelp","requireDoomHelpService","DOOM_CORDIS_SESSION_SERVICE","createDoomReadinessCoordinator","DOOM_READINESS_SERVICE","createDoomTelemetry","getHarnessState","createDoomConfigContextAsync","isDoomMcpProjection","createDisabledDoomMcpProjection","DOOM_MCP_PROJECTION_SERVICE","createDoomMcpProjectionService"],"sources":["../../../src/adapters/pi/configExtension.ts"],"sourcesContent":["import {\n createDisabledDoomMcpProjection,\n createDoomMcpProjectionService,\n DOOM_MCP_PROJECTION_SERVICE,\n isDoomMcpProjection,\n} from '@agimon-ai/doompi-extension-contracts/mcp-projection';\nimport {\n connectDoomCordisHost,\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport {\n createDoomReadinessCoordinator,\n DOOM_READINESS_SERVICE,\n type DoomReadinessHandle,\n type DoomReadinessNotification,\n} from '@agimon-ai/doompi-extension-contracts/readiness';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n type DoomNotificationLevel,\n readDoomNotificationService,\n} from '@agimon-ai/doompi-extension-contracts/notification';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { getHarnessState } from '../harnessStore.ts';\nimport {\n acknowledgeDoomConfigTransition,\n createDoomConfigContextAsync,\n provideDoomConfigContext,\n} from './piContext.ts';\nimport { registerDoomConfigHelp } from './helpContribution.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-config';\n\nfunction notify(cordis: Context, context: ExtensionContext, body: string, level: DoomNotificationLevel): void {\n const service = readDoomNotificationService(cordis);\n if (service) {\n try {\n void Promise.resolve(service.request({ body, level })).catch(() => undefined);\n } catch {\n // Notification delivery is best effort and does not fall back after routing.\n }\n return;\n }\n if (context.hasUI) context.ui.notify(body, level);\n else process.emitWarning(body);\n}\n\n/** The fixed Config core's single standard Pi factory. */\nexport async function registerConfigExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(configPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n await fiber.dispose();\n await connection.dispose();\n throw error;\n }\n\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface ConfigPluginOptions {\n readonly pi: ExtensionAPI;\n}\n\ninterface ConfigInitialization {\n readonly token: symbol;\n readonly sessionManager: ExtensionContext['sessionManager'];\n readonly handle: DoomReadinessHandle<void>;\n}\n\nfunction configPlugin(cordis: Context, { pi }: ConfigPluginOptions): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const help = registerDoomConfigHelp(requireDoomHelpService(helpContext));\n return () => help.dispose();\n });\n\n let initialization: ConfigInitialization | undefined;\n const sessionInjection = cordis.inject([DOOM_CORDIS_SESSION_SERVICE], (sessionContext) => {\n const session = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const context = session.context;\n const token = Symbol(session.generation);\n let active = true;\n let initializationTask: Promise<void> = Promise.resolve();\n let telemetry: DoomTelemetry | undefined;\n const readiness = createDoomReadinessCoordinator({\n notify(notification: DoomReadinessNotification) {\n const diagnostics = notification.diagnostics.join('; ');\n const detail = (notification.error?.message ?? diagnostics) || 'Initialization did not complete.';\n const message = `${notification.packageId} initialization ${notification.state}: ${detail}`;\n notify(sessionContext, context, message, 'warning');\n },\n });\n sessionContext.effect(\n () => async () => {\n active = false;\n if (initialization?.token === token) initialization = undefined;\n await Promise.allSettled([readiness.dispose(), initializationTask]);\n await telemetry?.shutdown();\n },\n `${PACKAGE_SOURCE}/session`,\n );\n\n // Consumers can resolve and register their own work as soon as the host's\n // session fiber activates. Config file I/O continues behind the barrier\n // below so Cordis activation itself stays synchronous.\n sessionContext.provide(DOOM_READINESS_SERVICE, readiness);\n const sessionTelemetry = createDoomTelemetry({\n serviceName: 'doom-config',\n packageName: PACKAGE_SOURCE,\n cwd: context.cwd,\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n telemetry = sessionTelemetry;\n const handle = readiness.start<void>(PACKAGE_SOURCE, session.generation, async (signal) => {\n acknowledgeDoomConfigTransition(pi, context, getHarnessState());\n const config = await createDoomConfigContextAsync(context);\n if (signal.aborted) throw signal.reason;\n if (!active || initialization?.token !== token) {\n throw new Error(`Doom Config initialization was superseded: ${session.generation}.`);\n }\n\n provideDoomConfigContext(sessionContext, config, `${session.generation}:config`);\n const repoRoot = config.harness.root ?? context.cwd;\n const stagingDirectory = config.harness.temporaryDirectory ?? context.cwd;\n const projection =\n config.harness.mcp &&\n isDoomMcpProjection(config.harness.mcpProjection) &&\n config.harness.mcpProjection.repoRoot === repoRoot\n ? config.harness.mcpProjection\n : createDisabledDoomMcpProjection({ repoRoot, stagingDirectory });\n sessionContext.provide(\n DOOM_MCP_PROJECTION_SERVICE,\n createDoomMcpProjectionService({\n sessionId: session.sessionId,\n generation: `${session.generation}:mcp-projection`,\n projection,\n }),\n );\n void Promise.allSettled([\n sessionTelemetry.recordEvent('doom_config.context_bound', {\n 'config.major_mode': config.harness.majorMode,\n 'config.domain_count': config.harness.domains.length,\n 'config.has_profile': config.harness.profile !== undefined,\n 'config.requires_relaunch': config.requiresRelaunch,\n }),\n ]);\n return { value: undefined };\n });\n initialization = { token, sessionManager: context.sessionManager, handle };\n initializationTask = handle.wait();\n // The Pi barrier observes this same promise. Attaching a rejection handler\n // now also covers hosts that dispose before Pi reaches that handler.\n void Promise.allSettled([initializationTask]);\n });\n\n // Pi invokes session_start handlers in registration order. The Cordis host\n // runs first and commits readiness; this Config-owned barrier then preserves\n // the historical guarantee that later package handlers see bound config.\n pi.on('session_start', async (_event, context) => {\n await sessionInjection.await();\n const current = initialization;\n if (!current || current.sessionManager !== context.sessionManager) {\n throw new Error('Doom Config initialization is unavailable for the active session.');\n }\n await current.handle.wait();\n if (initialization?.token !== current.token) {\n throw new Error('Doom Config initialization was superseded before the session barrier completed.');\n }\n });\n}\n\nexport default registerConfigExtension;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"configExtension.d.cts","names":[],"sources":["../../../src/adapters/pi/configExtension.ts"],"mappings":";;;iBAgCsB,wBAAwB,IAAI,eAAe"}
1
+ {"version":3,"file":"configExtension.d.cts","names":[],"sources":["../../../src/adapters/pi/configExtension.ts"],"mappings":";;;iBAkDsB,wBAAwB,IAAI,eAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"configExtension.d.mts","names":[],"sources":["../../../src/adapters/pi/configExtension.ts"],"mappings":";;;iBAgCsB,wBAAwB,IAAI,eAAe"}
1
+ {"version":3,"file":"configExtension.d.mts","names":[],"sources":["../../../src/adapters/pi/configExtension.ts"],"mappings":";;;iBAkDsB,wBAAwB,IAAI,eAAe"}
@@ -1,2 +1,2 @@
1
- import{getHarnessState as e}from"../harnessStore.mjs";import{acknowledgeDoomConfigTransition as t,createDoomConfigContextAsync as n,provideDoomConfigContext as r}from"./piContext.mjs";import{registerDoomConfigHelp as i}from"./helpContribution.mjs";import{DOOM_MCP_PROJECTION_SERVICE as a,createDisabledDoomMcpProjection as o,createDoomMcpProjectionService as s,isDoomMcpProjection as c}from"@agimon-ai/doompi-extension-contracts/mcp-projection";import{DOOM_CORDIS_SESSION_SERVICE as l,connectDoomCordisHost as u}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_READINESS_SERVICE as d,createDoomReadinessCoordinator as f}from"@agimon-ai/doompi-extension-contracts/readiness";import{DOOM_HELP_SERVICE as p,requireDoomHelpService as m}from"@agimon-ai/doompi-extension-contracts/help";import{createDoomTelemetry as h}from"@agimon-ai/doompi-telemetry";const g=`@agimon-ai/doompi-config`;async function _(e){let t=await u(e,g),n=t.root.plugin(v,{pi:e});try{await n}catch(e){throw await n.dispose(),await t.dispose(),e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function v(u,{pi:_}){u.inject([p],e=>{let t=i(m(e));return()=>t.dispose()});let v,y=u.inject([l],i=>{let u=i.get(l),p=u.context,m=Symbol(u.generation),y=!0,b=Promise.resolve(),x,S=f({notify(e){let t=e.diagnostics.join(`; `),n=(e.error?.message??t)||`Initialization did not complete.`,r=`${e.packageId} initialization ${e.state}: ${n}`;p.hasUI?p.ui.notify(r,`warning`):process.emitWarning(r)}});i.effect(()=>async()=>{y=!1,v?.token===m&&(v=void 0),await Promise.allSettled([S.dispose(),b]),await x?.shutdown()},`${g}/session`),i.provide(d,S);let C=h({serviceName:`doom-config`,packageName:g,cwd:p.cwd,env:process.env,enableLogs:!0,enableTraces:!0});x=C;let w=S.start(g,u.generation,async l=>{t(_,p,e());let d=await n(p);if(l.aborted)throw l.reason;if(!y||v?.token!==m)throw Error(`Doom Config initialization was superseded: ${u.generation}.`);r(i,d,`${u.generation}:config`);let f=d.harness.root??p.cwd,h=d.harness.temporaryDirectory??p.cwd,g=d.harness.mcp&&c(d.harness.mcpProjection)&&d.harness.mcpProjection.repoRoot===f?d.harness.mcpProjection:o({repoRoot:f,stagingDirectory:h});return i.provide(a,s({sessionId:u.sessionId,generation:`${u.generation}:mcp-projection`,projection:g})),Promise.allSettled([C.recordEvent(`doom_config.context_bound`,{"config.major_mode":d.harness.majorMode,"config.domain_count":d.harness.domains.length,"config.has_profile":d.harness.profile!==void 0,"config.requires_relaunch":d.requiresRelaunch})]),{value:void 0}});v={token:m,sessionManager:p.sessionManager,handle:w},b=w.wait(),Promise.allSettled([b])});_.on(`session_start`,async(e,t)=>{await y.await();let n=v;if(!n||n.sessionManager!==t.sessionManager)throw Error(`Doom Config initialization is unavailable for the active session.`);if(await n.handle.wait(),v?.token!==n.token)throw Error(`Doom Config initialization was superseded before the session barrier completed.`)})}export{_ as default,_ as registerConfigExtension};
1
+ import{getHarnessState as e}from"../harnessStore.mjs";import{acknowledgeDoomConfigTransition as t,createDoomConfigContextAsync as n,provideDoomConfigContext as r}from"./piContext.mjs";import{registerDoomConfigHelp as i}from"./helpContribution.mjs";import{DOOM_MCP_PROJECTION_SERVICE as a,createDisabledDoomMcpProjection as o,createDoomMcpProjectionService as s,isDoomMcpProjection as c}from"@agimon-ai/doompi-extension-contracts/mcp-projection";import{DOOM_CORDIS_SESSION_SERVICE as l,connectDoomCordisHost as u}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_READINESS_SERVICE as d,createDoomReadinessCoordinator as f}from"@agimon-ai/doompi-extension-contracts/readiness";import{DOOM_HELP_SERVICE as p,requireDoomHelpService as m}from"@agimon-ai/doompi-extension-contracts/help";import{readDoomNotificationService as h}from"@agimon-ai/doompi-extension-contracts/notification";import{createDoomTelemetry as g}from"@agimon-ai/doompi-telemetry";const _=`@agimon-ai/doompi-config`;function v(e,t,n,r){let i=h(e);if(i){try{Promise.resolve(i.request({body:n,level:r})).catch(()=>void 0)}catch{}return}t.hasUI?t.ui.notify(n,r):process.emitWarning(n)}async function y(e){let t=await u(e,_),n=t.root.plugin(b,{pi:e});try{await n}catch(e){throw await n.dispose(),await t.dispose(),e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function b(u,{pi:h}){u.inject([p],e=>{let t=i(m(e));return()=>t.dispose()});let y,b=u.inject([l],i=>{let u=i.get(l),p=u.context,m=Symbol(u.generation),b=!0,x=Promise.resolve(),S,C=f({notify(e){let t=e.diagnostics.join(`; `),n=(e.error?.message??t)||`Initialization did not complete.`,r=`${e.packageId} initialization ${e.state}: ${n}`;v(i,p,r,`warning`)}});i.effect(()=>async()=>{b=!1,y?.token===m&&(y=void 0),await Promise.allSettled([C.dispose(),x]),await S?.shutdown()},`${_}/session`),i.provide(d,C);let w=g({serviceName:`doom-config`,packageName:_,cwd:p.cwd,env:process.env,enableLogs:!0,enableTraces:!0});S=w;let T=C.start(_,u.generation,async l=>{t(h,p,e());let d=await n(p);if(l.aborted)throw l.reason;if(!b||y?.token!==m)throw Error(`Doom Config initialization was superseded: ${u.generation}.`);r(i,d,`${u.generation}:config`);let f=d.harness.root??p.cwd,g=d.harness.temporaryDirectory??p.cwd,_=d.harness.mcp&&c(d.harness.mcpProjection)&&d.harness.mcpProjection.repoRoot===f?d.harness.mcpProjection:o({repoRoot:f,stagingDirectory:g});return i.provide(a,s({sessionId:u.sessionId,generation:`${u.generation}:mcp-projection`,projection:_})),Promise.allSettled([w.recordEvent(`doom_config.context_bound`,{"config.major_mode":d.harness.majorMode,"config.domain_count":d.harness.domains.length,"config.has_profile":d.harness.profile!==void 0,"config.requires_relaunch":d.requiresRelaunch})]),{value:void 0}});y={token:m,sessionManager:p.sessionManager,handle:T},x=T.wait(),Promise.allSettled([x])});h.on(`session_start`,async(e,t)=>{await b.await();let n=y;if(!n||n.sessionManager!==t.sessionManager)throw Error(`Doom Config initialization is unavailable for the active session.`);if(await n.handle.wait(),y?.token!==n.token)throw Error(`Doom Config initialization was superseded before the session barrier completed.`)})}export{y as default,y as registerConfigExtension};
2
2
  //# sourceMappingURL=configExtension.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"configExtension.mjs","names":[],"sources":["../../../src/adapters/pi/configExtension.ts"],"sourcesContent":["import {\n createDisabledDoomMcpProjection,\n createDoomMcpProjectionService,\n DOOM_MCP_PROJECTION_SERVICE,\n isDoomMcpProjection,\n} from '@agimon-ai/doompi-extension-contracts/mcp-projection';\nimport {\n connectDoomCordisHost,\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport {\n createDoomReadinessCoordinator,\n DOOM_READINESS_SERVICE,\n type DoomReadinessHandle,\n type DoomReadinessNotification,\n} from '@agimon-ai/doompi-extension-contracts/readiness';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { getHarnessState } from '../harnessStore.ts';\nimport {\n acknowledgeDoomConfigTransition,\n createDoomConfigContextAsync,\n provideDoomConfigContext,\n} from './piContext.ts';\nimport { registerDoomConfigHelp } from './helpContribution.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-config';\n\n/** The fixed Config core's single standard Pi factory. */\nexport async function registerConfigExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(configPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n await fiber.dispose();\n await connection.dispose();\n throw error;\n }\n\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface ConfigPluginOptions {\n readonly pi: ExtensionAPI;\n}\n\ninterface ConfigInitialization {\n readonly token: symbol;\n readonly sessionManager: ExtensionContext['sessionManager'];\n readonly handle: DoomReadinessHandle<void>;\n}\n\nfunction configPlugin(cordis: Context, { pi }: ConfigPluginOptions): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const help = registerDoomConfigHelp(requireDoomHelpService(helpContext));\n return () => help.dispose();\n });\n\n let initialization: ConfigInitialization | undefined;\n const sessionInjection = cordis.inject([DOOM_CORDIS_SESSION_SERVICE], (sessionContext) => {\n const session = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const context = session.context;\n const token = Symbol(session.generation);\n let active = true;\n let initializationTask: Promise<void> = Promise.resolve();\n let telemetry: DoomTelemetry | undefined;\n const readiness = createDoomReadinessCoordinator({\n notify(notification: DoomReadinessNotification) {\n const diagnostics = notification.diagnostics.join('; ');\n const detail = (notification.error?.message ?? diagnostics) || 'Initialization did not complete.';\n const message = `${notification.packageId} initialization ${notification.state}: ${detail}`;\n if (context.hasUI) context.ui.notify(message, 'warning');\n else process.emitWarning(message);\n },\n });\n sessionContext.effect(\n () => async () => {\n active = false;\n if (initialization?.token === token) initialization = undefined;\n await Promise.allSettled([readiness.dispose(), initializationTask]);\n await telemetry?.shutdown();\n },\n `${PACKAGE_SOURCE}/session`,\n );\n\n // Consumers can resolve and register their own work as soon as the host's\n // session fiber activates. Config file I/O continues behind the barrier\n // below so Cordis activation itself stays synchronous.\n sessionContext.provide(DOOM_READINESS_SERVICE, readiness);\n const sessionTelemetry = createDoomTelemetry({\n serviceName: 'doom-config',\n packageName: PACKAGE_SOURCE,\n cwd: context.cwd,\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n telemetry = sessionTelemetry;\n const handle = readiness.start<void>(PACKAGE_SOURCE, session.generation, async (signal) => {\n acknowledgeDoomConfigTransition(pi, context, getHarnessState());\n const config = await createDoomConfigContextAsync(context);\n if (signal.aborted) throw signal.reason;\n if (!active || initialization?.token !== token) {\n throw new Error(`Doom Config initialization was superseded: ${session.generation}.`);\n }\n\n provideDoomConfigContext(sessionContext, config, `${session.generation}:config`);\n const repoRoot = config.harness.root ?? context.cwd;\n const stagingDirectory = config.harness.temporaryDirectory ?? context.cwd;\n const projection =\n config.harness.mcp &&\n isDoomMcpProjection(config.harness.mcpProjection) &&\n config.harness.mcpProjection.repoRoot === repoRoot\n ? config.harness.mcpProjection\n : createDisabledDoomMcpProjection({ repoRoot, stagingDirectory });\n sessionContext.provide(\n DOOM_MCP_PROJECTION_SERVICE,\n createDoomMcpProjectionService({\n sessionId: session.sessionId,\n generation: `${session.generation}:mcp-projection`,\n projection,\n }),\n );\n void Promise.allSettled([\n sessionTelemetry.recordEvent('doom_config.context_bound', {\n 'config.major_mode': config.harness.majorMode,\n 'config.domain_count': config.harness.domains.length,\n 'config.has_profile': config.harness.profile !== undefined,\n 'config.requires_relaunch': config.requiresRelaunch,\n }),\n ]);\n return { value: undefined };\n });\n initialization = { token, sessionManager: context.sessionManager, handle };\n initializationTask = handle.wait();\n // The Pi barrier observes this same promise. Attaching a rejection handler\n // now also covers hosts that dispose before Pi reaches that handler.\n void Promise.allSettled([initializationTask]);\n });\n\n // Pi invokes session_start handlers in registration order. The Cordis host\n // runs first and commits readiness; this Config-owned barrier then preserves\n // the historical guarantee that later package handlers see bound config.\n pi.on('session_start', async (_event, context) => {\n await sessionInjection.await();\n const current = initialization;\n if (!current || current.sessionManager !== context.sessionManager) {\n throw new Error('Doom Config initialization is unavailable for the active session.');\n }\n await current.handle.wait();\n if (initialization?.token !== current.token) {\n throw new Error('Doom Config initialization was superseded before the session barrier completed.');\n }\n });\n}\n\nexport default registerConfigExtension;\n"],"mappings":"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"}
1
+ {"version":3,"file":"configExtension.mjs","names":[],"sources":["../../../src/adapters/pi/configExtension.ts"],"sourcesContent":["import {\n createDisabledDoomMcpProjection,\n createDoomMcpProjectionService,\n DOOM_MCP_PROJECTION_SERVICE,\n isDoomMcpProjection,\n} from '@agimon-ai/doompi-extension-contracts/mcp-projection';\nimport {\n connectDoomCordisHost,\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport {\n createDoomReadinessCoordinator,\n DOOM_READINESS_SERVICE,\n type DoomReadinessHandle,\n type DoomReadinessNotification,\n} from '@agimon-ai/doompi-extension-contracts/readiness';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n type DoomNotificationLevel,\n readDoomNotificationService,\n} from '@agimon-ai/doompi-extension-contracts/notification';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { getHarnessState } from '../harnessStore.ts';\nimport {\n acknowledgeDoomConfigTransition,\n createDoomConfigContextAsync,\n provideDoomConfigContext,\n} from './piContext.ts';\nimport { registerDoomConfigHelp } from './helpContribution.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-config';\n\nfunction notify(cordis: Context, context: ExtensionContext, body: string, level: DoomNotificationLevel): void {\n const service = readDoomNotificationService(cordis);\n if (service) {\n try {\n void Promise.resolve(service.request({ body, level })).catch(() => undefined);\n } catch {\n // Notification delivery is best effort and does not fall back after routing.\n }\n return;\n }\n if (context.hasUI) context.ui.notify(body, level);\n else process.emitWarning(body);\n}\n\n/** The fixed Config core's single standard Pi factory. */\nexport async function registerConfigExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(configPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n await fiber.dispose();\n await connection.dispose();\n throw error;\n }\n\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface ConfigPluginOptions {\n readonly pi: ExtensionAPI;\n}\n\ninterface ConfigInitialization {\n readonly token: symbol;\n readonly sessionManager: ExtensionContext['sessionManager'];\n readonly handle: DoomReadinessHandle<void>;\n}\n\nfunction configPlugin(cordis: Context, { pi }: ConfigPluginOptions): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const help = registerDoomConfigHelp(requireDoomHelpService(helpContext));\n return () => help.dispose();\n });\n\n let initialization: ConfigInitialization | undefined;\n const sessionInjection = cordis.inject([DOOM_CORDIS_SESSION_SERVICE], (sessionContext) => {\n const session = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const context = session.context;\n const token = Symbol(session.generation);\n let active = true;\n let initializationTask: Promise<void> = Promise.resolve();\n let telemetry: DoomTelemetry | undefined;\n const readiness = createDoomReadinessCoordinator({\n notify(notification: DoomReadinessNotification) {\n const diagnostics = notification.diagnostics.join('; ');\n const detail = (notification.error?.message ?? diagnostics) || 'Initialization did not complete.';\n const message = `${notification.packageId} initialization ${notification.state}: ${detail}`;\n notify(sessionContext, context, message, 'warning');\n },\n });\n sessionContext.effect(\n () => async () => {\n active = false;\n if (initialization?.token === token) initialization = undefined;\n await Promise.allSettled([readiness.dispose(), initializationTask]);\n await telemetry?.shutdown();\n },\n `${PACKAGE_SOURCE}/session`,\n );\n\n // Consumers can resolve and register their own work as soon as the host's\n // session fiber activates. Config file I/O continues behind the barrier\n // below so Cordis activation itself stays synchronous.\n sessionContext.provide(DOOM_READINESS_SERVICE, readiness);\n const sessionTelemetry = createDoomTelemetry({\n serviceName: 'doom-config',\n packageName: PACKAGE_SOURCE,\n cwd: context.cwd,\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n telemetry = sessionTelemetry;\n const handle = readiness.start<void>(PACKAGE_SOURCE, session.generation, async (signal) => {\n acknowledgeDoomConfigTransition(pi, context, getHarnessState());\n const config = await createDoomConfigContextAsync(context);\n if (signal.aborted) throw signal.reason;\n if (!active || initialization?.token !== token) {\n throw new Error(`Doom Config initialization was superseded: ${session.generation}.`);\n }\n\n provideDoomConfigContext(sessionContext, config, `${session.generation}:config`);\n const repoRoot = config.harness.root ?? context.cwd;\n const stagingDirectory = config.harness.temporaryDirectory ?? context.cwd;\n const projection =\n config.harness.mcp &&\n isDoomMcpProjection(config.harness.mcpProjection) &&\n config.harness.mcpProjection.repoRoot === repoRoot\n ? config.harness.mcpProjection\n : createDisabledDoomMcpProjection({ repoRoot, stagingDirectory });\n sessionContext.provide(\n DOOM_MCP_PROJECTION_SERVICE,\n createDoomMcpProjectionService({\n sessionId: session.sessionId,\n generation: `${session.generation}:mcp-projection`,\n projection,\n }),\n );\n void Promise.allSettled([\n sessionTelemetry.recordEvent('doom_config.context_bound', {\n 'config.major_mode': config.harness.majorMode,\n 'config.domain_count': config.harness.domains.length,\n 'config.has_profile': config.harness.profile !== undefined,\n 'config.requires_relaunch': config.requiresRelaunch,\n }),\n ]);\n return { value: undefined };\n });\n initialization = { token, sessionManager: context.sessionManager, handle };\n initializationTask = handle.wait();\n // The Pi barrier observes this same promise. Attaching a rejection handler\n // now also covers hosts that dispose before Pi reaches that handler.\n void Promise.allSettled([initializationTask]);\n });\n\n // Pi invokes session_start handlers in registration order. The Cordis host\n // runs first and commits readiness; this Config-owned barrier then preserves\n // the historical guarantee that later package handlers see bound config.\n pi.on('session_start', async (_event, context) => {\n await sessionInjection.await();\n const current = initialization;\n if (!current || current.sessionManager !== context.sessionManager) {\n throw new Error('Doom Config initialization is unavailable for the active session.');\n }\n await current.handle.wait();\n if (initialization?.token !== current.token) {\n throw new Error('Doom Config initialization was superseded before the session barrier completed.');\n }\n });\n}\n\nexport default registerConfigExtension;\n"],"mappings":"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"}
package/dist/config.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/configPolicy.cjs"),t=require("./adapters/config.cjs");exports.DOOM_PLANNING_THINKING_LEVELS=e.DOOM_PLANNING_THINKING_LEVELS,exports.DOOM_VOICE_ENGINES=e.DOOM_VOICE_ENGINES,exports.DOOM_VOICE_TTS_ENGINES=e.DOOM_VOICE_TTS_ENGINES,exports.globalDoomConfigDirectory=t.globalDoomConfigDirectory,exports.globalDoomConfigPath=t.globalDoomConfigPath,exports.loadDoomConfig=t.loadDoomConfig,exports.loadDoomConfigAsync=t.loadDoomConfigAsync,exports.mergeDoomConfigs=e.mergeDoomConfigs,exports.parseDoomConfig=e.parseDoomConfig,exports.parsePlanningModeConfig=e.parsePlanningModeConfig,exports.repositoryDoomConfigPath=t.repositoryDoomConfigPath,exports.resolvePlanningPlansDirectory=t.resolvePlanningPlansDirectory,exports.resolveVoiceConfig=e.resolveVoiceConfig;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/configPolicy.cjs"),t=require("./adapters/config.cjs");exports.DOOM_PLANNING_THINKING_LEVELS=e.DOOM_PLANNING_THINKING_LEVELS,exports.DOOM_VOICE_ENGINES=e.DOOM_VOICE_ENGINES,exports.DOOM_VOICE_TTS_ENGINES=e.DOOM_VOICE_TTS_ENGINES,exports.configLeafKeys=e.configLeafKeys,exports.configRootKeys=e.configRootKeys,exports.configScopeOf=e.configScopeOf,exports.globalDoomConfigDirectory=t.globalDoomConfigDirectory,exports.globalDoomConfigPath=t.globalDoomConfigPath,exports.loadDoomConfig=t.loadDoomConfig,exports.loadDoomConfigAsync=t.loadDoomConfigAsync,exports.loadDoomConfigLayers=t.loadDoomConfigLayers,exports.mergeDoomConfigs=e.mergeDoomConfigs,exports.parseDoomConfig=e.parseDoomConfig,exports.parsePlanningModeConfig=e.parsePlanningModeConfig,exports.repositoryDoomConfigPath=t.repositoryDoomConfigPath,exports.resolvePlanningPlansDirectory=t.resolvePlanningPlansDirectory,exports.resolveVoiceConfig=e.resolveVoiceConfig,exports.valueAtKeyPath=e.valueAtKeyPath;
package/dist/config.d.cts CHANGED
@@ -1,3 +1,4 @@
1
- import { globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, loadDoomConfigAsync, repositoryDoomConfigPath, resolvePlanningPlansDirectory } from "./adapters/config.cjs";
2
- import { DOOM_PLANNING_THINKING_LEVELS, DOOM_VOICE_ENGINES, DOOM_VOICE_TTS_ENGINES, mergeDoomConfigs, parseDoomConfig, parsePlanningModeConfig, resolveVoiceConfig } from "./services/configPolicy.cjs";
3
- export { DOOM_PLANNING_THINKING_LEVELS, DOOM_VOICE_ENGINES, DOOM_VOICE_TTS_ENGINES, globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, loadDoomConfigAsync, mergeDoomConfigs, parseDoomConfig, parsePlanningModeConfig, repositoryDoomConfigPath, resolvePlanningPlansDirectory, resolveVoiceConfig };
1
+ import { ConfigValueOrigin, DoomConfigLayer, DoomConfigLayers } from "./types/config.cjs";
2
+ import { globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, loadDoomConfigAsync, loadDoomConfigLayers, repositoryDoomConfigPath, resolvePlanningPlansDirectory } from "./adapters/config.cjs";
3
+ import { ConfigKeyScope, DOOM_PLANNING_THINKING_LEVELS, DOOM_VOICE_ENGINES, DOOM_VOICE_TTS_ENGINES, configLeafKeys, configRootKeys, configScopeOf, mergeDoomConfigs, parseDoomConfig, parsePlanningModeConfig, resolveVoiceConfig, valueAtKeyPath } from "./services/configPolicy.cjs";
4
+ export { type ConfigKeyScope, type ConfigValueOrigin, DOOM_PLANNING_THINKING_LEVELS, DOOM_VOICE_ENGINES, DOOM_VOICE_TTS_ENGINES, type DoomConfigLayer, type DoomConfigLayers, configLeafKeys, configRootKeys, configScopeOf, globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, loadDoomConfigAsync, loadDoomConfigLayers, mergeDoomConfigs, parseDoomConfig, parsePlanningModeConfig, repositoryDoomConfigPath, resolvePlanningPlansDirectory, resolveVoiceConfig, valueAtKeyPath };
package/dist/config.d.mts CHANGED
@@ -1,3 +1,4 @@
1
- import { globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, loadDoomConfigAsync, repositoryDoomConfigPath, resolvePlanningPlansDirectory } from "./adapters/config.mjs";
2
- import { DOOM_PLANNING_THINKING_LEVELS, DOOM_VOICE_ENGINES, DOOM_VOICE_TTS_ENGINES, mergeDoomConfigs, parseDoomConfig, parsePlanningModeConfig, resolveVoiceConfig } from "./services/configPolicy.mjs";
3
- export { DOOM_PLANNING_THINKING_LEVELS, DOOM_VOICE_ENGINES, DOOM_VOICE_TTS_ENGINES, globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, loadDoomConfigAsync, mergeDoomConfigs, parseDoomConfig, parsePlanningModeConfig, repositoryDoomConfigPath, resolvePlanningPlansDirectory, resolveVoiceConfig };
1
+ import { ConfigValueOrigin, DoomConfigLayer, DoomConfigLayers } from "./types/config.mjs";
2
+ import { globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, loadDoomConfigAsync, loadDoomConfigLayers, repositoryDoomConfigPath, resolvePlanningPlansDirectory } from "./adapters/config.mjs";
3
+ import { ConfigKeyScope, DOOM_PLANNING_THINKING_LEVELS, DOOM_VOICE_ENGINES, DOOM_VOICE_TTS_ENGINES, configLeafKeys, configRootKeys, configScopeOf, mergeDoomConfigs, parseDoomConfig, parsePlanningModeConfig, resolveVoiceConfig, valueAtKeyPath } from "./services/configPolicy.mjs";
4
+ export { type ConfigKeyScope, type ConfigValueOrigin, DOOM_PLANNING_THINKING_LEVELS, DOOM_VOICE_ENGINES, DOOM_VOICE_TTS_ENGINES, type DoomConfigLayer, type DoomConfigLayers, configLeafKeys, configRootKeys, configScopeOf, globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, loadDoomConfigAsync, loadDoomConfigLayers, mergeDoomConfigs, parseDoomConfig, parsePlanningModeConfig, repositoryDoomConfigPath, resolvePlanningPlansDirectory, resolveVoiceConfig, valueAtKeyPath };
package/dist/config.mjs CHANGED
@@ -1 +1 @@
1
- import{DOOM_PLANNING_THINKING_LEVELS as e,DOOM_VOICE_ENGINES as t,DOOM_VOICE_TTS_ENGINES as n,mergeDoomConfigs as r,parseDoomConfig as i,parsePlanningModeConfig as a,resolveVoiceConfig as o}from"./services/configPolicy.mjs";import{globalDoomConfigDirectory as s,globalDoomConfigPath as c,loadDoomConfig as l,loadDoomConfigAsync as u,repositoryDoomConfigPath as d,resolvePlanningPlansDirectory as f}from"./adapters/config.mjs";export{e as DOOM_PLANNING_THINKING_LEVELS,t as DOOM_VOICE_ENGINES,n as DOOM_VOICE_TTS_ENGINES,s as globalDoomConfigDirectory,c as globalDoomConfigPath,l as loadDoomConfig,u as loadDoomConfigAsync,r as mergeDoomConfigs,i as parseDoomConfig,a as parsePlanningModeConfig,d as repositoryDoomConfigPath,f as resolvePlanningPlansDirectory,o as resolveVoiceConfig};
1
+ import{DOOM_PLANNING_THINKING_LEVELS as e,DOOM_VOICE_ENGINES as t,DOOM_VOICE_TTS_ENGINES as n,configLeafKeys as r,configRootKeys as i,configScopeOf as a,mergeDoomConfigs as o,parseDoomConfig as s,parsePlanningModeConfig as c,resolveVoiceConfig as l,valueAtKeyPath as u}from"./services/configPolicy.mjs";import{globalDoomConfigDirectory as d,globalDoomConfigPath as f,loadDoomConfig as p,loadDoomConfigAsync as m,loadDoomConfigLayers as h,repositoryDoomConfigPath as g,resolvePlanningPlansDirectory as _}from"./adapters/config.mjs";export{e as DOOM_PLANNING_THINKING_LEVELS,t as DOOM_VOICE_ENGINES,n as DOOM_VOICE_TTS_ENGINES,r as configLeafKeys,i as configRootKeys,a as configScopeOf,d as globalDoomConfigDirectory,f as globalDoomConfigPath,p as loadDoomConfig,m as loadDoomConfigAsync,h as loadDoomConfigLayers,o as mergeDoomConfigs,s as parseDoomConfig,c as parsePlanningModeConfig,g as repositoryDoomConfigPath,_ as resolvePlanningPlansDirectory,l as resolveVoiceConfig,u as valueAtKeyPath};
@@ -1,2 +1,2 @@
1
- import { DoomConfigEdit, WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues } from "./adapters/configWriter.cjs";
2
- export { type DoomConfigEdit, type WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues };
1
+ import { DoomConfigEdit, DoomConfigScope, WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues } from "./adapters/configWriter.cjs";
2
+ export { type DoomConfigEdit, type DoomConfigScope, type WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues };
@@ -1,2 +1,2 @@
1
- import { DoomConfigEdit, WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues } from "./adapters/configWriter.mjs";
2
- export { type DoomConfigEdit, type WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues };
1
+ import { DoomConfigEdit, DoomConfigScope, WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues } from "./adapters/configWriter.mjs";
2
+ export { type DoomConfigEdit, type DoomConfigScope, type WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues };