@agimon-ai/doompi-config 0.0.1-alpha.2 → 0.0.1-alpha.20

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 (309) hide show
  1. package/README.md +139 -14
  2. package/dist/adapters/atomicJson.cjs +2 -0
  3. package/dist/adapters/atomicJson.cjs.map +1 -0
  4. package/dist/adapters/atomicJson.d.cts +6 -0
  5. package/dist/adapters/atomicJson.d.cts.map +1 -0
  6. package/dist/adapters/atomicJson.d.mts +6 -0
  7. package/dist/adapters/atomicJson.d.mts.map +1 -0
  8. package/dist/adapters/atomicJson.mjs +2 -0
  9. package/dist/adapters/atomicJson.mjs.map +1 -0
  10. package/dist/adapters/config.cjs +2 -0
  11. package/dist/adapters/config.cjs.map +1 -0
  12. package/dist/adapters/config.d.cts +11 -0
  13. package/dist/adapters/config.d.cts.map +1 -0
  14. package/dist/adapters/config.d.mts +11 -0
  15. package/dist/adapters/config.d.mts.map +1 -0
  16. package/dist/adapters/config.mjs +2 -0
  17. package/dist/adapters/config.mjs.map +1 -0
  18. package/dist/adapters/configWriter.cjs +2 -0
  19. package/dist/adapters/configWriter.cjs.map +1 -0
  20. package/dist/adapters/configWriter.d.cts +52 -0
  21. package/dist/adapters/configWriter.d.cts.map +1 -0
  22. package/dist/adapters/configWriter.d.mts +52 -0
  23. package/dist/adapters/configWriter.d.mts.map +1 -0
  24. package/dist/adapters/configWriter.mjs +2 -0
  25. package/dist/adapters/configWriter.mjs.map +1 -0
  26. package/dist/adapters/domains.cjs +2 -0
  27. package/dist/adapters/domains.cjs.map +1 -0
  28. package/dist/adapters/domains.d.cts +25 -0
  29. package/dist/adapters/domains.d.cts.map +1 -0
  30. package/dist/adapters/domains.d.mts +25 -0
  31. package/dist/adapters/domains.d.mts.map +1 -0
  32. package/dist/adapters/domains.mjs +2 -0
  33. package/dist/adapters/domains.mjs.map +1 -0
  34. package/dist/adapters/harnessState.cjs +2 -0
  35. package/dist/adapters/harnessState.cjs.map +1 -0
  36. package/dist/adapters/harnessState.d.cts +50 -0
  37. package/dist/adapters/harnessState.d.cts.map +1 -0
  38. package/dist/adapters/harnessState.d.mts +50 -0
  39. package/dist/adapters/harnessState.d.mts.map +1 -0
  40. package/dist/adapters/harnessState.mjs +2 -0
  41. package/dist/adapters/harnessState.mjs.map +1 -0
  42. package/dist/adapters/harnessStore.cjs +2 -0
  43. package/dist/adapters/harnessStore.cjs.map +1 -0
  44. package/dist/adapters/harnessStore.d.cts +102 -0
  45. package/dist/adapters/harnessStore.d.cts.map +1 -0
  46. package/dist/adapters/harnessStore.d.mts +102 -0
  47. package/dist/adapters/harnessStore.d.mts.map +1 -0
  48. package/dist/adapters/harnessStore.mjs +2 -0
  49. package/dist/adapters/harnessStore.mjs.map +1 -0
  50. package/dist/adapters/init.cjs +178 -0
  51. package/dist/adapters/init.cjs.map +1 -0
  52. package/dist/adapters/init.d.cts +39 -0
  53. package/dist/adapters/init.d.cts.map +1 -0
  54. package/dist/adapters/init.d.mts +39 -0
  55. package/dist/adapters/init.d.mts.map +1 -0
  56. package/dist/adapters/init.mjs +178 -0
  57. package/dist/adapters/init.mjs.map +1 -0
  58. package/dist/adapters/layeredConfig.cjs +2 -0
  59. package/dist/adapters/layeredConfig.cjs.map +1 -0
  60. package/dist/adapters/layeredConfig.d.cts +49 -0
  61. package/dist/adapters/layeredConfig.d.cts.map +1 -0
  62. package/dist/adapters/layeredConfig.d.mts +49 -0
  63. package/dist/adapters/layeredConfig.d.mts.map +1 -0
  64. package/dist/adapters/layeredConfig.mjs +2 -0
  65. package/dist/adapters/layeredConfig.mjs.map +1 -0
  66. package/dist/adapters/majorModes.cjs +2 -0
  67. package/dist/adapters/majorModes.cjs.map +1 -0
  68. package/dist/adapters/majorModes.d.cts +88 -0
  69. package/dist/adapters/majorModes.d.cts.map +1 -0
  70. package/dist/adapters/majorModes.d.mts +88 -0
  71. package/dist/adapters/majorModes.d.mts.map +1 -0
  72. package/dist/adapters/majorModes.mjs +2 -0
  73. package/dist/adapters/majorModes.mjs.map +1 -0
  74. package/dist/adapters/pi/configExtension.cjs +2 -0
  75. package/dist/adapters/pi/configExtension.cjs.map +1 -0
  76. package/dist/adapters/pi/configExtension.d.cts +8 -0
  77. package/dist/adapters/pi/configExtension.d.cts.map +1 -0
  78. package/dist/adapters/pi/configExtension.d.mts +8 -0
  79. package/dist/adapters/pi/configExtension.d.mts.map +1 -0
  80. package/dist/adapters/pi/configExtension.mjs +2 -0
  81. package/dist/adapters/pi/configExtension.mjs.map +1 -0
  82. package/dist/adapters/pi/doomConfigExtension.cjs +2 -0
  83. package/dist/adapters/pi/doomConfigExtension.cjs.map +1 -0
  84. package/dist/adapters/pi/doomConfigExtension.d.cts +8 -0
  85. package/dist/adapters/pi/doomConfigExtension.d.cts.map +1 -0
  86. package/dist/adapters/pi/doomConfigExtension.d.mts +8 -0
  87. package/dist/adapters/pi/doomConfigExtension.d.mts.map +1 -0
  88. package/dist/adapters/pi/doomConfigExtension.mjs +2 -0
  89. package/dist/adapters/pi/doomConfigExtension.mjs.map +1 -0
  90. package/dist/adapters/pi/helpContribution.cjs +2 -0
  91. package/dist/adapters/pi/helpContribution.cjs.map +1 -0
  92. package/dist/adapters/pi/helpContribution.mjs +2 -0
  93. package/dist/adapters/pi/helpContribution.mjs.map +1 -0
  94. package/dist/adapters/pi/piContext.cjs +2 -0
  95. package/dist/adapters/pi/piContext.cjs.map +1 -0
  96. package/dist/adapters/pi/piContext.d.cts +20 -0
  97. package/dist/adapters/pi/piContext.d.cts.map +1 -0
  98. package/dist/adapters/pi/piContext.d.mts +20 -0
  99. package/dist/adapters/pi/piContext.d.mts.map +1 -0
  100. package/dist/adapters/pi/piContext.mjs +2 -0
  101. package/dist/adapters/pi/piContext.mjs.map +1 -0
  102. package/dist/adapters/piConfig.cjs +2 -0
  103. package/dist/adapters/piConfig.cjs.map +1 -0
  104. package/dist/adapters/piConfig.d.cts +8 -0
  105. package/dist/adapters/piConfig.d.cts.map +1 -0
  106. package/dist/adapters/piConfig.d.mts +8 -0
  107. package/dist/adapters/piConfig.d.mts.map +1 -0
  108. package/dist/adapters/piConfig.mjs +2 -0
  109. package/dist/adapters/piConfig.mjs.map +1 -0
  110. package/dist/adapters/pluginCatalog.cjs +2 -0
  111. package/dist/adapters/pluginCatalog.cjs.map +1 -0
  112. package/dist/adapters/pluginCatalog.d.cts +12 -0
  113. package/dist/adapters/pluginCatalog.d.cts.map +1 -0
  114. package/dist/adapters/pluginCatalog.d.mts +12 -0
  115. package/dist/adapters/pluginCatalog.d.mts.map +1 -0
  116. package/dist/adapters/pluginCatalog.mjs +2 -0
  117. package/dist/adapters/pluginCatalog.mjs.map +1 -0
  118. package/dist/adapters/profiles.cjs +4 -0
  119. package/dist/adapters/profiles.cjs.map +1 -0
  120. package/dist/adapters/profiles.d.cts +36 -0
  121. package/dist/adapters/profiles.d.cts.map +1 -0
  122. package/dist/adapters/profiles.d.mts +36 -0
  123. package/dist/adapters/profiles.d.mts.map +1 -0
  124. package/dist/adapters/profiles.mjs +4 -0
  125. package/dist/adapters/profiles.mjs.map +1 -0
  126. package/dist/atomicJson.cjs +1 -2
  127. package/dist/atomicJson.d.cts +2 -6
  128. package/dist/atomicJson.d.mts +2 -6
  129. package/dist/atomicJson.mjs +1 -2
  130. package/dist/config/piConfig.cjs +1 -2
  131. package/dist/config/piConfig.d.cts +2 -7
  132. package/dist/config/piConfig.d.mts +2 -7
  133. package/dist/config/piConfig.mjs +1 -2
  134. package/dist/config/schema.d.cts +2 -33
  135. package/dist/config/schema.d.mts +2 -33
  136. package/dist/config.cjs +1 -2
  137. package/dist/config.d.cts +3 -24
  138. package/dist/config.d.mts +3 -24
  139. package/dist/config.mjs +1 -2
  140. package/dist/configWriter.cjs +1 -2
  141. package/dist/configWriter.d.cts +2 -52
  142. package/dist/configWriter.d.mts +2 -52
  143. package/dist/configWriter.mjs +1 -2
  144. package/dist/container/doomConfigContainer.cjs +2 -0
  145. package/dist/container/doomConfigContainer.cjs.map +1 -0
  146. package/dist/container/doomConfigContainer.d.cts +12 -0
  147. package/dist/container/doomConfigContainer.d.cts.map +1 -0
  148. package/dist/container/doomConfigContainer.d.mts +12 -0
  149. package/dist/container/doomConfigContainer.d.mts.map +1 -0
  150. package/dist/container/doomConfigContainer.mjs +2 -0
  151. package/dist/container/doomConfigContainer.mjs.map +1 -0
  152. package/dist/container.cjs +1 -2
  153. package/dist/container.d.cts +2 -12
  154. package/dist/container.d.mts +2 -12
  155. package/dist/container.mjs +1 -2
  156. package/dist/domains.cjs +1 -2
  157. package/dist/domains.d.cts +6 -77
  158. package/dist/domains.d.mts +6 -77
  159. package/dist/domains.mjs +1 -2
  160. package/dist/extensions/doom.cjs +1 -2
  161. package/dist/extensions/doom.d.cts +2 -8
  162. package/dist/extensions/doom.d.mts +2 -8
  163. package/dist/extensions/doom.mjs +1 -2
  164. package/dist/extensions/pi.cjs +1 -2
  165. package/dist/extensions/pi.d.cts +2 -8
  166. package/dist/extensions/pi.d.mts +2 -8
  167. package/dist/extensions/pi.mjs +1 -2
  168. package/dist/harnessState.cjs +1 -2
  169. package/dist/harnessState.d.cts +2 -50
  170. package/dist/harnessState.d.mts +2 -50
  171. package/dist/harnessState.mjs +1 -2
  172. package/dist/harnessStore.cjs +1 -2
  173. package/dist/harnessStore.d.cts +2 -102
  174. package/dist/harnessStore.d.mts +2 -102
  175. package/dist/harnessStore.mjs +1 -2
  176. package/dist/index.cjs +1 -1
  177. package/dist/index.d.cts +20 -16
  178. package/dist/index.d.mts +20 -16
  179. package/dist/index.mjs +1 -1
  180. package/dist/init.cjs +1 -76
  181. package/dist/init.d.cts +2 -33
  182. package/dist/init.d.mts +2 -33
  183. package/dist/init.mjs +1 -76
  184. package/dist/layeredConfig.cjs +1 -2
  185. package/dist/layeredConfig.d.cts +2 -49
  186. package/dist/layeredConfig.d.mts +2 -49
  187. package/dist/layeredConfig.mjs +1 -2
  188. package/dist/majorModes.cjs +1 -2
  189. package/dist/majorModes.d.cts +2 -75
  190. package/dist/majorModes.d.mts +2 -75
  191. package/dist/majorModes.mjs +1 -2
  192. package/dist/pi.cjs +1 -1
  193. package/dist/pi.d.cts +1 -1
  194. package/dist/pi.d.mts +1 -1
  195. package/dist/pi.mjs +1 -1
  196. package/dist/piContext.cjs +1 -2
  197. package/dist/piContext.d.cts +2 -19
  198. package/dist/piContext.d.mts +2 -19
  199. package/dist/piContext.mjs +1 -2
  200. package/dist/profiles.cjs +1 -4
  201. package/dist/profiles.d.cts +2 -35
  202. package/dist/profiles.d.mts +2 -35
  203. package/dist/profiles.mjs +1 -4
  204. package/dist/schemas/config/schema.d.cts +33 -0
  205. package/dist/schemas/config/schema.d.cts.map +1 -0
  206. package/dist/schemas/config/schema.d.mts +33 -0
  207. package/dist/schemas/config/schema.d.mts.map +1 -0
  208. package/dist/services/configPolicy.cjs +2 -0
  209. package/dist/services/configPolicy.cjs.map +1 -0
  210. package/dist/services/configPolicy.d.cts +19 -0
  211. package/dist/services/configPolicy.d.cts.map +1 -0
  212. package/dist/services/configPolicy.d.mts +19 -0
  213. package/dist/services/configPolicy.d.mts.map +1 -0
  214. package/dist/services/configPolicy.mjs +2 -0
  215. package/dist/services/configPolicy.mjs.map +1 -0
  216. package/dist/services/domains.cjs +2 -0
  217. package/dist/services/domains.cjs.map +1 -0
  218. package/dist/services/domains.d.cts +12 -0
  219. package/dist/services/domains.d.cts.map +1 -0
  220. package/dist/services/domains.d.mts +12 -0
  221. package/dist/services/domains.d.mts.map +1 -0
  222. package/dist/services/domains.mjs +2 -0
  223. package/dist/services/domains.mjs.map +1 -0
  224. package/dist/types/config.cjs +2 -0
  225. package/dist/types/config.cjs.map +1 -0
  226. package/dist/types/config.d.cts +136 -0
  227. package/dist/types/config.d.cts.map +1 -0
  228. package/dist/types/config.d.mts +136 -0
  229. package/dist/types/config.d.mts.map +1 -0
  230. package/dist/types/config.mjs +2 -0
  231. package/dist/types/config.mjs.map +1 -0
  232. package/dist/types/domains.d.cts +89 -0
  233. package/dist/types/domains.d.cts.map +1 -0
  234. package/dist/types/domains.d.mts +89 -0
  235. package/dist/types/domains.d.mts.map +1 -0
  236. package/dist/types.cjs +1 -2
  237. package/dist/types.d.cts +2 -132
  238. package/dist/types.d.mts +2 -132
  239. package/dist/types.mjs +1 -2
  240. package/llms.txt +9 -0
  241. package/package.json +18 -6
  242. package/skills/doom-pi-config/SKILL.md +6 -3
  243. package/skills/doom-pi-config/references/config-contract.md +123 -8
  244. package/dist/atomicJson.cjs.map +0 -1
  245. package/dist/atomicJson.d.cts.map +0 -1
  246. package/dist/atomicJson.d.mts.map +0 -1
  247. package/dist/atomicJson.mjs.map +0 -1
  248. package/dist/config/piConfig.cjs.map +0 -1
  249. package/dist/config/piConfig.d.cts.map +0 -1
  250. package/dist/config/piConfig.d.mts.map +0 -1
  251. package/dist/config/piConfig.mjs.map +0 -1
  252. package/dist/config/schema.d.cts.map +0 -1
  253. package/dist/config/schema.d.mts.map +0 -1
  254. package/dist/config.cjs.map +0 -1
  255. package/dist/config.d.cts.map +0 -1
  256. package/dist/config.d.mts.map +0 -1
  257. package/dist/config.mjs.map +0 -1
  258. package/dist/configWriter.cjs.map +0 -1
  259. package/dist/configWriter.d.cts.map +0 -1
  260. package/dist/configWriter.d.mts.map +0 -1
  261. package/dist/configWriter.mjs.map +0 -1
  262. package/dist/container.cjs.map +0 -1
  263. package/dist/container.d.cts.map +0 -1
  264. package/dist/container.d.mts.map +0 -1
  265. package/dist/container.mjs.map +0 -1
  266. package/dist/domains.cjs.map +0 -1
  267. package/dist/domains.d.cts.map +0 -1
  268. package/dist/domains.d.mts.map +0 -1
  269. package/dist/domains.mjs.map +0 -1
  270. package/dist/extensions/doom.cjs.map +0 -1
  271. package/dist/extensions/doom.d.cts.map +0 -1
  272. package/dist/extensions/doom.d.mts.map +0 -1
  273. package/dist/extensions/doom.mjs.map +0 -1
  274. package/dist/extensions/pi.cjs.map +0 -1
  275. package/dist/extensions/pi.d.cts.map +0 -1
  276. package/dist/extensions/pi.d.mts.map +0 -1
  277. package/dist/extensions/pi.mjs.map +0 -1
  278. package/dist/harnessState.cjs.map +0 -1
  279. package/dist/harnessState.d.cts.map +0 -1
  280. package/dist/harnessState.d.mts.map +0 -1
  281. package/dist/harnessState.mjs.map +0 -1
  282. package/dist/harnessStore.cjs.map +0 -1
  283. package/dist/harnessStore.d.cts.map +0 -1
  284. package/dist/harnessStore.d.mts.map +0 -1
  285. package/dist/harnessStore.mjs.map +0 -1
  286. package/dist/init.cjs.map +0 -1
  287. package/dist/init.d.cts.map +0 -1
  288. package/dist/init.d.mts.map +0 -1
  289. package/dist/init.mjs.map +0 -1
  290. package/dist/layeredConfig.cjs.map +0 -1
  291. package/dist/layeredConfig.d.cts.map +0 -1
  292. package/dist/layeredConfig.d.mts.map +0 -1
  293. package/dist/layeredConfig.mjs.map +0 -1
  294. package/dist/majorModes.cjs.map +0 -1
  295. package/dist/majorModes.d.cts.map +0 -1
  296. package/dist/majorModes.d.mts.map +0 -1
  297. package/dist/majorModes.mjs.map +0 -1
  298. package/dist/piContext.cjs.map +0 -1
  299. package/dist/piContext.d.cts.map +0 -1
  300. package/dist/piContext.d.mts.map +0 -1
  301. package/dist/piContext.mjs.map +0 -1
  302. package/dist/profiles.cjs.map +0 -1
  303. package/dist/profiles.d.cts.map +0 -1
  304. package/dist/profiles.d.mts.map +0 -1
  305. package/dist/profiles.mjs.map +0 -1
  306. package/dist/types.cjs.map +0 -1
  307. package/dist/types.d.cts.map +0 -1
  308. package/dist/types.d.mts.map +0 -1
  309. package/dist/types.mjs.map +0 -1
package/README.md CHANGED
@@ -1,13 +1,19 @@
1
1
  # @agimon-ai/doompi-config
2
2
 
3
- Shared Doom configuration loading and dependency injection.
3
+ **The one place Doompi decides what a session means.**
4
4
 
5
- Loads and validates `.doom/`: major modes and their layers, domains, profiles, and the hook
6
- registry. Owns the resolved harness state file that every other reader derives from.
5
+ Four YAML files are enough places to look: `config.yaml`, `modes.yaml`, `domains.yaml`, and
6
+ `profiles.yaml`. The rest of Doompi should not grow a second opinion about them. This package
7
+ loads and validates `.doom/`, resolves the user and repository layers, and writes the harness
8
+ state every other package reads.
7
9
 
8
- Part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi), a coding agent that loads
9
- only the skills and tools you name. This package is composed by Doompi and is not usually
10
- installed on its own.
10
+ It owns runtime settings, major modes and their extension layers, domains and their plugins,
11
+ profiles, hook-group references inside modes, and the `doompi init` defaults. A bad declaration
12
+ fails here instead of becoming a stranger error halfway through startup.
13
+
14
+ This is part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi). Most users get
15
+ it with the distribution; install it directly when building against Doompi's config
16
+ contract.
11
17
 
12
18
  ## Install
13
19
 
@@ -17,19 +23,138 @@ npm install @agimon-ai/doompi-config
17
23
 
18
24
  ## How it loads
19
25
 
20
- Doompi loads this unconditionally. It is core, not a layer, so it does not appear in
21
- `.doom/modes.yaml`: making it optional would only create broken configurations.
26
+ Doompi loads this package as core. It is not a layer and does not belong in
27
+ `.doom/modes.yaml`; making the config reader optional would only create configs nobody can
28
+ read.
29
+
30
+ The bare package name follows its Pi manifest and selects the Doom adapter. The explicit
31
+ `/extensions/pi` and `/extensions/doom` subpaths remain available when choosing an adapter
32
+ by hand.
33
+
34
+ ## What it owns
35
+
36
+ - Loading user config from `~/.pi/.doom/` and repository config from `.doom/`.
37
+ - Discovering Codex-compatible marketplaces, plugin roots, and profile roots.
38
+ - Validating `config.yaml`, modes, domains, profiles, and package-owned runtime settings.
39
+ - Resolving repository overrides without losing the declaration root for relative paths.
40
+ - Writing the resolved harness state used by child sessions and other extensions.
41
+ - Seeding a working config through `doompi init`.
42
+
43
+ ## Domain plugin catalogs
44
+
45
+ `~/.pi/.doom/domains.yaml` is loaded first and `<repo>/.doom/domains.yaml` is layered over it.
46
+ Plugin roots from both files are retained and resolved relative to the file that declared them;
47
+ plugin entries, domains, and aliases merge by name, with the repository definition winning a
48
+ collision.
49
+
50
+ ```yaml
51
+ plugins:
52
+ roots: [plugins, ../shared-plugins]
53
+ entries:
54
+ git-plugin:
55
+ source: url
56
+ url: https://github.com/acme/plugins.git
57
+ path: plugins/reviewer
58
+ ref: main
59
+ npm-plugin:
60
+ source: npm
61
+ package: '@acme/doompi-plugin'
62
+ version: 1.2.3
63
+
64
+ domains:
65
+ default:
66
+ description: Default repository tools.
67
+ plugins: [local-plugin, git-plugin, npm-plugin]
68
+ ```
69
+
70
+ Each root can be a marketplace, a single plugin, or a container whose direct-child folders
71
+ are plugins. Container discovery is intentionally nonrecursive. Plugins use the manifest
72
+ `name` when present and otherwise the directory name. DoomPi also inspects the standard
73
+ personal and repository Codex marketplace files, but never Codex's private cache. Marketplace
74
+ plugins are referenced as `plugin@marketplace`. Invalid auto-discoveries are diagnostics;
75
+ invalid explicit entries or unknown domain references fail configuration loading.
76
+
77
+ ## Profile catalogs
78
+
79
+ `profiles.yaml` uses the same roots-and-entries pattern for personas. A root can be one
80
+ profile or a container whose direct-child directories are profiles. A directory qualifies
81
+ when it contains `profile.md`, `SOUL.md`, or `AGENTS.md`; the directory name becomes the
82
+ profile name, its environment defaults are empty, and discovery never recurses.
83
+
84
+ ```yaml
85
+ profiles:
86
+ roots: [agents/acme, ../shared-personas]
87
+ entries:
88
+ writer:
89
+ persona: agents/special/writer
90
+ env:
91
+ TONE: concise
92
+ ```
93
+
94
+ Home roots are inspected before repository roots, and every relative root stays relative to
95
+ the file that declared it. Later discovered names replace earlier ones. Explicit entries
96
+ override automatic discoveries, and a repository entry replaces a same-named home entry.
97
+ An explicit persona may live under `agents/` or under a root declared in the same file.
98
+ DoomPi concatenates `profile.md`, `SOUL.md`, and `AGENTS.md` in that order. Sync freshness
99
+ includes discovered names and persona file content. The legacy flat `profiles: {name: ...}`
100
+ mapping remains accepted for migration.
101
+
102
+ ## Layer extensions and package-owned config
103
+
104
+ A layer can mix DoomPi built-ins, repository-local Pi extension scripts, and packages.
105
+ Bare values in `extensions` name DoomPi's own entries. Path-like values use Pi's local
106
+ extension behavior and resolve relative to the config that declares them:
107
+
108
+ ```yaml
109
+ layers:
110
+ development:
111
+ extensions:
112
+ - repositoryHooks
113
+ - ./extensions/review.ts
114
+ - ./extensions/local-tools
115
+ packages:
116
+ - '@agimon-ai/vibe-lint'
117
+ ```
118
+
119
+ Local extension paths may name a TypeScript or JavaScript module, or a directory containing
120
+ supported script files and child `index` modules. Use `./` or `../` for a relative path so
121
+ it cannot be confused with a built-in name. Repository paths resolve from the repository
122
+ root. Paths in `~/.pi/.doom/modes.yaml` resolve from `~/.pi/.doom`.
123
+
124
+ A layer orders extensions and packages; it does not own package settings. Put configuration
125
+ on the package entry that consumes it:
126
+
127
+ ```yaml
128
+ layers:
129
+ team:
130
+ packages:
131
+ - name: '@agimon-ai/doompi-team'
132
+ config:
133
+ models:
134
+ - model: provider/model-id
135
+ thinking: high
136
+ ```
22
137
 
23
- Extension entry: `@agimon-ai/doompi-config/pi`
138
+ DoomPi Config validates the package envelope and preserves `config` as an opaque mapping.
139
+ The selected package validates its own fields. Bare package strings, optional package
140
+ objects, and explicit adapter subpaths keep their existing resolution and ordering
141
+ behavior. Local paths under `packages` remain accepted for compatibility with unpublished
142
+ Pi packages. The removed sibling `layer.config` form is rejected rather than silently
143
+ assigned to whichever package happens to read it.
24
144
 
25
- ## Notes
145
+ ## Package guidance
26
146
 
27
- This is the package that defines the config contract. Changing the shape of `.doom/*.yaml`
28
- starts here.
147
+ The package ships a root `llms.txt` index and contributes the unified
148
+ `doompi-config-help` skill while the parent-only Help mode is active. The index links to
149
+ this README and the bundled configuration contract instead of duplicating them.
29
150
 
30
- ## Entry points
151
+ ## Public API
31
152
 
32
- 18 subpaths are declared in the `exports` field of `package.json`.
153
+ The root export contains the common loaders and types. Focused imports such as `/config`,
154
+ `/domains`, `/majorModes`, `/profiles`, `/harnessState`, and `/container` keep consumers
155
+ out of internal files. `resolvePackageConfigurations` returns one package's selected
156
+ configuration entries in layer and package order. `package.json` is the source of truth
157
+ for the complete export list.
33
158
 
34
159
  ## License
35
160
 
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:crypto"),n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);function i(e,i){n.default.mkdirSync(r.default.dirname(e),{mode:448,recursive:!0});let a=`${e}.${process.pid}.${(0,t.randomUUID)()}.tmp`;try{n.default.writeFileSync(a,`${JSON.stringify(i,null,2)}\n`,{mode:384}),n.default.renameSync(a,e)}catch(e){throw n.default.rmSync(a,{force:!0}),e}}exports.writePrivateAtomicJson=i;
2
+ //# sourceMappingURL=atomicJson.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomicJson.cjs","names":["path","randomUUID"],"sources":["../../src/adapters/atomicJson.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * Private atomic JSON writes.\n *\n * Write to a uniquely named temporary file in the destination directory, then\n * rename onto the target. Rename is atomic within a filesystem, so a reader\n * sees either the previous file or the complete new one and never a partial\n * write. The temporary name carries the pid and a random suffix, so two\n * processes writing the same target cannot collide on it.\n *\n * This prevents torn reads. It does NOT prevent lost updates: two writers doing\n * read-modify-write still clobber each other, because the rename only publishes\n * what the caller already computed. Harness state avoids that by giving every\n * process a file it owns rather than by locking.\n *\n * AVOID:\n * - Treating this as a lock\n * - Leaving the temporary file behind when the write fails\n */\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst TEMPORARY_SUFFIX = '.tmp';\n\n/** Writes JSON readable by its owner only, replacing the target atomically. */\nexport function writePrivateAtomicJson(filePath: string, value: unknown): void {\n fs.mkdirSync(path.dirname(filePath), { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}${TEMPORARY_SUFFIX}`;\n try {\n fs.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\\n`, { mode: PRIVATE_FILE_MODE });\n fs.renameSync(temporaryPath, filePath);\n } catch (error) {\n fs.rmSync(temporaryPath, { force: true });\n throw error;\n }\n}\n"],"mappings":"oKA4BA,SAAgB,EAAuB,EAAkB,EAAsB,CAC7E,EAAA,QAAG,UAAUA,EAAAA,QAAK,QAAQ,CAAQ,EAAG,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EACtF,IAAM,EAAgB,GAAG,EAAS,GAAG,QAAQ,IAAI,IAAA,EAAGC,EAAAA,WAAAA,CAAW,QAC/D,GAAI,CACF,EAAA,QAAG,cAAc,EAAe,GAAG,KAAK,UAAU,EAAO,KAAM,CAAC,EAAE,IAAK,CAAE,KAAM,GAAkB,CAAC,EAClG,EAAA,QAAG,WAAW,EAAe,CAAQ,CACvC,OAAS,EAAO,CAEd,MADA,EAAA,QAAG,OAAO,EAAe,CAAE,MAAO,EAAK,CAAC,EAClC,CACR,CACF"}
@@ -0,0 +1,6 @@
1
+ //#region src/adapters/atomicJson.d.ts
2
+ /** Writes JSON readable by its owner only, replacing the target atomically. */
3
+ declare function writePrivateAtomicJson(filePath: string, value: unknown): void;
4
+ //#endregion
5
+ export { writePrivateAtomicJson };
6
+ //# sourceMappingURL=atomicJson.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomicJson.d.cts","names":[],"sources":["../../src/adapters/atomicJson.ts"],"mappings":";;iBA4BgB,uBAAuB,kBAAkB"}
@@ -0,0 +1,6 @@
1
+ //#region src/adapters/atomicJson.d.ts
2
+ /** Writes JSON readable by its owner only, replacing the target atomically. */
3
+ declare function writePrivateAtomicJson(filePath: string, value: unknown): void;
4
+ //#endregion
5
+ export { writePrivateAtomicJson };
6
+ //# sourceMappingURL=atomicJson.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomicJson.d.mts","names":[],"sources":["../../src/adapters/atomicJson.ts"],"mappings":";;iBA4BgB,uBAAuB,kBAAkB"}
@@ -0,0 +1,2 @@
1
+ import{randomUUID as e}from"node:crypto";import t from"node:fs";import n from"node:path";function r(r,i){t.mkdirSync(n.dirname(r),{mode:448,recursive:!0});let a=`${r}.${process.pid}.${e()}.tmp`;try{t.writeFileSync(a,`${JSON.stringify(i,null,2)}\n`,{mode:384}),t.renameSync(a,r)}catch(e){throw t.rmSync(a,{force:!0}),e}}export{r as writePrivateAtomicJson};
2
+ //# sourceMappingURL=atomicJson.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomicJson.mjs","names":[],"sources":["../../src/adapters/atomicJson.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * Private atomic JSON writes.\n *\n * Write to a uniquely named temporary file in the destination directory, then\n * rename onto the target. Rename is atomic within a filesystem, so a reader\n * sees either the previous file or the complete new one and never a partial\n * write. The temporary name carries the pid and a random suffix, so two\n * processes writing the same target cannot collide on it.\n *\n * This prevents torn reads. It does NOT prevent lost updates: two writers doing\n * read-modify-write still clobber each other, because the rename only publishes\n * what the caller already computed. Harness state avoids that by giving every\n * process a file it owns rather than by locking.\n *\n * AVOID:\n * - Treating this as a lock\n * - Leaving the temporary file behind when the write fails\n */\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst TEMPORARY_SUFFIX = '.tmp';\n\n/** Writes JSON readable by its owner only, replacing the target atomically. */\nexport function writePrivateAtomicJson(filePath: string, value: unknown): void {\n fs.mkdirSync(path.dirname(filePath), { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}${TEMPORARY_SUFFIX}`;\n try {\n fs.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\\n`, { mode: PRIVATE_FILE_MODE });\n fs.renameSync(temporaryPath, filePath);\n } catch (error) {\n fs.rmSync(temporaryPath, { force: true });\n throw error;\n }\n}\n"],"mappings":"yFA4BA,SAAgB,EAAuB,EAAkB,EAAsB,CAC7E,EAAG,UAAU,EAAK,QAAQ,CAAQ,EAAG,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EACtF,IAAM,EAAgB,GAAG,EAAS,GAAG,QAAQ,IAAI,GAAG,EAAW,QAC/D,GAAI,CACF,EAAG,cAAc,EAAe,GAAG,KAAK,UAAU,EAAO,KAAM,CAAC,EAAE,IAAK,CAAE,KAAM,GAAkB,CAAC,EAClG,EAAG,WAAW,EAAe,CAAQ,CACvC,OAAS,EAAO,CAEd,MADA,EAAG,OAAO,EAAe,CAAE,MAAO,EAAK,CAAC,EAClC,CACR,CACF"}
@@ -0,0 +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;
2
+ //# sourceMappingURL=config.cjs.map
@@ -0,0 +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"}
@@ -0,0 +1,11 @@
1
+ import { DoomConfig } from "../types/config.cjs";
2
+ //#region src/adapters/config.d.ts
3
+ declare function globalDoomConfigDirectory(homeDirectory?: string): string;
4
+ declare function globalDoomConfigPath(homeDirectory?: string): string;
5
+ declare function resolvePlanningPlansDirectory(configured: string | undefined, repoRoot: string, homeDirectory?: string): string;
6
+ declare function repositoryDoomConfigPath(repoRoot: string): string;
7
+ declare function loadDoomConfig(repoRoot: string, homeDirectory?: string): DoomConfig;
8
+ declare function loadDoomConfigAsync(repoRoot: string, homeDirectory?: string): Promise<DoomConfig>;
9
+ //#endregion
10
+ export { globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, loadDoomConfigAsync, repositoryDoomConfigPath, resolvePlanningPlansDirectory };
11
+ //# sourceMappingURL=config.d.cts.map
@@ -0,0 +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"}
@@ -0,0 +1,11 @@
1
+ import { DoomConfig } from "../types/config.mjs";
2
+ //#region src/adapters/config.d.ts
3
+ declare function globalDoomConfigDirectory(homeDirectory?: string): string;
4
+ declare function globalDoomConfigPath(homeDirectory?: string): string;
5
+ declare function resolvePlanningPlansDirectory(configured: string | undefined, repoRoot: string, homeDirectory?: string): string;
6
+ declare function repositoryDoomConfigPath(repoRoot: string): string;
7
+ declare function loadDoomConfig(repoRoot: string, homeDirectory?: string): DoomConfig;
8
+ declare function loadDoomConfigAsync(repoRoot: string, homeDirectory?: string): Promise<DoomConfig>;
9
+ //#endregion
10
+ export { globalDoomConfigDirectory, globalDoomConfigPath, loadDoomConfig, loadDoomConfigAsync, repositoryDoomConfigPath, resolvePlanningPlansDirectory };
11
+ //# sourceMappingURL=config.d.mts.map
@@ -0,0 +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"}
@@ -0,0 +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};
2
+ //# sourceMappingURL=config.mjs.map
@@ -0,0 +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"}
@@ -0,0 +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;
2
+ //# sourceMappingURL=configWriter.cjs.map
@@ -0,0 +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 /** The value to write. `undefined` removes the key. */\n readonly value?: 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":"yOAkDA,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"}
@@ -0,0 +1,52 @@
1
+ //#region src/adapters/configWriter.d.ts
2
+ /**
3
+ * Writes into the global Doom config file, one key path at a time.
4
+ *
5
+ * Extracted from doom-file-edit, which owned the only writer in the repo and had
6
+ * it hard-coded to `editor.command`. Several extensions now edit their own
7
+ * settings from the config panel, and every one of them needs the same four
8
+ * properties: keep the user's comments, never publish a file the parser would
9
+ * reject, never leave a half-written file behind, and apply a set of related
10
+ * edits together.
11
+ *
12
+ * That last one is not a convenience. `voice.adapters.<engine>.model` accepts a
13
+ * path or an id but not both, so switching between them is a set and an unset
14
+ * that must land in the same write, or the file is briefly invalid.
15
+ *
16
+ * Atomicity is the same rename trick as `atomicJson.ts` next door, and carries
17
+ * the same caveat: it prevents torn reads, not lost updates. Two sessions with
18
+ * the panel open still clobber each other, because the rename only publishes
19
+ * what the caller already computed.
20
+ *
21
+ * AVOID:
22
+ * - Treating this as a lock
23
+ * - Writing an empty string; the parser rejects blank values, so clearing a key
24
+ * means removing it
25
+ */
26
+ interface DoomConfigEdit {
27
+ /** Key path into the document, e.g. `['voice', 'adapters', 'mlx-whisper', 'model', 'id']`. */
28
+ readonly keyPath: readonly string[];
29
+ /** The value to write. `undefined` removes the key. */
30
+ readonly value?: string;
31
+ }
32
+ interface WriteDoomConfigOptions {
33
+ /**
34
+ * Remove ancestors left holding nothing. Defaults to true, and is not
35
+ * cosmetic: an adapter left as `{}` fails validation, because the parser
36
+ * requires a model once the adapter exists at all.
37
+ */
38
+ readonly prune?: boolean;
39
+ }
40
+ /**
41
+ * Applies every edit as one document mutation, or throws having written nothing.
42
+ *
43
+ * Validation runs against the serialized result rather than each edit, so a pair
44
+ * of edits only valid together is accepted, and a single edit that breaks an
45
+ * invariant elsewhere is still caught.
46
+ */
47
+ declare function writeDoomConfigValues(filePath: string, edits: readonly DoomConfigEdit[], options?: WriteDoomConfigOptions): Promise<void>;
48
+ declare function setDoomConfigValue(filePath: string, keyPath: readonly string[], value: string): Promise<void>;
49
+ declare function unsetDoomConfigValue(filePath: string, keyPath: readonly string[]): Promise<void>;
50
+ //#endregion
51
+ export { DoomConfigEdit, WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues };
52
+ //# sourceMappingURL=configWriter.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configWriter.d.cts","names":[],"sources":["../../src/adapters/configWriter.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;UAkCiB;;WAEN;;WAEA;;UAGM;;;;;;WAMN;;;;;;;;;iBAmCW,sBACpB,kBACA,gBAAgB,kBAChB,UAAS,yBACR;iBAoCmB,mBAAmB,kBAAkB,4BAA4B,gBAAgB;iBAIjF,qBAAqB,kBAAkB,6BAA6B"}
@@ -0,0 +1,52 @@
1
+ //#region src/adapters/configWriter.d.ts
2
+ /**
3
+ * Writes into the global Doom config file, one key path at a time.
4
+ *
5
+ * Extracted from doom-file-edit, which owned the only writer in the repo and had
6
+ * it hard-coded to `editor.command`. Several extensions now edit their own
7
+ * settings from the config panel, and every one of them needs the same four
8
+ * properties: keep the user's comments, never publish a file the parser would
9
+ * reject, never leave a half-written file behind, and apply a set of related
10
+ * edits together.
11
+ *
12
+ * That last one is not a convenience. `voice.adapters.<engine>.model` accepts a
13
+ * path or an id but not both, so switching between them is a set and an unset
14
+ * that must land in the same write, or the file is briefly invalid.
15
+ *
16
+ * Atomicity is the same rename trick as `atomicJson.ts` next door, and carries
17
+ * the same caveat: it prevents torn reads, not lost updates. Two sessions with
18
+ * the panel open still clobber each other, because the rename only publishes
19
+ * what the caller already computed.
20
+ *
21
+ * AVOID:
22
+ * - Treating this as a lock
23
+ * - Writing an empty string; the parser rejects blank values, so clearing a key
24
+ * means removing it
25
+ */
26
+ interface DoomConfigEdit {
27
+ /** Key path into the document, e.g. `['voice', 'adapters', 'mlx-whisper', 'model', 'id']`. */
28
+ readonly keyPath: readonly string[];
29
+ /** The value to write. `undefined` removes the key. */
30
+ readonly value?: string;
31
+ }
32
+ interface WriteDoomConfigOptions {
33
+ /**
34
+ * Remove ancestors left holding nothing. Defaults to true, and is not
35
+ * cosmetic: an adapter left as `{}` fails validation, because the parser
36
+ * requires a model once the adapter exists at all.
37
+ */
38
+ readonly prune?: boolean;
39
+ }
40
+ /**
41
+ * Applies every edit as one document mutation, or throws having written nothing.
42
+ *
43
+ * Validation runs against the serialized result rather than each edit, so a pair
44
+ * of edits only valid together is accepted, and a single edit that breaks an
45
+ * invariant elsewhere is still caught.
46
+ */
47
+ declare function writeDoomConfigValues(filePath: string, edits: readonly DoomConfigEdit[], options?: WriteDoomConfigOptions): Promise<void>;
48
+ declare function setDoomConfigValue(filePath: string, keyPath: readonly string[], value: string): Promise<void>;
49
+ declare function unsetDoomConfigValue(filePath: string, keyPath: readonly string[]): Promise<void>;
50
+ //#endregion
51
+ export { DoomConfigEdit, WriteDoomConfigOptions, setDoomConfigValue, unsetDoomConfigValue, writeDoomConfigValues };
52
+ //# sourceMappingURL=configWriter.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configWriter.d.mts","names":[],"sources":["../../src/adapters/configWriter.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;UAkCiB;;WAEN;;WAEA;;UAGM;;;;;;WAMN;;;;;;;;;iBAmCW,sBACpB,kBACA,gBAAgB,kBAChB,UAAS,yBACR;iBAoCmB,mBAAmB,kBAAkB,4BAA4B,gBAAgB;iBAIjF,qBAAqB,kBAAkB,6BAA6B"}
@@ -0,0 +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};
2
+ //# sourceMappingURL=configWriter.mjs.map
@@ -0,0 +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 /** The value to write. `undefined` removes the key. */\n readonly value?: 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":"sMAkDA,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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./config.cjs"),n=require("../services/domains.cjs"),r=require("./layeredConfig.cjs"),i=require("./pluginCatalog.cjs");let a=require("node:fs");a=e.__toESM(a,1);let o=require("node:path");o=e.__toESM(o,1);let s=require("node:os");s=e.__toESM(s,1);const c=`Plugin directory does not exist`,l=`.doom/domains.yaml`;function u(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function d(e,t){if(!u(e))throw Error(`${t} must be an object`);return e}function f(e,t,n){let r=Object.keys(e).filter(e=>!t.includes(e));if(r.length>0)throw Error(`${n} contains unsupported field "${r[0]}"`)}function p(e,t){if(typeof e!=`string`||!e.trim())throw Error(`${t} must be a non-empty string`);return e.trim()}function m(e,t){if(e!==void 0)return p(e,t)}function h(e,t){if(!Array.isArray(e))throw Error(`${t} must be an array of non-empty strings`);return[...new Set(e.map((e,n)=>p(e,`${t}[${n}]`)))]}function g(e,t){if(e!==void 0){if(typeof e!=`boolean`)throw Error(`${t} must be a boolean`);return e}}function _(e){return h(e,`defaultDomains in ${l}`)}function v(e,t){if(typeof e==`string`)return p(e,t);let n=d(e,t);f(n,[`name`,`skills`,`agents`,`hooks`,`mcp`],t);let r=n.skills===void 0?void 0:h(n.skills,`${t}.skills`),i=n.agents===void 0?void 0:h(n.agents,`${t}.agents`),a=g(n.hooks,`${t}.hooks`),o=g(n.mcp,`${t}.mcp`);return{name:p(n.name,`${t}.name`),...r?{skills:r}:{},...i?{agents:i}:{},...a===void 0?{}:{hooks:a},...o===void 0?{}:{mcp:o}}}function y(e,t){let n=d(e,t);return f(n,[`servers`,`proxy`],t),{...n.servers===void 0?{}:{servers:h(n.servers,`${t}.servers`)},...n.proxy===void 0?{}:{proxy:h(n.proxy,`${t}.proxy`)}}}function b(e,t){let n=d(e,t);f(n,[`description`,`plugins`,`mcp`,`sharedSkills`],t);let r=m(n.description,`${t}.description`);if(n.plugins!==void 0&&!Array.isArray(n.plugins))throw Error(`${t}.plugins must be an array`);let i=g(n.sharedSkills,`${t}.sharedSkills`);return{...r?{description:r}:{},...Array.isArray(n.plugins)?{plugins:n.plugins.map((e,n)=>v(e,`${t}.plugins[${n}]`))}:{},...n.mcp===void 0?{}:{mcp:y(n.mcp,`${t}.mcp`)},...i===void 0?{}:{sharedSkills:i}}}function x(e,t){if(e===void 0)return{};let n=d(e,t);return Object.fromEntries(Object.entries(n).map(([e,n])=>[e,h(n,`${t}.${e}`)]))}function S(e,t=s.default.homedir()){let a=r.readDoomConfigSources(`domains.yaml`,e,t),o={},c={},u;for(let e of a){let t=d(e.document,e.filePath);if(f(t,[`plugins`,`domains`,`aliases`,`defaultDomains`],e.filePath),Object.assign(o,x(t.aliases,`aliases in ${e.filePath}`)),t.domains!==void 0){let n=d(t.domains,`domains in ${e.filePath}`);for(let[t,r]of Object.entries(n))c[t]={...b(r,`domains.${t} in ${e.filePath}`),baseDirectory:e.baseDirectory}}Object.hasOwn(t,`defaultDomains`)&&(u=_(t.defaultDomains))}let p=i.loadPluginCatalog(a.map(e=>({filePath:e.filePath,baseDirectory:e.baseDirectory,value:d(e.document,e.filePath).plugins})),e,t);if(u){for(let e of n.expandDomainNames({plugins:p,domains:c,aliases:o},u))if(!Object.hasOwn(c,e))throw Error(`Unknown default domain "${e}" in ${l}`)}return{plugins:p,domains:c,aliases:o,...u?{defaultDomains:u}:{}}}function C(e,t){let n=S(e,t);return[...new Set([...Object.keys(n.domains),...Object.keys(n.aliases)])].sort()}function w(e,t,r){let i=S(e,r),a=n.expandDomainNames(i,t);return a.length===0||a.some(e=>i.domains[e]?.sharedSkills!==!1)}function T(e,r,l,u=s.default.homedir()){let d=S(e,u),f=[],p=new Set,m=t.globalDoomConfigDirectory(u);for(let e of n.expandDomainNames(d,r)){let t=d.domains[e];if(!t)throw Error(`Unknown domain: ${e}`);for(let n of t.plugins??[]){let t=typeof n!=`string`,r=t?n.name:n,o=d.plugins.entries[r];if(!o)throw Error(`Unknown plugin "${r}" referenced by domain "${e}"`);let s=i.pluginDirectoryForSource(o.source,m);if(o.source.type===`local`&&(!a.default.existsSync(s)||!a.default.statSync(s).isDirectory()))throw Error(`${c}: ${s}`);p.has(s)||(p.add(s),f.push({name:r,directory:s,source:o.source,...o.manifest?.agentPluginSchema===`https://agent-plugins.org/schemas/1.0.0/plugin.schema.json`?{skillDiscovery:`direct-children`}:{},...t&&n.skills?{skills:n.skills}:{},...t&&n.agents?{agents:n.agents}:{},...t&&n.hooks!==void 0?{hooks:n.hooks}:{},...t&&n.mcp!==void 0?{mcp:n.mcp}:{}}))}}for(let e of l){let t=o.default.resolve(e);if(!a.default.existsSync(t)||!a.default.statSync(t).isDirectory())throw Error(`${c}: ${t}`);p.has(t)||(p.add(t),f.push({name:o.default.basename(t),directory:t,source:{type:`local`,path:t}}))}return f}function E(e,t,n,r){return T(e,t,n,r).map(e=>e.directory)}exports.DOOM_DIR=r.DOOM_DIR,exports.listDomainNames=C,exports.loadDomains=S,exports.resolvePluginDirectories=E,exports.resolvePluginEntries=T,exports.resolveSharedSkills=w;
2
+ //# sourceMappingURL=domains.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domains.cjs","names":["os","readDoomConfigSources","loadPluginCatalog","expandDomainNames","globalDoomConfigDirectory","pluginDirectoryForSource","fs","path"],"sources":["../../src/adapters/domains.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { expandDomainNames } from '../services/domains.ts';\nimport type {\n DomainDefinition,\n DomainManifest,\n DomainMcpAllowlist,\n DomainPlugin,\n PluginEntry,\n ResolvedDomain,\n} from '../types/domains.ts';\nimport { globalDoomConfigDirectory } from './config.ts';\nimport { readDoomConfigSources } from './layeredConfig.ts';\nimport { loadPluginCatalog, pluginDirectoryForSource } from './pluginCatalog.ts';\n\nexport { DOOM_DIR } from './layeredConfig.ts';\n\nconst DOMAINS_FILE = 'domains.yaml';\nconst UNKNOWN_DOMAIN_ERROR = 'Unknown domain';\nconst UNKNOWN_PLUGIN_ERROR = 'Unknown plugin';\nconst MISSING_PLUGIN_DIRECTORY_ERROR = 'Plugin directory does not exist';\nconst DEFAULT_DOMAINS_PATH = '.doom/domains.yaml';\nconst SUPPORTED_AGENT_PLUGIN_SCHEMA = 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction requireRecord(value: unknown, label: string): Record<string, unknown> {\n if (!isRecord(value)) throw new Error(`${label} must be an object`);\n return value;\n}\n\nfunction assertOnlyKeys(record: Record<string, unknown>, allowed: readonly string[], label: string): void {\n const unexpected = Object.keys(record).filter((key) => !allowed.includes(key));\n if (unexpected.length > 0) throw new Error(`${label} contains unsupported field \"${unexpected[0]}\"`);\n}\n\nfunction requireString(value: unknown, label: string): string {\n if (typeof value !== 'string' || !value.trim()) throw new Error(`${label} must be a non-empty string`);\n return value.trim();\n}\n\nfunction optionalString(value: unknown, label: string): string | undefined {\n if (value === undefined) return undefined;\n return requireString(value, label);\n}\n\nfunction parseStringArray(value: unknown, label: string): string[] {\n if (!Array.isArray(value)) throw new Error(`${label} must be an array of non-empty strings`);\n return [...new Set(value.map((entry, index) => requireString(entry, `${label}[${index}]`)))];\n}\n\nfunction parseOptionalBoolean(value: unknown, label: string): boolean | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'boolean') throw new Error(`${label} must be a boolean`);\n return value;\n}\n\nfunction parseDefaultDomains(value: unknown): string[] {\n return parseStringArray(value, `defaultDomains in ${DEFAULT_DOMAINS_PATH}`);\n}\n\nfunction parseDomainPlugin(value: unknown, label: string): DomainPlugin {\n if (typeof value === 'string') return requireString(value, label);\n const plugin = requireRecord(value, label);\n assertOnlyKeys(plugin, ['name', 'skills', 'agents', 'hooks', 'mcp'], label);\n const skills = plugin.skills === undefined ? undefined : parseStringArray(plugin.skills, `${label}.skills`);\n const agents = plugin.agents === undefined ? undefined : parseStringArray(plugin.agents, `${label}.agents`);\n const hooks = parseOptionalBoolean(plugin.hooks, `${label}.hooks`);\n const mcp = parseOptionalBoolean(plugin.mcp, `${label}.mcp`);\n return {\n name: requireString(plugin.name, `${label}.name`),\n ...(skills ? { skills } : {}),\n ...(agents ? { agents } : {}),\n ...(hooks === undefined ? {} : { hooks }),\n ...(mcp === undefined ? {} : { mcp }),\n };\n}\n\nfunction parseMcpAllowlist(value: unknown, label: string): DomainMcpAllowlist {\n const mcp = requireRecord(value, label);\n assertOnlyKeys(mcp, ['servers', 'proxy'], label);\n return {\n ...(mcp.servers === undefined ? {} : { servers: parseStringArray(mcp.servers, `${label}.servers`) }),\n ...(mcp.proxy === undefined ? {} : { proxy: parseStringArray(mcp.proxy, `${label}.proxy`) }),\n };\n}\n\nfunction parseDomain(value: unknown, label: string): DomainDefinition {\n const domain = requireRecord(value, label);\n assertOnlyKeys(domain, ['description', 'plugins', 'mcp', 'sharedSkills'], label);\n const description = optionalString(domain.description, `${label}.description`);\n if (domain.plugins !== undefined && !Array.isArray(domain.plugins)) {\n throw new Error(`${label}.plugins must be an array`);\n }\n const sharedSkills = parseOptionalBoolean(domain.sharedSkills, `${label}.sharedSkills`);\n return {\n ...(description ? { description } : {}),\n ...(Array.isArray(domain.plugins)\n ? { plugins: domain.plugins.map((plugin, index) => parseDomainPlugin(plugin, `${label}.plugins[${index}]`)) }\n : {}),\n ...(domain.mcp === undefined ? {} : { mcp: parseMcpAllowlist(domain.mcp, `${label}.mcp`) }),\n ...(sharedSkills === undefined ? {} : { sharedSkills }),\n };\n}\n\nfunction parseAliases(value: unknown, label: string): Record<string, string[]> {\n if (value === undefined) return {};\n const aliases = requireRecord(value, label);\n return Object.fromEntries(\n Object.entries(aliases).map(([name, domains]) => [name, parseStringArray(domains, `${label}.${name}`)]),\n );\n}\n\n/**\n * Reads domains and plugin catalogs from global and repository configs.\n *\n * Named domains and explicit plugin entries are replaced by later repository\n * definitions. Configured roots accumulate, with exact marketplace manifests\n * deduplicated in discovery order. Explicit entries override discovered IDs.\n */\nexport function loadDomains(repoRoot: string, homeDirectory: string = os.homedir()): DomainManifest {\n const sources = readDoomConfigSources<unknown>(DOMAINS_FILE, repoRoot, homeDirectory);\n const aliases: Record<string, string[]> = {};\n const domains: Record<string, ResolvedDomain> = {};\n let defaultDomains: string[] | undefined;\n\n for (const source of sources) {\n const document = requireRecord(source.document, source.filePath);\n assertOnlyKeys(document, ['plugins', 'domains', 'aliases', 'defaultDomains'], source.filePath);\n Object.assign(aliases, parseAliases(document.aliases, `aliases in ${source.filePath}`));\n if (document.domains !== undefined) {\n const sourceDomains = requireRecord(document.domains, `domains in ${source.filePath}`);\n for (const [name, definition] of Object.entries(sourceDomains)) {\n domains[name] = {\n ...parseDomain(definition, `domains.${name} in ${source.filePath}`),\n baseDirectory: source.baseDirectory,\n };\n }\n }\n if (Object.hasOwn(document, 'defaultDomains')) defaultDomains = parseDefaultDomains(document.defaultDomains);\n }\n\n const plugins = loadPluginCatalog(\n sources.map((source) => ({\n filePath: source.filePath,\n baseDirectory: source.baseDirectory,\n value: requireRecord(source.document, source.filePath).plugins,\n })),\n repoRoot,\n homeDirectory,\n );\n\n if (defaultDomains) {\n for (const name of expandDomainNames({ plugins, domains, aliases }, defaultDomains)) {\n if (!Object.hasOwn(domains, name)) {\n throw new Error(`Unknown default domain \"${name}\" in ${DEFAULT_DOMAINS_PATH}`);\n }\n }\n }\n return { plugins, domains, aliases, ...(defaultDomains ? { defaultDomains } : {}) };\n}\n\nexport function listDomainNames(repoRoot: string, homeDirectory?: string): string[] {\n const manifest = loadDomains(repoRoot, homeDirectory);\n return [...new Set([...Object.keys(manifest.domains), ...Object.keys(manifest.aliases)])].sort();\n}\n\n/**\n * Whether to include .claude/skills as shared fallbacks.\n *\n * Shared skills are included unless every selected domain opts out, so mixing a\n * trimmed domain with an untrimmed one cannot strip the untrimmed one's skills.\n */\nexport function resolveSharedSkills(repoRoot: string, domainNames: string[], homeDirectory?: string): boolean {\n const manifest = loadDomains(repoRoot, homeDirectory);\n const expanded = expandDomainNames(manifest, domainNames);\n if (expanded.length === 0) return true;\n return expanded.some((name) => manifest.domains[name]?.sharedSkills !== false);\n}\n\n/** Resolves named domain plugins while preserving each subset filter. */\nexport function resolvePluginEntries(\n repoRoot: string,\n domainNames: string[],\n explicitDirectories: string[],\n homeDirectory: string = os.homedir(),\n): PluginEntry[] {\n const manifest = loadDomains(repoRoot, homeDirectory);\n const entries: PluginEntry[] = [];\n const seen = new Set<string>();\n const homeDoomDirectory = globalDoomConfigDirectory(homeDirectory);\n\n for (const domainName of expandDomainNames(manifest, domainNames)) {\n const domain = manifest.domains[domainName];\n if (!domain) throw new Error(`${UNKNOWN_DOMAIN_ERROR}: ${domainName}`);\n for (const selection of domain.plugins ?? []) {\n const isSubset = typeof selection !== 'string';\n const pluginName = isSubset ? selection.name : selection;\n const plugin = manifest.plugins.entries[pluginName];\n if (!plugin) throw new Error(`${UNKNOWN_PLUGIN_ERROR} \"${pluginName}\" referenced by domain \"${domainName}\"`);\n const directory = pluginDirectoryForSource(plugin.source, homeDoomDirectory);\n if (plugin.source.type === 'local' && (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory())) {\n throw new Error(`${MISSING_PLUGIN_DIRECTORY_ERROR}: ${directory}`);\n }\n if (seen.has(directory)) continue;\n seen.add(directory);\n entries.push({\n name: pluginName,\n directory,\n source: plugin.source,\n ...(plugin.manifest?.agentPluginSchema === SUPPORTED_AGENT_PLUGIN_SCHEMA\n ? { skillDiscovery: 'direct-children' as const }\n : {}),\n ...(isSubset && selection.skills ? { skills: selection.skills } : {}),\n ...(isSubset && selection.agents ? { agents: selection.agents } : {}),\n ...(isSubset && selection.hooks !== undefined ? { hooks: selection.hooks } : {}),\n ...(isSubset && selection.mcp !== undefined ? { mcp: selection.mcp } : {}),\n });\n }\n }\n\n for (const configuredDirectory of explicitDirectories) {\n const directory = path.resolve(configuredDirectory);\n if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) {\n throw new Error(`${MISSING_PLUGIN_DIRECTORY_ERROR}: ${directory}`);\n }\n if (seen.has(directory)) continue;\n seen.add(directory);\n entries.push({\n name: path.basename(directory),\n directory,\n source: { type: 'local', path: directory },\n });\n }\n return entries;\n}\n\nexport function resolvePluginDirectories(\n repoRoot: string,\n domainNames: string[],\n explicitDirectories: string[],\n homeDirectory?: string,\n): string[] {\n return resolvePluginEntries(repoRoot, domainNames, explicitDirectories, homeDirectory).map(\n (entry) => entry.directory,\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { DomainManifest, PluginEntry } from "../types/domains.cjs";
2
+ import { DOOM_DIR } from "./layeredConfig.cjs";
3
+ //#region src/adapters/domains.d.ts
4
+ /**
5
+ * Reads domains and plugin catalogs from global and repository configs.
6
+ *
7
+ * Named domains and explicit plugin entries are replaced by later repository
8
+ * definitions. Configured roots accumulate, with exact marketplace manifests
9
+ * deduplicated in discovery order. Explicit entries override discovered IDs.
10
+ */
11
+ declare function loadDomains(repoRoot: string, homeDirectory?: string): DomainManifest;
12
+ declare function listDomainNames(repoRoot: string, homeDirectory?: string): string[];
13
+ /**
14
+ * Whether to include .claude/skills as shared fallbacks.
15
+ *
16
+ * Shared skills are included unless every selected domain opts out, so mixing a
17
+ * trimmed domain with an untrimmed one cannot strip the untrimmed one's skills.
18
+ */
19
+ declare function resolveSharedSkills(repoRoot: string, domainNames: string[], homeDirectory?: string): boolean;
20
+ /** Resolves named domain plugins while preserving each subset filter. */
21
+ declare function resolvePluginEntries(repoRoot: string, domainNames: string[], explicitDirectories: string[], homeDirectory?: string): PluginEntry[];
22
+ declare function resolvePluginDirectories(repoRoot: string, domainNames: string[], explicitDirectories: string[], homeDirectory?: string): string[];
23
+ //#endregion
24
+ export { DOOM_DIR, listDomainNames, loadDomains, resolvePluginDirectories, resolvePluginEntries, resolveSharedSkills };
25
+ //# sourceMappingURL=domains.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domains.d.cts","names":[],"sources":["../../src/adapters/domains.ts"],"mappings":";;;;;;;;;;iBA2HgB,YAAY,kBAAkB,yBAAuC;iBA0CrE,gBAAgB,kBAAkB;;;;;;;iBAWlC,oBAAoB,kBAAkB,uBAAuB;;iBAQ7D,qBACd,kBACA,uBACA,+BACA,yBACC;iBAmDa,yBACd,kBACA,uBACA,+BACA"}
@@ -0,0 +1,25 @@
1
+ import { DomainManifest, PluginEntry } from "../types/domains.mjs";
2
+ import { DOOM_DIR } from "./layeredConfig.mjs";
3
+ //#region src/adapters/domains.d.ts
4
+ /**
5
+ * Reads domains and plugin catalogs from global and repository configs.
6
+ *
7
+ * Named domains and explicit plugin entries are replaced by later repository
8
+ * definitions. Configured roots accumulate, with exact marketplace manifests
9
+ * deduplicated in discovery order. Explicit entries override discovered IDs.
10
+ */
11
+ declare function loadDomains(repoRoot: string, homeDirectory?: string): DomainManifest;
12
+ declare function listDomainNames(repoRoot: string, homeDirectory?: string): string[];
13
+ /**
14
+ * Whether to include .claude/skills as shared fallbacks.
15
+ *
16
+ * Shared skills are included unless every selected domain opts out, so mixing a
17
+ * trimmed domain with an untrimmed one cannot strip the untrimmed one's skills.
18
+ */
19
+ declare function resolveSharedSkills(repoRoot: string, domainNames: string[], homeDirectory?: string): boolean;
20
+ /** Resolves named domain plugins while preserving each subset filter. */
21
+ declare function resolvePluginEntries(repoRoot: string, domainNames: string[], explicitDirectories: string[], homeDirectory?: string): PluginEntry[];
22
+ declare function resolvePluginDirectories(repoRoot: string, domainNames: string[], explicitDirectories: string[], homeDirectory?: string): string[];
23
+ //#endregion
24
+ export { DOOM_DIR, listDomainNames, loadDomains, resolvePluginDirectories, resolvePluginEntries, resolveSharedSkills };
25
+ //# sourceMappingURL=domains.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domains.d.mts","names":[],"sources":["../../src/adapters/domains.ts"],"mappings":";;;;;;;;;;iBA2HgB,YAAY,kBAAkB,yBAAuC;iBA0CrE,gBAAgB,kBAAkB;;;;;;;iBAWlC,oBAAoB,kBAAkB,uBAAuB;;iBAQ7D,qBACd,kBACA,uBACA,+BACA,yBACC;iBAmDa,yBACd,kBACA,uBACA,+BACA"}
@@ -0,0 +1,2 @@
1
+ import{globalDoomConfigDirectory as e}from"./config.mjs";import{expandDomainNames as t}from"../services/domains.mjs";import{DOOM_DIR as n,readDoomConfigSources as r}from"./layeredConfig.mjs";import{loadPluginCatalog as i,pluginDirectoryForSource as a}from"./pluginCatalog.mjs";import o from"node:fs";import s from"node:path";import c from"node:os";const l=`Plugin directory does not exist`,u=`.doom/domains.yaml`;function d(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function f(e,t){if(!d(e))throw Error(`${t} must be an object`);return e}function p(e,t,n){let r=Object.keys(e).filter(e=>!t.includes(e));if(r.length>0)throw Error(`${n} contains unsupported field "${r[0]}"`)}function m(e,t){if(typeof e!=`string`||!e.trim())throw Error(`${t} must be a non-empty string`);return e.trim()}function h(e,t){if(e!==void 0)return m(e,t)}function g(e,t){if(!Array.isArray(e))throw Error(`${t} must be an array of non-empty strings`);return[...new Set(e.map((e,n)=>m(e,`${t}[${n}]`)))]}function _(e,t){if(e!==void 0){if(typeof e!=`boolean`)throw Error(`${t} must be a boolean`);return e}}function v(e){return g(e,`defaultDomains in ${u}`)}function y(e,t){if(typeof e==`string`)return m(e,t);let n=f(e,t);p(n,[`name`,`skills`,`agents`,`hooks`,`mcp`],t);let r=n.skills===void 0?void 0:g(n.skills,`${t}.skills`),i=n.agents===void 0?void 0:g(n.agents,`${t}.agents`),a=_(n.hooks,`${t}.hooks`),o=_(n.mcp,`${t}.mcp`);return{name:m(n.name,`${t}.name`),...r?{skills:r}:{},...i?{agents:i}:{},...a===void 0?{}:{hooks:a},...o===void 0?{}:{mcp:o}}}function b(e,t){let n=f(e,t);return p(n,[`servers`,`proxy`],t),{...n.servers===void 0?{}:{servers:g(n.servers,`${t}.servers`)},...n.proxy===void 0?{}:{proxy:g(n.proxy,`${t}.proxy`)}}}function x(e,t){let n=f(e,t);p(n,[`description`,`plugins`,`mcp`,`sharedSkills`],t);let r=h(n.description,`${t}.description`);if(n.plugins!==void 0&&!Array.isArray(n.plugins))throw Error(`${t}.plugins must be an array`);let i=_(n.sharedSkills,`${t}.sharedSkills`);return{...r?{description:r}:{},...Array.isArray(n.plugins)?{plugins:n.plugins.map((e,n)=>y(e,`${t}.plugins[${n}]`))}:{},...n.mcp===void 0?{}:{mcp:b(n.mcp,`${t}.mcp`)},...i===void 0?{}:{sharedSkills:i}}}function S(e,t){if(e===void 0)return{};let n=f(e,t);return Object.fromEntries(Object.entries(n).map(([e,n])=>[e,g(n,`${t}.${e}`)]))}function C(e,n=c.homedir()){let a=r(`domains.yaml`,e,n),o={},s={},l;for(let e of a){let t=f(e.document,e.filePath);if(p(t,[`plugins`,`domains`,`aliases`,`defaultDomains`],e.filePath),Object.assign(o,S(t.aliases,`aliases in ${e.filePath}`)),t.domains!==void 0){let n=f(t.domains,`domains in ${e.filePath}`);for(let[t,r]of Object.entries(n))s[t]={...x(r,`domains.${t} in ${e.filePath}`),baseDirectory:e.baseDirectory}}Object.hasOwn(t,`defaultDomains`)&&(l=v(t.defaultDomains))}let d=i(a.map(e=>({filePath:e.filePath,baseDirectory:e.baseDirectory,value:f(e.document,e.filePath).plugins})),e,n);if(l){for(let e of t({plugins:d,domains:s,aliases:o},l))if(!Object.hasOwn(s,e))throw Error(`Unknown default domain "${e}" in ${u}`)}return{plugins:d,domains:s,aliases:o,...l?{defaultDomains:l}:{}}}function w(e,t){let n=C(e,t);return[...new Set([...Object.keys(n.domains),...Object.keys(n.aliases)])].sort()}function T(e,n,r){let i=C(e,r),a=t(i,n);return a.length===0||a.some(e=>i.domains[e]?.sharedSkills!==!1)}function E(n,r,i,u=c.homedir()){let d=C(n,u),f=[],p=new Set,m=e(u);for(let e of t(d,r)){let t=d.domains[e];if(!t)throw Error(`Unknown domain: ${e}`);for(let n of t.plugins??[]){let t=typeof n!=`string`,r=t?n.name:n,i=d.plugins.entries[r];if(!i)throw Error(`Unknown plugin "${r}" referenced by domain "${e}"`);let s=a(i.source,m);if(i.source.type===`local`&&(!o.existsSync(s)||!o.statSync(s).isDirectory()))throw Error(`${l}: ${s}`);p.has(s)||(p.add(s),f.push({name:r,directory:s,source:i.source,...i.manifest?.agentPluginSchema===`https://agent-plugins.org/schemas/1.0.0/plugin.schema.json`?{skillDiscovery:`direct-children`}:{},...t&&n.skills?{skills:n.skills}:{},...t&&n.agents?{agents:n.agents}:{},...t&&n.hooks!==void 0?{hooks:n.hooks}:{},...t&&n.mcp!==void 0?{mcp:n.mcp}:{}}))}}for(let e of i){let t=s.resolve(e);if(!o.existsSync(t)||!o.statSync(t).isDirectory())throw Error(`${l}: ${t}`);p.has(t)||(p.add(t),f.push({name:s.basename(t),directory:t,source:{type:`local`,path:t}}))}return f}function D(e,t,n,r){return E(e,t,n,r).map(e=>e.directory)}export{n as DOOM_DIR,w as listDomainNames,C as loadDomains,D as resolvePluginDirectories,E as resolvePluginEntries,T as resolveSharedSkills};
2
+ //# sourceMappingURL=domains.mjs.map