@agimon-ai/doompi-config 0.0.1-alpha.0 → 0.0.1-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/domains.cjs +1 -1
- package/dist/domains.cjs.map +1 -1
- package/dist/domains.d.cts +4 -2
- package/dist/domains.d.cts.map +1 -1
- package/dist/domains.d.mts +4 -2
- package/dist/domains.d.mts.map +1 -1
- package/dist/domains.mjs +1 -1
- package/dist/domains.mjs.map +1 -1
- package/dist/init.cjs +7 -4
- package/dist/init.cjs.map +1 -1
- package/dist/init.d.cts.map +1 -1
- package/dist/init.d.mts.map +1 -1
- package/dist/init.mjs +7 -4
- package/dist/init.mjs.map +1 -1
- package/dist/majorModes.cjs +1 -1
- package/dist/majorModes.cjs.map +1 -1
- package/dist/majorModes.d.cts +2 -0
- package/dist/majorModes.d.cts.map +1 -1
- package/dist/majorModes.d.mts +2 -0
- package/dist/majorModes.d.mts.map +1 -1
- package/dist/majorModes.mjs +1 -1
- package/dist/majorModes.mjs.map +1 -1
- package/package.json +2 -2
- package/skills/doom-pi-config/references/config-contract.md +1 -1
package/dist/domains.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./layeredConfig.cjs");let n=require("node:fs");n=e.__toESM(n,1);let r=require("node:os");r=e.__toESM(r,1);let i=require("node:path");i=e.__toESM(i,1);function a(e,n=r.default.homedir()){let i=t.readDoomConfigSources(`domains.yaml`,e,n),
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./layeredConfig.cjs");let n=require("node:fs");n=e.__toESM(n,1);let r=require("node:os");r=e.__toESM(r,1);let i=require("node:path");i=e.__toESM(i,1);const a=`.doom/domains.yaml`;function o(e){if(!Array.isArray(e)||e.some(e=>typeof e!=`string`||!e.trim()))throw Error(`defaultDomains in ${a} must be an array of non-empty strings`);return[...new Set(e.map(e=>e.trim()))]}function s(e,n=r.default.homedir()){let i=t.readDoomConfigSources(`domains.yaml`,e,n),s={},c;for(let e of i)Object.assign(s,e.document.aliases??{}),Object.hasOwn(e.document,`defaultDomains`)&&(c=o(e.document.defaultDomains));let u=t.mergeNamedSources(i,e=>e.domains);if(c){for(let e of l({domains:u,aliases:s},c))if(!Object.hasOwn(u,e))throw Error(`Unknown default domain "${e}" in ${a}`)}return{domains:u,aliases:s,...c?{defaultDomains:c}:{}}}function c(e,t){let n=s(e,t);return[...new Set([...Object.keys(n.domains),...Object.keys(n.aliases)])].sort()}function l(e,t){return t.flatMap(e=>e.split(`,`).map(e=>e.trim()).filter(Boolean)).flatMap(t=>e.aliases[t]??[t])}function u(e){return e.match(/^\/domains\s+(?:[^\s,]+,)*([^\s,]*)$/)?.[1]}function d(e,t){let n=t.toLowerCase();return e.filter(e=>e.toLowerCase().startsWith(n)).map(e=>({value:e,label:e}))}function f(e,t,n){let r=s(e,n),i=l(r,t);return i.length===0||i.some(e=>r.domains[e]?.sharedSkills!==!1)}function p(e,t,r,a){let o=s(e,a),c=[],u=new Set;for(let e of l(o,t)){let t=o.domains[e];if(!t)throw Error(`Unknown domain: ${e}`);for(let e of t.plugins??[]){let r=typeof e!=`string`,a=i.default.resolve(t.baseDirectory,r?e.dir:e);if(!n.default.existsSync(a))throw Error(`Domain plugin does not exist: ${a}`);u.has(a)||(u.add(a),c.push(r?{directory:a,skills:e.skills,agents:e.agents,hooks:e.hooks,mcp:e.mcp}:{directory:a}))}}for(let e of r){if(!n.default.existsSync(e))throw Error(`Plugin directory does not exist: ${e}`);let t=i.default.resolve(e);u.has(t)||(u.add(t),c.push({directory:t}))}return c}function m(e,t,n,r){return p(e,t,n,r).map(e=>e.directory)}exports.DOOM_DIR=t.DOOM_DIR,exports.domainCompletionItems=d,exports.domainCompletionPrefix=u,exports.expandDomainNames=l,exports.listDomainNames=c,exports.loadDomains=s,exports.resolvePluginDirectories=m,exports.resolvePluginEntries=p,exports.resolveSharedSkills=f;
|
|
2
2
|
//# sourceMappingURL=domains.cjs.map
|
package/dist/domains.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domains.cjs","names":["os","readDoomConfigSources","mergeNamedSources","path","fs"],"sources":["../src/domains.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { mergeNamedSources, readDoomConfigSources } from './layeredConfig.ts';\n\nexport { DOOM_DIR } from './layeredConfig.ts';\n\n/** A plugin entry is a whole plugin dir, or a subset of one. */\nexport type DomainPlugin =\n | string\n | { dir: string; skills?: string[]; agents?: string[]; hooks?: boolean; mcp?: boolean };\n\nexport interface DomainMcpAllowlist {\n /** Server names kept from the repo-level .mcp.json. */\n servers?: string[];\n /** Upstream server names kept in the agiflow-proxy config. */\n proxy?: string[];\n}\n\nexport interface DomainDefinition {\n /**\n * Plugin directories, each one relative to the config that declares it: the\n * repository root for `.doom/domains.yaml`, the global `.doom` directory for\n * `~/.pi/.doom/domains.yaml`. The path is written out in full, so a plugin\n * does not have to live under `plugins/`.\n */\n plugins?: DomainPlugin[];\n mcp?: DomainMcpAllowlist;\n /** Prepend .claude/skills. Defaults to true. */\n sharedSkills?: boolean;\n}\n\n/** A domain plus the root its plugin paths resolve against. */\nexport interface ResolvedDomain extends DomainDefinition {\n baseDirectory: string;\n}\n\nexport interface DomainManifest {\n domains: Record<string, ResolvedDomain>;\n aliases: Record<string, string[]>;\n}\n\ninterface DomainDocument {\n domains?: Record<string, DomainDefinition>;\n aliases?: Record<string, string[]>;\n}\n\nconst DOMAINS_FILE = 'domains.yaml';\nconst UNKNOWN_DOMAIN_ERROR = 'Unknown domain';\nconst MISSING_DOMAIN_PLUGIN_ERROR = 'Domain plugin does not exist';\nconst MISSING_PLUGIN_DIRECTORY_ERROR = 'Plugin directory does not exist';\nconst NAME_DELIMITER = ',';\n\n/**\n * Reads domains from the global and repository configs, repository last.\n *\n * Either file alone is a complete manifest. A repository domain replaces the\n * global domain of the same name outright, so a machine-wide default can be\n * inherited everywhere and overridden where a repository disagrees.\n */\nexport function loadDomains(repoRoot: string, homeDirectory: string = os.homedir()): DomainManifest {\n const sources = readDoomConfigSources<DomainDocument>(DOMAINS_FILE, repoRoot, homeDirectory);\n const aliases: Record<string, string[]> = {};\n for (const source of sources) Object.assign(aliases, source.document.aliases ?? {});\n return { domains: mergeNamedSources(sources, (document) => document.domains), aliases };\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/** Splits comma-joined selections and expands aliases into concrete domain names. */\nexport function expandDomainNames(manifest: DomainManifest, domainNames: readonly string[]): string[] {\n return domainNames\n .flatMap((raw) =>\n raw\n .split(NAME_DELIMITER)\n .map((name) => name.trim())\n .filter(Boolean),\n )\n .flatMap((name) => manifest.aliases[name] ?? [name]);\n}\n\nexport function domainCompletionPrefix(textBeforeCursor: string): string | undefined {\n const match = textBeforeCursor.match(/^\\/domains\\s+(?:[^\\s,]+,)*([^\\s,]*)$/);\n return match?.[1];\n}\n\nexport function domainCompletionItems(domainNames: string[], query: string): Array<{ value: string; label: string }> {\n const normalizedQuery = query.toLowerCase();\n return domainNames\n .filter((name) => name.toLowerCase().startsWith(normalizedQuery))\n .map((name) => ({ value: name, label: name }));\n}\n\n/** A resolved plugin directory plus any subset filter the domain applied. */\nexport interface PluginEntry {\n directory: string;\n /** Skill names to keep. Omit to keep every skill in the plugin. */\n skills?: string[];\n /** Agent names to keep. Omit to keep every agent in the plugin. */\n agents?: string[];\n hooks?: boolean;\n mcp?: boolean;\n}\n\n/**\n * Whether to prepend .claude/skills.\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 domains to plugin entries, preserving each plugin's subset filter. */\nexport function resolvePluginEntries(\n repoRoot: string,\n domainNames: string[],\n explicitDirectories: string[],\n homeDirectory?: string,\n): PluginEntry[] {\n const manifest = loadDomains(repoRoot, homeDirectory);\n const entries: PluginEntry[] = [];\n const seen = new Set<string>();\n\n for (const name of expandDomainNames(manifest, domainNames)) {\n const domain = manifest.domains[name];\n if (!domain) throw new Error(`${UNKNOWN_DOMAIN_ERROR}: ${name}`);\n for (const plugin of domain.plugins ?? []) {\n const isSubset = typeof plugin !== 'string';\n const directory = path.resolve(domain.baseDirectory, isSubset ? plugin.dir : plugin);\n if (!fs.existsSync(directory)) throw new Error(`${MISSING_DOMAIN_PLUGIN_ERROR}: ${directory}`);\n if (seen.has(directory)) continue;\n seen.add(directory);\n entries.push(\n isSubset\n ? { directory, skills: plugin.skills, agents: plugin.agents, hooks: plugin.hooks, mcp: plugin.mcp }\n : { directory },\n );\n }\n }\n\n for (const directory of explicitDirectories) {\n if (!fs.existsSync(directory)) throw new Error(`${MISSING_PLUGIN_DIRECTORY_ERROR}: ${directory}`);\n const resolved = path.resolve(directory);\n if (seen.has(resolved)) continue;\n seen.add(resolved);\n entries.push({ directory: resolved });\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":"wRA4DA,SAAgB,EAAY,EAAkB,EAAwBA,EAAAA,QAAG,QAAQ,EAAmB,CAClG,IAAM,EAAUC,EAAAA,sBAAsC,eAAc,EAAU,CAAa,EACrF,EAAoC,CAAC,EAC3C,IAAK,IAAM,KAAU,EAAS,OAAO,OAAO,EAAS,EAAO,SAAS,SAAW,CAAC,CAAC,EAClF,MAAO,CAAE,QAASC,EAAAA,kBAAkB,EAAU,GAAa,EAAS,OAAO,EAAG,SAAQ,CACxF,CAEA,SAAgB,EAAgB,EAAkB,EAAkC,CAClF,IAAM,EAAW,EAAY,EAAU,CAAa,EACpD,MAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,EAAS,OAAO,EAAG,GAAG,OAAO,KAAK,EAAS,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CACjG,CAGA,SAAgB,EAAkB,EAA0B,EAA0C,CACpG,OAAO,EACJ,QAAS,GACR,EACG,MAAM,GAAc,CAAC,CACrB,IAAK,GAAS,EAAK,KAAK,CAAC,CAAC,CAC1B,OAAO,OAAO,CACnB,CAAC,CACA,QAAS,GAAS,EAAS,QAAQ,IAAS,CAAC,CAAI,CAAC,CACvD,CAEA,SAAgB,EAAuB,EAA8C,CAEnF,OADc,EAAiB,MAAM,sCAC1B,CAAC,GAAG,EACjB,CAEA,SAAgB,EAAsB,EAAuB,EAAwD,CACnH,IAAM,EAAkB,EAAM,YAAY,EAC1C,OAAO,EACJ,OAAQ,GAAS,EAAK,YAAY,CAAC,CAAC,WAAW,CAAe,CAAC,CAAC,CAChE,IAAK,IAAU,CAAE,MAAO,EAAM,MAAO,CAAK,EAAE,CACjD,CAmBA,SAAgB,EAAoB,EAAkB,EAAuB,EAAiC,CAC5G,IAAM,EAAW,EAAY,EAAU,CAAa,EAC9C,EAAW,EAAkB,EAAU,CAAW,EAExD,OADI,EAAS,SAAW,GACjB,EAAS,KAAM,GAAS,EAAS,QAAQ,EAAK,EAAE,eAAiB,EAAK,CAC/E,CAGA,SAAgB,EACd,EACA,EACA,EACA,EACe,CACf,IAAM,EAAW,EAAY,EAAU,CAAa,EAC9C,EAAyB,CAAC,EAC1B,EAAO,IAAI,IAEjB,IAAK,IAAM,KAAQ,EAAkB,EAAU,CAAW,EAAG,CAC3D,IAAM,EAAS,EAAS,QAAQ,GAChC,GAAI,CAAC,EAAQ,MAAU,MAAM,mBAA4B,GAAM,EAC/D,IAAK,IAAM,KAAU,EAAO,SAAW,CAAC,EAAG,CACzC,IAAM,EAAW,OAAO,GAAW,SAC7B,EAAYC,EAAAA,QAAK,QAAQ,EAAO,cAAe,EAAW,EAAO,IAAM,CAAM,EACnF,GAAI,CAACC,EAAAA,QAAG,WAAW,CAAS,EAAG,MAAU,MAAM,iCAAmC,GAAW,EACzF,EAAK,IAAI,CAAS,IACtB,EAAK,IAAI,CAAS,EAClB,EAAQ,KACN,EACI,CAAE,YAAW,OAAQ,EAAO,OAAQ,OAAQ,EAAO,OAAQ,MAAO,EAAO,MAAO,IAAK,EAAO,GAAI,EAChG,CAAE,WAAU,CAClB,EACF,CACF,CAEA,IAAK,IAAM,KAAa,EAAqB,CAC3C,GAAI,CAACA,EAAAA,QAAG,WAAW,CAAS,EAAG,MAAU,MAAM,oCAAsC,GAAW,EAChG,IAAM,EAAWD,EAAAA,QAAK,QAAQ,CAAS,EACnC,EAAK,IAAI,CAAQ,IACrB,EAAK,IAAI,CAAQ,EACjB,EAAQ,KAAK,CAAE,UAAW,CAAS,CAAC,EACtC,CACA,OAAO,CACT,CAEA,SAAgB,EACd,EACA,EACA,EACA,EACU,CACV,OAAO,EAAqB,EAAU,EAAa,EAAqB,CAAa,CAAC,CAAC,IACpF,GAAU,EAAM,SACnB,CACF"}
|
|
1
|
+
{"version":3,"file":"domains.cjs","names":["os","readDoomConfigSources","mergeNamedSources","path","fs"],"sources":["../src/domains.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { mergeNamedSources, readDoomConfigSources } from './layeredConfig.ts';\n\nexport { DOOM_DIR } from './layeredConfig.ts';\n\n/** A plugin entry is a whole plugin dir, or a subset of one. */\nexport type DomainPlugin =\n | string\n | { dir: string; skills?: string[]; agents?: string[]; hooks?: boolean; mcp?: boolean };\n\nexport interface DomainMcpAllowlist {\n /** Server names kept from the repo-level .mcp.json. */\n servers?: string[];\n /** Upstream server names kept in the agiflow-proxy config. */\n proxy?: string[];\n}\n\nexport interface DomainDefinition {\n /**\n * Plugin directories, each one relative to the config that declares it: the\n * repository root for `.doom/domains.yaml`, the global `.doom` directory for\n * `~/.pi/.doom/domains.yaml`. The path is written out in full, so a plugin\n * does not have to live under `plugins/`.\n */\n plugins?: DomainPlugin[];\n mcp?: DomainMcpAllowlist;\n /** Prepend .claude/skills. Defaults to true. */\n sharedSkills?: boolean;\n}\n\n/** A domain plus the root its plugin paths resolve against. */\nexport interface ResolvedDomain extends DomainDefinition {\n baseDirectory: string;\n}\n\nexport interface DomainManifest {\n domains: Record<string, ResolvedDomain>;\n aliases: Record<string, string[]>;\n /** Domains selected when neither flags nor the environment name any. */\n defaultDomains?: string[];\n}\n\ninterface DomainDocument {\n domains?: Record<string, DomainDefinition>;\n aliases?: Record<string, string[]>;\n defaultDomains?: unknown;\n}\n\nconst DOMAINS_FILE = 'domains.yaml';\nconst UNKNOWN_DOMAIN_ERROR = 'Unknown domain';\nconst MISSING_DOMAIN_PLUGIN_ERROR = 'Domain plugin does not exist';\nconst MISSING_PLUGIN_DIRECTORY_ERROR = 'Plugin directory does not exist';\nconst NAME_DELIMITER = ',';\nconst DEFAULT_DOMAINS_PATH = '.doom/domains.yaml';\n\nfunction parseDefaultDomains(value: unknown): string[] {\n if (!Array.isArray(value) || value.some((name) => typeof name !== 'string' || !name.trim())) {\n throw new Error(`defaultDomains in ${DEFAULT_DOMAINS_PATH} must be an array of non-empty strings`);\n }\n return [...new Set(value.map((name: string) => name.trim()))];\n}\n\n/**\n * Reads domains from the global and repository configs, repository last.\n *\n * Either file alone is a complete manifest. A repository domain replaces the\n * global domain of the same name outright, and a repository defaultDomains\n * list replaces the global list when present.\n */\nexport function loadDomains(repoRoot: string, homeDirectory: string = os.homedir()): DomainManifest {\n const sources = readDoomConfigSources<DomainDocument>(DOMAINS_FILE, repoRoot, homeDirectory);\n const aliases: Record<string, string[]> = {};\n let defaultDomains: string[] | undefined;\n for (const source of sources) {\n Object.assign(aliases, source.document.aliases ?? {});\n if (Object.hasOwn(source.document, 'defaultDomains')) {\n defaultDomains = parseDefaultDomains(source.document.defaultDomains);\n }\n }\n const domains = mergeNamedSources(sources, (document) => document.domains);\n if (defaultDomains) {\n for (const name of expandDomainNames({ 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 { 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/** Splits comma-joined selections and expands aliases into concrete domain names. */\nexport function expandDomainNames(manifest: DomainManifest, domainNames: readonly string[]): string[] {\n return domainNames\n .flatMap((raw) =>\n raw\n .split(NAME_DELIMITER)\n .map((name) => name.trim())\n .filter(Boolean),\n )\n .flatMap((name) => manifest.aliases[name] ?? [name]);\n}\n\nexport function domainCompletionPrefix(textBeforeCursor: string): string | undefined {\n const match = textBeforeCursor.match(/^\\/domains\\s+(?:[^\\s,]+,)*([^\\s,]*)$/);\n return match?.[1];\n}\n\nexport function domainCompletionItems(domainNames: string[], query: string): Array<{ value: string; label: string }> {\n const normalizedQuery = query.toLowerCase();\n return domainNames\n .filter((name) => name.toLowerCase().startsWith(normalizedQuery))\n .map((name) => ({ value: name, label: name }));\n}\n\n/** A resolved plugin directory plus any subset filter the domain applied. */\nexport interface PluginEntry {\n directory: string;\n /** Skill names to keep. Omit to keep every skill in the plugin. */\n skills?: string[];\n /** Agent names to keep. Omit to keep every agent in the plugin. */\n agents?: string[];\n hooks?: boolean;\n mcp?: boolean;\n}\n\n/**\n * Whether to prepend .claude/skills.\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 domains to plugin entries, preserving each plugin's subset filter. */\nexport function resolvePluginEntries(\n repoRoot: string,\n domainNames: string[],\n explicitDirectories: string[],\n homeDirectory?: string,\n): PluginEntry[] {\n const manifest = loadDomains(repoRoot, homeDirectory);\n const entries: PluginEntry[] = [];\n const seen = new Set<string>();\n\n for (const name of expandDomainNames(manifest, domainNames)) {\n const domain = manifest.domains[name];\n if (!domain) throw new Error(`${UNKNOWN_DOMAIN_ERROR}: ${name}`);\n for (const plugin of domain.plugins ?? []) {\n const isSubset = typeof plugin !== 'string';\n const directory = path.resolve(domain.baseDirectory, isSubset ? plugin.dir : plugin);\n if (!fs.existsSync(directory)) throw new Error(`${MISSING_DOMAIN_PLUGIN_ERROR}: ${directory}`);\n if (seen.has(directory)) continue;\n seen.add(directory);\n entries.push(\n isSubset\n ? { directory, skills: plugin.skills, agents: plugin.agents, hooks: plugin.hooks, mcp: plugin.mcp }\n : { directory },\n );\n }\n }\n\n for (const directory of explicitDirectories) {\n if (!fs.existsSync(directory)) throw new Error(`${MISSING_PLUGIN_DIRECTORY_ERROR}: ${directory}`);\n const resolved = path.resolve(directory);\n if (seen.has(resolved)) continue;\n seen.add(resolved);\n entries.push({ directory: resolved });\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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"}
|
package/dist/domains.d.cts
CHANGED
|
@@ -33,13 +33,15 @@ interface ResolvedDomain extends DomainDefinition {
|
|
|
33
33
|
interface DomainManifest {
|
|
34
34
|
domains: Record<string, ResolvedDomain>;
|
|
35
35
|
aliases: Record<string, string[]>;
|
|
36
|
+
/** Domains selected when neither flags nor the environment name any. */
|
|
37
|
+
defaultDomains?: string[];
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
40
|
* Reads domains from the global and repository configs, repository last.
|
|
39
41
|
*
|
|
40
42
|
* Either file alone is a complete manifest. A repository domain replaces the
|
|
41
|
-
* global domain of the same name outright,
|
|
42
|
-
*
|
|
43
|
+
* global domain of the same name outright, and a repository defaultDomains
|
|
44
|
+
* list replaces the global list when present.
|
|
43
45
|
*/
|
|
44
46
|
declare function loadDomains(repoRoot: string, homeDirectory?: string): DomainManifest;
|
|
45
47
|
declare function listDomainNames(repoRoot: string, homeDirectory?: string): string[];
|
package/dist/domains.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domains.d.cts","names":[],"sources":["../src/domains.ts"],"mappings":";;;KAQY;EAEN;EAAa;EAAmB;EAAmB;EAAiB;;UAEzD;;EAEf;;EAEA;;UAGe;;;;;;;EAOf,UAAU;EACV,MAAM;;EAEN;;;UAIe,uBAAuB;EACtC;;UAGe;EACf,SAAS,eAAe;EACxB,SAAS;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"domains.d.cts","names":[],"sources":["../src/domains.ts"],"mappings":";;;KAQY;EAEN;EAAa;EAAmB;EAAmB;EAAiB;;UAEzD;;EAEf;;EAEA;;UAGe;;;;;;;EAOf,UAAU;EACV,MAAM;;EAEN;;;UAIe,uBAAuB;EACtC;;UAGe;EACf,SAAS,eAAe;EACxB,SAAS;;EAET;;;;;;;;;iBA8Bc,YAAY,kBAAkB,yBAAuC;iBAqBrE,gBAAgB,kBAAkB;;iBAMlC,kBAAkB,UAAU,gBAAgB;iBAW5C,uBAAuB;iBAKvB,sBAAsB,uBAAuB,gBAAgB;EAAQ;EAAe;;;UAQnF;EACf;;EAEA;;EAEA;EACA;EACA;;;;;;;;iBASc,oBAAoB,kBAAkB,uBAAuB;;iBAQ7D,qBACd,kBACA,uBACA,+BACA,yBACC;iBAgCa,yBACd,kBACA,uBACA,+BACA"}
|
package/dist/domains.d.mts
CHANGED
|
@@ -33,13 +33,15 @@ interface ResolvedDomain extends DomainDefinition {
|
|
|
33
33
|
interface DomainManifest {
|
|
34
34
|
domains: Record<string, ResolvedDomain>;
|
|
35
35
|
aliases: Record<string, string[]>;
|
|
36
|
+
/** Domains selected when neither flags nor the environment name any. */
|
|
37
|
+
defaultDomains?: string[];
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
40
|
* Reads domains from the global and repository configs, repository last.
|
|
39
41
|
*
|
|
40
42
|
* Either file alone is a complete manifest. A repository domain replaces the
|
|
41
|
-
* global domain of the same name outright,
|
|
42
|
-
*
|
|
43
|
+
* global domain of the same name outright, and a repository defaultDomains
|
|
44
|
+
* list replaces the global list when present.
|
|
43
45
|
*/
|
|
44
46
|
declare function loadDomains(repoRoot: string, homeDirectory?: string): DomainManifest;
|
|
45
47
|
declare function listDomainNames(repoRoot: string, homeDirectory?: string): string[];
|
package/dist/domains.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domains.d.mts","names":[],"sources":["../src/domains.ts"],"mappings":";;;KAQY;EAEN;EAAa;EAAmB;EAAmB;EAAiB;;UAEzD;;EAEf;;EAEA;;UAGe;;;;;;;EAOf,UAAU;EACV,MAAM;;EAEN;;;UAIe,uBAAuB;EACtC;;UAGe;EACf,SAAS,eAAe;EACxB,SAAS;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"domains.d.mts","names":[],"sources":["../src/domains.ts"],"mappings":";;;KAQY;EAEN;EAAa;EAAmB;EAAmB;EAAiB;;UAEzD;;EAEf;;EAEA;;UAGe;;;;;;;EAOf,UAAU;EACV,MAAM;;EAEN;;;UAIe,uBAAuB;EACtC;;UAGe;EACf,SAAS,eAAe;EACxB,SAAS;;EAET;;;;;;;;;iBA8Bc,YAAY,kBAAkB,yBAAuC;iBAqBrE,gBAAgB,kBAAkB;;iBAMlC,kBAAkB,UAAU,gBAAgB;iBAW5C,uBAAuB;iBAKvB,sBAAsB,uBAAuB,gBAAgB;EAAQ;EAAe;;;UAQnF;EACf;;EAEA;;EAEA;EACA;EACA;;;;;;;;iBASc,oBAAoB,kBAAkB,uBAAuB;;iBAQ7D,qBACd,kBACA,uBACA,+BACA,yBACC;iBAgCa,yBACd,kBACA,uBACA,+BACA"}
|
package/dist/domains.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{DOOM_DIR as e,mergeNamedSources as t,readDoomConfigSources as n}from"./layeredConfig.mjs";import r from"node:fs";import i from"node:os";import a from"node:path";function o(e,r=i.homedir()){let a=n(`domains.yaml`,e,r),
|
|
1
|
+
import{DOOM_DIR as e,mergeNamedSources as t,readDoomConfigSources as n}from"./layeredConfig.mjs";import r from"node:fs";import i from"node:os";import a from"node:path";const o=`.doom/domains.yaml`;function s(e){if(!Array.isArray(e)||e.some(e=>typeof e!=`string`||!e.trim()))throw Error(`defaultDomains in ${o} must be an array of non-empty strings`);return[...new Set(e.map(e=>e.trim()))]}function c(e,r=i.homedir()){let a=n(`domains.yaml`,e,r),c={},l;for(let e of a)Object.assign(c,e.document.aliases??{}),Object.hasOwn(e.document,`defaultDomains`)&&(l=s(e.document.defaultDomains));let d=t(a,e=>e.domains);if(l){for(let e of u({domains:d,aliases:c},l))if(!Object.hasOwn(d,e))throw Error(`Unknown default domain "${e}" in ${o}`)}return{domains:d,aliases:c,...l?{defaultDomains:l}:{}}}function l(e,t){let n=c(e,t);return[...new Set([...Object.keys(n.domains),...Object.keys(n.aliases)])].sort()}function u(e,t){return t.flatMap(e=>e.split(`,`).map(e=>e.trim()).filter(Boolean)).flatMap(t=>e.aliases[t]??[t])}function d(e){return e.match(/^\/domains\s+(?:[^\s,]+,)*([^\s,]*)$/)?.[1]}function f(e,t){let n=t.toLowerCase();return e.filter(e=>e.toLowerCase().startsWith(n)).map(e=>({value:e,label:e}))}function p(e,t,n){let r=c(e,n),i=u(r,t);return i.length===0||i.some(e=>r.domains[e]?.sharedSkills!==!1)}function m(e,t,n,i){let o=c(e,i),s=[],l=new Set;for(let e of u(o,t)){let t=o.domains[e];if(!t)throw Error(`Unknown domain: ${e}`);for(let e of t.plugins??[]){let n=typeof e!=`string`,i=a.resolve(t.baseDirectory,n?e.dir:e);if(!r.existsSync(i))throw Error(`Domain plugin does not exist: ${i}`);l.has(i)||(l.add(i),s.push(n?{directory:i,skills:e.skills,agents:e.agents,hooks:e.hooks,mcp:e.mcp}:{directory:i}))}}for(let e of n){if(!r.existsSync(e))throw Error(`Plugin directory does not exist: ${e}`);let t=a.resolve(e);l.has(t)||(l.add(t),s.push({directory:t}))}return s}function h(e,t,n,r){return m(e,t,n,r).map(e=>e.directory)}export{e as DOOM_DIR,f as domainCompletionItems,d as domainCompletionPrefix,u as expandDomainNames,l as listDomainNames,c as loadDomains,h as resolvePluginDirectories,m as resolvePluginEntries,p as resolveSharedSkills};
|
|
2
2
|
//# sourceMappingURL=domains.mjs.map
|
package/dist/domains.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domains.mjs","names":[],"sources":["../src/domains.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { mergeNamedSources, readDoomConfigSources } from './layeredConfig.ts';\n\nexport { DOOM_DIR } from './layeredConfig.ts';\n\n/** A plugin entry is a whole plugin dir, or a subset of one. */\nexport type DomainPlugin =\n | string\n | { dir: string; skills?: string[]; agents?: string[]; hooks?: boolean; mcp?: boolean };\n\nexport interface DomainMcpAllowlist {\n /** Server names kept from the repo-level .mcp.json. */\n servers?: string[];\n /** Upstream server names kept in the agiflow-proxy config. */\n proxy?: string[];\n}\n\nexport interface DomainDefinition {\n /**\n * Plugin directories, each one relative to the config that declares it: the\n * repository root for `.doom/domains.yaml`, the global `.doom` directory for\n * `~/.pi/.doom/domains.yaml`. The path is written out in full, so a plugin\n * does not have to live under `plugins/`.\n */\n plugins?: DomainPlugin[];\n mcp?: DomainMcpAllowlist;\n /** Prepend .claude/skills. Defaults to true. */\n sharedSkills?: boolean;\n}\n\n/** A domain plus the root its plugin paths resolve against. */\nexport interface ResolvedDomain extends DomainDefinition {\n baseDirectory: string;\n}\n\nexport interface DomainManifest {\n domains: Record<string, ResolvedDomain>;\n aliases: Record<string, string[]>;\n}\n\ninterface DomainDocument {\n domains?: Record<string, DomainDefinition>;\n aliases?: Record<string, string[]>;\n}\n\nconst DOMAINS_FILE = 'domains.yaml';\nconst UNKNOWN_DOMAIN_ERROR = 'Unknown domain';\nconst MISSING_DOMAIN_PLUGIN_ERROR = 'Domain plugin does not exist';\nconst MISSING_PLUGIN_DIRECTORY_ERROR = 'Plugin directory does not exist';\nconst NAME_DELIMITER = ',';\n\n/**\n * Reads domains from the global and repository configs, repository last.\n *\n * Either file alone is a complete manifest. A repository domain replaces the\n * global domain of the same name outright,
|
|
1
|
+
{"version":3,"file":"domains.mjs","names":[],"sources":["../src/domains.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { mergeNamedSources, readDoomConfigSources } from './layeredConfig.ts';\n\nexport { DOOM_DIR } from './layeredConfig.ts';\n\n/** A plugin entry is a whole plugin dir, or a subset of one. */\nexport type DomainPlugin =\n | string\n | { dir: string; skills?: string[]; agents?: string[]; hooks?: boolean; mcp?: boolean };\n\nexport interface DomainMcpAllowlist {\n /** Server names kept from the repo-level .mcp.json. */\n servers?: string[];\n /** Upstream server names kept in the agiflow-proxy config. */\n proxy?: string[];\n}\n\nexport interface DomainDefinition {\n /**\n * Plugin directories, each one relative to the config that declares it: the\n * repository root for `.doom/domains.yaml`, the global `.doom` directory for\n * `~/.pi/.doom/domains.yaml`. The path is written out in full, so a plugin\n * does not have to live under `plugins/`.\n */\n plugins?: DomainPlugin[];\n mcp?: DomainMcpAllowlist;\n /** Prepend .claude/skills. Defaults to true. */\n sharedSkills?: boolean;\n}\n\n/** A domain plus the root its plugin paths resolve against. */\nexport interface ResolvedDomain extends DomainDefinition {\n baseDirectory: string;\n}\n\nexport interface DomainManifest {\n domains: Record<string, ResolvedDomain>;\n aliases: Record<string, string[]>;\n /** Domains selected when neither flags nor the environment name any. */\n defaultDomains?: string[];\n}\n\ninterface DomainDocument {\n domains?: Record<string, DomainDefinition>;\n aliases?: Record<string, string[]>;\n defaultDomains?: unknown;\n}\n\nconst DOMAINS_FILE = 'domains.yaml';\nconst UNKNOWN_DOMAIN_ERROR = 'Unknown domain';\nconst MISSING_DOMAIN_PLUGIN_ERROR = 'Domain plugin does not exist';\nconst MISSING_PLUGIN_DIRECTORY_ERROR = 'Plugin directory does not exist';\nconst NAME_DELIMITER = ',';\nconst DEFAULT_DOMAINS_PATH = '.doom/domains.yaml';\n\nfunction parseDefaultDomains(value: unknown): string[] {\n if (!Array.isArray(value) || value.some((name) => typeof name !== 'string' || !name.trim())) {\n throw new Error(`defaultDomains in ${DEFAULT_DOMAINS_PATH} must be an array of non-empty strings`);\n }\n return [...new Set(value.map((name: string) => name.trim()))];\n}\n\n/**\n * Reads domains from the global and repository configs, repository last.\n *\n * Either file alone is a complete manifest. A repository domain replaces the\n * global domain of the same name outright, and a repository defaultDomains\n * list replaces the global list when present.\n */\nexport function loadDomains(repoRoot: string, homeDirectory: string = os.homedir()): DomainManifest {\n const sources = readDoomConfigSources<DomainDocument>(DOMAINS_FILE, repoRoot, homeDirectory);\n const aliases: Record<string, string[]> = {};\n let defaultDomains: string[] | undefined;\n for (const source of sources) {\n Object.assign(aliases, source.document.aliases ?? {});\n if (Object.hasOwn(source.document, 'defaultDomains')) {\n defaultDomains = parseDefaultDomains(source.document.defaultDomains);\n }\n }\n const domains = mergeNamedSources(sources, (document) => document.domains);\n if (defaultDomains) {\n for (const name of expandDomainNames({ 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 { 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/** Splits comma-joined selections and expands aliases into concrete domain names. */\nexport function expandDomainNames(manifest: DomainManifest, domainNames: readonly string[]): string[] {\n return domainNames\n .flatMap((raw) =>\n raw\n .split(NAME_DELIMITER)\n .map((name) => name.trim())\n .filter(Boolean),\n )\n .flatMap((name) => manifest.aliases[name] ?? [name]);\n}\n\nexport function domainCompletionPrefix(textBeforeCursor: string): string | undefined {\n const match = textBeforeCursor.match(/^\\/domains\\s+(?:[^\\s,]+,)*([^\\s,]*)$/);\n return match?.[1];\n}\n\nexport function domainCompletionItems(domainNames: string[], query: string): Array<{ value: string; label: string }> {\n const normalizedQuery = query.toLowerCase();\n return domainNames\n .filter((name) => name.toLowerCase().startsWith(normalizedQuery))\n .map((name) => ({ value: name, label: name }));\n}\n\n/** A resolved plugin directory plus any subset filter the domain applied. */\nexport interface PluginEntry {\n directory: string;\n /** Skill names to keep. Omit to keep every skill in the plugin. */\n skills?: string[];\n /** Agent names to keep. Omit to keep every agent in the plugin. */\n agents?: string[];\n hooks?: boolean;\n mcp?: boolean;\n}\n\n/**\n * Whether to prepend .claude/skills.\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 domains to plugin entries, preserving each plugin's subset filter. */\nexport function resolvePluginEntries(\n repoRoot: string,\n domainNames: string[],\n explicitDirectories: string[],\n homeDirectory?: string,\n): PluginEntry[] {\n const manifest = loadDomains(repoRoot, homeDirectory);\n const entries: PluginEntry[] = [];\n const seen = new Set<string>();\n\n for (const name of expandDomainNames(manifest, domainNames)) {\n const domain = manifest.domains[name];\n if (!domain) throw new Error(`${UNKNOWN_DOMAIN_ERROR}: ${name}`);\n for (const plugin of domain.plugins ?? []) {\n const isSubset = typeof plugin !== 'string';\n const directory = path.resolve(domain.baseDirectory, isSubset ? plugin.dir : plugin);\n if (!fs.existsSync(directory)) throw new Error(`${MISSING_DOMAIN_PLUGIN_ERROR}: ${directory}`);\n if (seen.has(directory)) continue;\n seen.add(directory);\n entries.push(\n isSubset\n ? { directory, skills: plugin.skills, agents: plugin.agents, hooks: plugin.hooks, mcp: plugin.mcp }\n : { directory },\n );\n }\n }\n\n for (const directory of explicitDirectories) {\n if (!fs.existsSync(directory)) throw new Error(`${MISSING_PLUGIN_DIRECTORY_ERROR}: ${directory}`);\n const resolved = path.resolve(directory);\n if (seen.has(resolved)) continue;\n seen.add(resolved);\n entries.push({ directory: resolved });\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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"}
|
package/dist/init.cjs
CHANGED
|
@@ -49,20 +49,23 @@ layers:
|
|
|
49
49
|
ask-user:
|
|
50
50
|
packages:
|
|
51
51
|
- '@juicesharp/rpiv-ask-user-question'
|
|
52
|
-
#
|
|
53
|
-
#
|
|
52
|
+
# A mode is a named set of the layers above. The default keeps its real mode
|
|
53
|
+
# name; it is selected only when neither --major-mode nor DOOMPI_MAJOR_MODE is set.
|
|
54
|
+
defaultMajorMode: copilot
|
|
54
55
|
majorMode:
|
|
55
56
|
minimal: [guardrails, team]
|
|
56
57
|
copilot: [guardrails, vibe-lint, team, plan-mode, runner, style-system, goal, ask-user]
|
|
57
58
|
`,"domains.yaml":`# A domain is a named set of plugin directories loaded for a session.
|
|
58
59
|
# Add your own alongside default; aliases map short names onto them.
|
|
60
|
+
# Defaults are a list because one session may combine several domains.
|
|
61
|
+
defaultDomains: [default]
|
|
59
62
|
#
|
|
60
63
|
# Each plugin path is written in full and is relative to the config that
|
|
61
64
|
# declares it, so it resolves beside this file here (~/.pi/.doom) and against
|
|
62
65
|
# the repository root in a repository copy:
|
|
63
66
|
# plugins: ['plugins/my-plugin']
|
|
64
|
-
# A repository's own .doom/domains.yaml is read after this file
|
|
65
|
-
# domain of the same name.
|
|
67
|
+
# A repository's own .doom/domains.yaml is read after this file, replaces any
|
|
68
|
+
# domain of the same name, and can replace defaultDomains.
|
|
66
69
|
domains:
|
|
67
70
|
default:
|
|
68
71
|
plugins: []
|
package/dist/init.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.cjs","names":["fs","path","randomUUID","os","globalDoomConfigDirectory"],"sources":["../src/init.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from './config.ts';\n\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\nconst UTF8_ENCODING = 'utf8';\nconst FILE_ALREADY_EXISTS = 'EEXIST';\nconst FILE_NOT_FOUND = 'ENOENT';\n\n/** The global seed files are processed in this fixed order. */\nexport const GLOBAL_DOOM_SEED_FILES = [\n 'config.yaml',\n 'modes.yaml',\n 'domains.yaml',\n 'profiles.yaml',\n 'hooks.yaml',\n] as const;\n\ntype GlobalDoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];\n\n/** Portable defaults for a new global Doom configuration directory. */\nexport const DOOM_CONFIG_TEMPLATES: Readonly<Record<GlobalDoomSeedFile, string>> = {\n 'config.yaml': `projectTrust: ask\n\n# Autonomous voice is opt-in and uses an explicitly configured Pi model.\n# voice:\n# autoCapture:\n# model: provider/model-id\n# startPhrases:\n# - hey doom\n# stopPhrases:\n# - stop speaking\n# tts:\n# engine: macos-say\n# voice: Samantha\n# rate: 190\n`,\n 'modes.yaml': `# Seeded defaults. Every layer here resolves from what Doom Pi installs or\n# from a public package, so a fresh install works with no extra setup.\n#\n# Layers and modes defined here apply to every repository. A repository's own\n# .doom/modes.yaml is read after this file and replaces any layer or mode of the\n# same name, so a repository can override one entry without restating the rest.\n#\n# A layer package is an npm specifier, or a path if it was never published:\n# packages: ['./extensions/my-ext']\n# A path is relative to the config that declares it, so it resolves beside this\n# file here (~/.pi/.doom) and against the repository root in a repository copy.\nlayers:\n guardrails:\n # repositoryHooks ships with Doom Pi, so the layer steers the agent toward\n # the governing skill and scaffolding before it writes.\n extensions: [repositoryHooks]\n hookGroups: [guardrails, scaffolding]\n vibe-lint:\n packages:\n - '@agimon-ai/vibe-lint/extensions/pi'\n team:\n packages:\n - '@agimon-ai/doompi-team/extensions/doom'\n plan-mode:\n packages:\n - '@agimon-ai/doompi-plan/extensions/doom'\n runner:\n packages:\n - '@agimon-ai/doompi-runner/extensions/pi'\n style-system:\n extensions: [styleSystem]\n goal:\n packages:\n - '@narumitw/pi-goal/src/index.ts'\n ask-user:\n packages:\n - '@juicesharp/rpiv-ask-user-question'\n# copilot is the default when --major-mode is not passed. Add your own modes\n# here; a mode is just a named set of the layers above.\nmajorMode:\n minimal: [guardrails, team]\n copilot: [guardrails, vibe-lint, team, plan-mode, runner, style-system, goal, ask-user]\n`,\n 'domains.yaml': `# A domain is a named set of plugin directories loaded for a session.\n# Add your own alongside default; aliases map short names onto them.\n#\n# Each plugin path is written in full and is relative to the config that\n# declares it, so it resolves beside this file here (~/.pi/.doom) and against\n# the repository root in a repository copy:\n# plugins: ['plugins/my-plugin']\n# A repository's own .doom/domains.yaml is read after this file and replaces any\n# domain of the same name.\ndomains:\n default:\n plugins: []\naliases: {}\n`,\n 'profiles.yaml': 'profiles: {}\\n',\n 'hooks.yaml': 'groups: {}\\n',\n};\n\n/** Result of filling the global seed directory. File arrays contain names in seed order. */\nexport interface GlobalDoomInitResult {\n directory: string;\n created: string[];\n preserved: string[];\n /** Existing files overwritten from the template. Only ever non-empty under `force`. */\n replaced: string[];\n}\n\nexport interface GlobalDoomInitOptions {\n /**\n * Overwrite existing seed files instead of preserving them.\n *\n * Off by default: init is safe to re-run, and hand-edited config is the\n * normal case rather than the exception.\n */\n force?: boolean;\n}\n\nfunction errorCode(error: unknown): string | undefined {\n if (!(error instanceof Error)) return undefined;\n const code = (error as NodeJS.ErrnoException).code;\n return typeof code === 'string' ? code : undefined;\n}\n\nfunction ensureDirectory(directory: string): void {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(directory);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n try {\n fs.mkdirSync(directory, { mode: PRIVATE_DIRECTORY_MODE });\n stats = fs.lstatSync(directory);\n } catch (mkdirError) {\n if (errorCode(mkdirError) !== FILE_ALREADY_EXISTS) throw mkdirError;\n stats = fs.lstatSync(directory);\n }\n }\n\n if (stats.isSymbolicLink()) {\n throw new Error(`Refusing to initialize through symlinked directory: ${directory}`);\n }\n if (!stats.isDirectory()) {\n throw new Error(`Doom config path is not a directory: ${directory}`);\n }\n}\n\nfunction ensureGlobalDoomDirectory(directory: string): void {\n ensureDirectory(path.dirname(directory));\n ensureDirectory(directory);\n}\n\nfunction classifyTarget(targetPath: string): 'missing' | 'file' {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(targetPath);\n } catch (error) {\n if (errorCode(error) === FILE_NOT_FOUND) return 'missing';\n throw error;\n }\n\n if (stats.isFile()) return 'file';\n if (stats.isSymbolicLink()) {\n try {\n if (fs.statSync(targetPath).isFile()) return 'file';\n } catch (error) {\n throw new Error(`Doom config seed symlink is not file-like: ${targetPath}`, { cause: error });\n }\n }\n throw new Error(`Doom config seed path is not file-like: ${targetPath}`);\n}\n\nfunction cleanupTemporaryFile(temporaryPath: string): void {\n try {\n fs.unlinkSync(temporaryPath);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n }\n}\n\nfunction publishExclusively(targetPath: string, content: string): boolean {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', PRIVATE_FILE_MODE);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, PRIVATE_FILE_MODE);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n try {\n fs.linkSync(temporaryPath, targetPath);\n return true;\n } catch (error) {\n if (errorCode(error) === FILE_ALREADY_EXISTS && classifyTarget(targetPath) === 'file') return false;\n throw error;\n }\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Replaces a seed file with the template, atomically.\n *\n * The rename swaps the directory entry in one step, so a reader either sees the\n * whole old file or the whole new one. Renaming onto a symlinked seed replaces\n * the link rather than writing through it.\n */\nfunction publishForcefully(targetPath: string, content: string): void {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', PRIVATE_FILE_MODE);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, PRIVATE_FILE_MODE);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n fs.renameSync(temporaryPath, targetPath);\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Fills missing global seed files.\n *\n * Existing files are left alone so that re-running init never discards\n * hand-edited config; pass `force` to overwrite them from the template.\n */\nexport function initializeGlobalDoomConfig(\n homeDirectory = os.homedir(),\n options: GlobalDoomInitOptions = {},\n): GlobalDoomInitResult {\n const directory = globalDoomConfigDirectory(homeDirectory);\n ensureGlobalDoomDirectory(directory);\n\n const created: string[] = [];\n const preserved: string[] = [];\n const replaced: string[] = [];\n for (const fileName of GLOBAL_DOOM_SEED_FILES) {\n const targetPath = path.join(directory, fileName);\n const existed = classifyTarget(targetPath) === 'file';\n\n if (options.force) {\n publishForcefully(targetPath, DOOM_CONFIG_TEMPLATES[fileName]);\n (existed ? replaced : created).push(fileName);\n continue;\n }\n if (existed) {\n preserved.push(fileName);\n continue;\n }\n if (publishExclusively(targetPath, DOOM_CONFIG_TEMPLATES[fileName])) created.push(fileName);\n else preserved.push(fileName);\n }\n\n return { directory, created, preserved, replaced };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"init.cjs","names":["fs","path","randomUUID","os","globalDoomConfigDirectory"],"sources":["../src/init.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from './config.ts';\n\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\nconst UTF8_ENCODING = 'utf8';\nconst FILE_ALREADY_EXISTS = 'EEXIST';\nconst FILE_NOT_FOUND = 'ENOENT';\n\n/** The global seed files are processed in this fixed order. */\nexport const GLOBAL_DOOM_SEED_FILES = [\n 'config.yaml',\n 'modes.yaml',\n 'domains.yaml',\n 'profiles.yaml',\n 'hooks.yaml',\n] as const;\n\ntype GlobalDoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];\n\n/** Portable defaults for a new global Doom configuration directory. */\nexport const DOOM_CONFIG_TEMPLATES: Readonly<Record<GlobalDoomSeedFile, string>> = {\n 'config.yaml': `projectTrust: ask\n\n# Autonomous voice is opt-in and uses an explicitly configured Pi model.\n# voice:\n# autoCapture:\n# model: provider/model-id\n# startPhrases:\n# - hey doom\n# stopPhrases:\n# - stop speaking\n# tts:\n# engine: macos-say\n# voice: Samantha\n# rate: 190\n`,\n 'modes.yaml': `# Seeded defaults. Every layer here resolves from what Doom Pi installs or\n# from a public package, so a fresh install works with no extra setup.\n#\n# Layers and modes defined here apply to every repository. A repository's own\n# .doom/modes.yaml is read after this file and replaces any layer or mode of the\n# same name, so a repository can override one entry without restating the rest.\n#\n# A layer package is an npm specifier, or a path if it was never published:\n# packages: ['./extensions/my-ext']\n# A path is relative to the config that declares it, so it resolves beside this\n# file here (~/.pi/.doom) and against the repository root in a repository copy.\nlayers:\n guardrails:\n # repositoryHooks ships with Doom Pi, so the layer steers the agent toward\n # the governing skill and scaffolding before it writes.\n extensions: [repositoryHooks]\n hookGroups: [guardrails, scaffolding]\n vibe-lint:\n packages:\n - '@agimon-ai/vibe-lint/extensions/pi'\n team:\n packages:\n - '@agimon-ai/doompi-team/extensions/doom'\n plan-mode:\n packages:\n - '@agimon-ai/doompi-plan/extensions/doom'\n runner:\n packages:\n - '@agimon-ai/doompi-runner/extensions/pi'\n style-system:\n extensions: [styleSystem]\n goal:\n packages:\n - '@narumitw/pi-goal/src/index.ts'\n ask-user:\n packages:\n - '@juicesharp/rpiv-ask-user-question'\n# A mode is a named set of the layers above. The default keeps its real mode\n# name; it is selected only when neither --major-mode nor DOOMPI_MAJOR_MODE is set.\ndefaultMajorMode: copilot\nmajorMode:\n minimal: [guardrails, team]\n copilot: [guardrails, vibe-lint, team, plan-mode, runner, style-system, goal, ask-user]\n`,\n 'domains.yaml': `# A domain is a named set of plugin directories loaded for a session.\n# Add your own alongside default; aliases map short names onto them.\n# Defaults are a list because one session may combine several domains.\ndefaultDomains: [default]\n#\n# Each plugin path is written in full and is relative to the config that\n# declares it, so it resolves beside this file here (~/.pi/.doom) and against\n# the repository root in a repository copy:\n# plugins: ['plugins/my-plugin']\n# A repository's own .doom/domains.yaml is read after this file, replaces any\n# domain of the same name, and can replace defaultDomains.\ndomains:\n default:\n plugins: []\naliases: {}\n`,\n 'profiles.yaml': 'profiles: {}\\n',\n 'hooks.yaml': 'groups: {}\\n',\n};\n\n/** Result of filling the global seed directory. File arrays contain names in seed order. */\nexport interface GlobalDoomInitResult {\n directory: string;\n created: string[];\n preserved: string[];\n /** Existing files overwritten from the template. Only ever non-empty under `force`. */\n replaced: string[];\n}\n\nexport interface GlobalDoomInitOptions {\n /**\n * Overwrite existing seed files instead of preserving them.\n *\n * Off by default: init is safe to re-run, and hand-edited config is the\n * normal case rather than the exception.\n */\n force?: boolean;\n}\n\nfunction errorCode(error: unknown): string | undefined {\n if (!(error instanceof Error)) return undefined;\n const code = (error as NodeJS.ErrnoException).code;\n return typeof code === 'string' ? code : undefined;\n}\n\nfunction ensureDirectory(directory: string): void {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(directory);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n try {\n fs.mkdirSync(directory, { mode: PRIVATE_DIRECTORY_MODE });\n stats = fs.lstatSync(directory);\n } catch (mkdirError) {\n if (errorCode(mkdirError) !== FILE_ALREADY_EXISTS) throw mkdirError;\n stats = fs.lstatSync(directory);\n }\n }\n\n if (stats.isSymbolicLink()) {\n throw new Error(`Refusing to initialize through symlinked directory: ${directory}`);\n }\n if (!stats.isDirectory()) {\n throw new Error(`Doom config path is not a directory: ${directory}`);\n }\n}\n\nfunction ensureGlobalDoomDirectory(directory: string): void {\n ensureDirectory(path.dirname(directory));\n ensureDirectory(directory);\n}\n\nfunction classifyTarget(targetPath: string): 'missing' | 'file' {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(targetPath);\n } catch (error) {\n if (errorCode(error) === FILE_NOT_FOUND) return 'missing';\n throw error;\n }\n\n if (stats.isFile()) return 'file';\n if (stats.isSymbolicLink()) {\n try {\n if (fs.statSync(targetPath).isFile()) return 'file';\n } catch (error) {\n throw new Error(`Doom config seed symlink is not file-like: ${targetPath}`, { cause: error });\n }\n }\n throw new Error(`Doom config seed path is not file-like: ${targetPath}`);\n}\n\nfunction cleanupTemporaryFile(temporaryPath: string): void {\n try {\n fs.unlinkSync(temporaryPath);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n }\n}\n\nfunction publishExclusively(targetPath: string, content: string): boolean {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', PRIVATE_FILE_MODE);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, PRIVATE_FILE_MODE);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n try {\n fs.linkSync(temporaryPath, targetPath);\n return true;\n } catch (error) {\n if (errorCode(error) === FILE_ALREADY_EXISTS && classifyTarget(targetPath) === 'file') return false;\n throw error;\n }\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Replaces a seed file with the template, atomically.\n *\n * The rename swaps the directory entry in one step, so a reader either sees the\n * whole old file or the whole new one. Renaming onto a symlinked seed replaces\n * the link rather than writing through it.\n */\nfunction publishForcefully(targetPath: string, content: string): void {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', PRIVATE_FILE_MODE);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, PRIVATE_FILE_MODE);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n fs.renameSync(temporaryPath, targetPath);\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Fills missing global seed files.\n *\n * Existing files are left alone so that re-running init never discards\n * hand-edited config; pass `force` to overwrite them from the template.\n */\nexport function initializeGlobalDoomConfig(\n homeDirectory = os.homedir(),\n options: GlobalDoomInitOptions = {},\n): GlobalDoomInitResult {\n const directory = globalDoomConfigDirectory(homeDirectory);\n ensureGlobalDoomDirectory(directory);\n\n const created: string[] = [];\n const preserved: string[] = [];\n const replaced: string[] = [];\n for (const fileName of GLOBAL_DOOM_SEED_FILES) {\n const targetPath = path.join(directory, fileName);\n const existed = classifyTarget(targetPath) === 'file';\n\n if (options.force) {\n publishForcefully(targetPath, DOOM_CONFIG_TEMPLATES[fileName]);\n (existed ? replaced : created).push(fileName);\n continue;\n }\n if (existed) {\n preserved.push(fileName);\n continue;\n }\n if (publishExclusively(targetPath, DOOM_CONFIG_TEMPLATES[fileName])) created.push(fileName);\n else preserved.push(fileName);\n }\n\n return { directory, created, preserved, replaced };\n}\n"],"mappings":"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"}
|
package/dist/init.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.cts","names":[],"sources":["../src/init.ts"],"mappings":";;cAca;KAQR,6BAA6B;;cAGrB,uBAAuB,SAAS,OAAO;;
|
|
1
|
+
{"version":3,"file":"init.d.cts","names":[],"sources":["../src/init.ts"],"mappings":";;cAca;KAQR,6BAA6B;;cAGrB,uBAAuB,SAAS,OAAO;;UAiFnC;EACf;EACA;EACA;;EAEA;;UAGe;;;;;;;EAOf;;;;;;;;iBAkIc,2BACd,wBACA,UAAS,wBACR"}
|
package/dist/init.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.mts","names":[],"sources":["../src/init.ts"],"mappings":";;cAca;KAQR,6BAA6B;;cAGrB,uBAAuB,SAAS,OAAO;;
|
|
1
|
+
{"version":3,"file":"init.d.mts","names":[],"sources":["../src/init.ts"],"mappings":";;cAca;KAQR,6BAA6B;;cAGrB,uBAAuB,SAAS,OAAO;;UAiFnC;EACf;EACA;EACA;;EAEA;;UAGe;;;;;;;EAOf;;;;;;;;iBAkIc,2BACd,wBACA,UAAS,wBACR"}
|
package/dist/init.mjs
CHANGED
|
@@ -49,20 +49,23 @@ layers:
|
|
|
49
49
|
ask-user:
|
|
50
50
|
packages:
|
|
51
51
|
- '@juicesharp/rpiv-ask-user-question'
|
|
52
|
-
#
|
|
53
|
-
#
|
|
52
|
+
# A mode is a named set of the layers above. The default keeps its real mode
|
|
53
|
+
# name; it is selected only when neither --major-mode nor DOOMPI_MAJOR_MODE is set.
|
|
54
|
+
defaultMajorMode: copilot
|
|
54
55
|
majorMode:
|
|
55
56
|
minimal: [guardrails, team]
|
|
56
57
|
copilot: [guardrails, vibe-lint, team, plan-mode, runner, style-system, goal, ask-user]
|
|
57
58
|
`,"domains.yaml":`# A domain is a named set of plugin directories loaded for a session.
|
|
58
59
|
# Add your own alongside default; aliases map short names onto them.
|
|
60
|
+
# Defaults are a list because one session may combine several domains.
|
|
61
|
+
defaultDomains: [default]
|
|
59
62
|
#
|
|
60
63
|
# Each plugin path is written in full and is relative to the config that
|
|
61
64
|
# declares it, so it resolves beside this file here (~/.pi/.doom) and against
|
|
62
65
|
# the repository root in a repository copy:
|
|
63
66
|
# plugins: ['plugins/my-plugin']
|
|
64
|
-
# A repository's own .doom/domains.yaml is read after this file
|
|
65
|
-
# domain of the same name.
|
|
67
|
+
# A repository's own .doom/domains.yaml is read after this file, replaces any
|
|
68
|
+
# domain of the same name, and can replace defaultDomains.
|
|
66
69
|
domains:
|
|
67
70
|
default:
|
|
68
71
|
plugins: []
|
package/dist/init.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.mjs","names":[],"sources":["../src/init.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from './config.ts';\n\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\nconst UTF8_ENCODING = 'utf8';\nconst FILE_ALREADY_EXISTS = 'EEXIST';\nconst FILE_NOT_FOUND = 'ENOENT';\n\n/** The global seed files are processed in this fixed order. */\nexport const GLOBAL_DOOM_SEED_FILES = [\n 'config.yaml',\n 'modes.yaml',\n 'domains.yaml',\n 'profiles.yaml',\n 'hooks.yaml',\n] as const;\n\ntype GlobalDoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];\n\n/** Portable defaults for a new global Doom configuration directory. */\nexport const DOOM_CONFIG_TEMPLATES: Readonly<Record<GlobalDoomSeedFile, string>> = {\n 'config.yaml': `projectTrust: ask\n\n# Autonomous voice is opt-in and uses an explicitly configured Pi model.\n# voice:\n# autoCapture:\n# model: provider/model-id\n# startPhrases:\n# - hey doom\n# stopPhrases:\n# - stop speaking\n# tts:\n# engine: macos-say\n# voice: Samantha\n# rate: 190\n`,\n 'modes.yaml': `# Seeded defaults. Every layer here resolves from what Doom Pi installs or\n# from a public package, so a fresh install works with no extra setup.\n#\n# Layers and modes defined here apply to every repository. A repository's own\n# .doom/modes.yaml is read after this file and replaces any layer or mode of the\n# same name, so a repository can override one entry without restating the rest.\n#\n# A layer package is an npm specifier, or a path if it was never published:\n# packages: ['./extensions/my-ext']\n# A path is relative to the config that declares it, so it resolves beside this\n# file here (~/.pi/.doom) and against the repository root in a repository copy.\nlayers:\n guardrails:\n # repositoryHooks ships with Doom Pi, so the layer steers the agent toward\n # the governing skill and scaffolding before it writes.\n extensions: [repositoryHooks]\n hookGroups: [guardrails, scaffolding]\n vibe-lint:\n packages:\n - '@agimon-ai/vibe-lint/extensions/pi'\n team:\n packages:\n - '@agimon-ai/doompi-team/extensions/doom'\n plan-mode:\n packages:\n - '@agimon-ai/doompi-plan/extensions/doom'\n runner:\n packages:\n - '@agimon-ai/doompi-runner/extensions/pi'\n style-system:\n extensions: [styleSystem]\n goal:\n packages:\n - '@narumitw/pi-goal/src/index.ts'\n ask-user:\n packages:\n - '@juicesharp/rpiv-ask-user-question'\n# copilot is the default when --major-mode is not passed. Add your own modes\n# here; a mode is just a named set of the layers above.\nmajorMode:\n minimal: [guardrails, team]\n copilot: [guardrails, vibe-lint, team, plan-mode, runner, style-system, goal, ask-user]\n`,\n 'domains.yaml': `# A domain is a named set of plugin directories loaded for a session.\n# Add your own alongside default; aliases map short names onto them.\n#\n# Each plugin path is written in full and is relative to the config that\n# declares it, so it resolves beside this file here (~/.pi/.doom) and against\n# the repository root in a repository copy:\n# plugins: ['plugins/my-plugin']\n# A repository's own .doom/domains.yaml is read after this file and replaces any\n# domain of the same name.\ndomains:\n default:\n plugins: []\naliases: {}\n`,\n 'profiles.yaml': 'profiles: {}\\n',\n 'hooks.yaml': 'groups: {}\\n',\n};\n\n/** Result of filling the global seed directory. File arrays contain names in seed order. */\nexport interface GlobalDoomInitResult {\n directory: string;\n created: string[];\n preserved: string[];\n /** Existing files overwritten from the template. Only ever non-empty under `force`. */\n replaced: string[];\n}\n\nexport interface GlobalDoomInitOptions {\n /**\n * Overwrite existing seed files instead of preserving them.\n *\n * Off by default: init is safe to re-run, and hand-edited config is the\n * normal case rather than the exception.\n */\n force?: boolean;\n}\n\nfunction errorCode(error: unknown): string | undefined {\n if (!(error instanceof Error)) return undefined;\n const code = (error as NodeJS.ErrnoException).code;\n return typeof code === 'string' ? code : undefined;\n}\n\nfunction ensureDirectory(directory: string): void {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(directory);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n try {\n fs.mkdirSync(directory, { mode: PRIVATE_DIRECTORY_MODE });\n stats = fs.lstatSync(directory);\n } catch (mkdirError) {\n if (errorCode(mkdirError) !== FILE_ALREADY_EXISTS) throw mkdirError;\n stats = fs.lstatSync(directory);\n }\n }\n\n if (stats.isSymbolicLink()) {\n throw new Error(`Refusing to initialize through symlinked directory: ${directory}`);\n }\n if (!stats.isDirectory()) {\n throw new Error(`Doom config path is not a directory: ${directory}`);\n }\n}\n\nfunction ensureGlobalDoomDirectory(directory: string): void {\n ensureDirectory(path.dirname(directory));\n ensureDirectory(directory);\n}\n\nfunction classifyTarget(targetPath: string): 'missing' | 'file' {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(targetPath);\n } catch (error) {\n if (errorCode(error) === FILE_NOT_FOUND) return 'missing';\n throw error;\n }\n\n if (stats.isFile()) return 'file';\n if (stats.isSymbolicLink()) {\n try {\n if (fs.statSync(targetPath).isFile()) return 'file';\n } catch (error) {\n throw new Error(`Doom config seed symlink is not file-like: ${targetPath}`, { cause: error });\n }\n }\n throw new Error(`Doom config seed path is not file-like: ${targetPath}`);\n}\n\nfunction cleanupTemporaryFile(temporaryPath: string): void {\n try {\n fs.unlinkSync(temporaryPath);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n }\n}\n\nfunction publishExclusively(targetPath: string, content: string): boolean {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', PRIVATE_FILE_MODE);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, PRIVATE_FILE_MODE);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n try {\n fs.linkSync(temporaryPath, targetPath);\n return true;\n } catch (error) {\n if (errorCode(error) === FILE_ALREADY_EXISTS && classifyTarget(targetPath) === 'file') return false;\n throw error;\n }\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Replaces a seed file with the template, atomically.\n *\n * The rename swaps the directory entry in one step, so a reader either sees the\n * whole old file or the whole new one. Renaming onto a symlinked seed replaces\n * the link rather than writing through it.\n */\nfunction publishForcefully(targetPath: string, content: string): void {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', PRIVATE_FILE_MODE);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, PRIVATE_FILE_MODE);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n fs.renameSync(temporaryPath, targetPath);\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Fills missing global seed files.\n *\n * Existing files are left alone so that re-running init never discards\n * hand-edited config; pass `force` to overwrite them from the template.\n */\nexport function initializeGlobalDoomConfig(\n homeDirectory = os.homedir(),\n options: GlobalDoomInitOptions = {},\n): GlobalDoomInitResult {\n const directory = globalDoomConfigDirectory(homeDirectory);\n ensureGlobalDoomDirectory(directory);\n\n const created: string[] = [];\n const preserved: string[] = [];\n const replaced: string[] = [];\n for (const fileName of GLOBAL_DOOM_SEED_FILES) {\n const targetPath = path.join(directory, fileName);\n const existed = classifyTarget(targetPath) === 'file';\n\n if (options.force) {\n publishForcefully(targetPath, DOOM_CONFIG_TEMPLATES[fileName]);\n (existed ? replaced : created).push(fileName);\n continue;\n }\n if (existed) {\n preserved.push(fileName);\n continue;\n }\n if (publishExclusively(targetPath, DOOM_CONFIG_TEMPLATES[fileName])) created.push(fileName);\n else preserved.push(fileName);\n }\n\n return { directory, created, preserved, replaced };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"init.mjs","names":[],"sources":["../src/init.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from './config.ts';\n\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\nconst UTF8_ENCODING = 'utf8';\nconst FILE_ALREADY_EXISTS = 'EEXIST';\nconst FILE_NOT_FOUND = 'ENOENT';\n\n/** The global seed files are processed in this fixed order. */\nexport const GLOBAL_DOOM_SEED_FILES = [\n 'config.yaml',\n 'modes.yaml',\n 'domains.yaml',\n 'profiles.yaml',\n 'hooks.yaml',\n] as const;\n\ntype GlobalDoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];\n\n/** Portable defaults for a new global Doom configuration directory. */\nexport const DOOM_CONFIG_TEMPLATES: Readonly<Record<GlobalDoomSeedFile, string>> = {\n 'config.yaml': `projectTrust: ask\n\n# Autonomous voice is opt-in and uses an explicitly configured Pi model.\n# voice:\n# autoCapture:\n# model: provider/model-id\n# startPhrases:\n# - hey doom\n# stopPhrases:\n# - stop speaking\n# tts:\n# engine: macos-say\n# voice: Samantha\n# rate: 190\n`,\n 'modes.yaml': `# Seeded defaults. Every layer here resolves from what Doom Pi installs or\n# from a public package, so a fresh install works with no extra setup.\n#\n# Layers and modes defined here apply to every repository. A repository's own\n# .doom/modes.yaml is read after this file and replaces any layer or mode of the\n# same name, so a repository can override one entry without restating the rest.\n#\n# A layer package is an npm specifier, or a path if it was never published:\n# packages: ['./extensions/my-ext']\n# A path is relative to the config that declares it, so it resolves beside this\n# file here (~/.pi/.doom) and against the repository root in a repository copy.\nlayers:\n guardrails:\n # repositoryHooks ships with Doom Pi, so the layer steers the agent toward\n # the governing skill and scaffolding before it writes.\n extensions: [repositoryHooks]\n hookGroups: [guardrails, scaffolding]\n vibe-lint:\n packages:\n - '@agimon-ai/vibe-lint/extensions/pi'\n team:\n packages:\n - '@agimon-ai/doompi-team/extensions/doom'\n plan-mode:\n packages:\n - '@agimon-ai/doompi-plan/extensions/doom'\n runner:\n packages:\n - '@agimon-ai/doompi-runner/extensions/pi'\n style-system:\n extensions: [styleSystem]\n goal:\n packages:\n - '@narumitw/pi-goal/src/index.ts'\n ask-user:\n packages:\n - '@juicesharp/rpiv-ask-user-question'\n# A mode is a named set of the layers above. The default keeps its real mode\n# name; it is selected only when neither --major-mode nor DOOMPI_MAJOR_MODE is set.\ndefaultMajorMode: copilot\nmajorMode:\n minimal: [guardrails, team]\n copilot: [guardrails, vibe-lint, team, plan-mode, runner, style-system, goal, ask-user]\n`,\n 'domains.yaml': `# A domain is a named set of plugin directories loaded for a session.\n# Add your own alongside default; aliases map short names onto them.\n# Defaults are a list because one session may combine several domains.\ndefaultDomains: [default]\n#\n# Each plugin path is written in full and is relative to the config that\n# declares it, so it resolves beside this file here (~/.pi/.doom) and against\n# the repository root in a repository copy:\n# plugins: ['plugins/my-plugin']\n# A repository's own .doom/domains.yaml is read after this file, replaces any\n# domain of the same name, and can replace defaultDomains.\ndomains:\n default:\n plugins: []\naliases: {}\n`,\n 'profiles.yaml': 'profiles: {}\\n',\n 'hooks.yaml': 'groups: {}\\n',\n};\n\n/** Result of filling the global seed directory. File arrays contain names in seed order. */\nexport interface GlobalDoomInitResult {\n directory: string;\n created: string[];\n preserved: string[];\n /** Existing files overwritten from the template. Only ever non-empty under `force`. */\n replaced: string[];\n}\n\nexport interface GlobalDoomInitOptions {\n /**\n * Overwrite existing seed files instead of preserving them.\n *\n * Off by default: init is safe to re-run, and hand-edited config is the\n * normal case rather than the exception.\n */\n force?: boolean;\n}\n\nfunction errorCode(error: unknown): string | undefined {\n if (!(error instanceof Error)) return undefined;\n const code = (error as NodeJS.ErrnoException).code;\n return typeof code === 'string' ? code : undefined;\n}\n\nfunction ensureDirectory(directory: string): void {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(directory);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n try {\n fs.mkdirSync(directory, { mode: PRIVATE_DIRECTORY_MODE });\n stats = fs.lstatSync(directory);\n } catch (mkdirError) {\n if (errorCode(mkdirError) !== FILE_ALREADY_EXISTS) throw mkdirError;\n stats = fs.lstatSync(directory);\n }\n }\n\n if (stats.isSymbolicLink()) {\n throw new Error(`Refusing to initialize through symlinked directory: ${directory}`);\n }\n if (!stats.isDirectory()) {\n throw new Error(`Doom config path is not a directory: ${directory}`);\n }\n}\n\nfunction ensureGlobalDoomDirectory(directory: string): void {\n ensureDirectory(path.dirname(directory));\n ensureDirectory(directory);\n}\n\nfunction classifyTarget(targetPath: string): 'missing' | 'file' {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(targetPath);\n } catch (error) {\n if (errorCode(error) === FILE_NOT_FOUND) return 'missing';\n throw error;\n }\n\n if (stats.isFile()) return 'file';\n if (stats.isSymbolicLink()) {\n try {\n if (fs.statSync(targetPath).isFile()) return 'file';\n } catch (error) {\n throw new Error(`Doom config seed symlink is not file-like: ${targetPath}`, { cause: error });\n }\n }\n throw new Error(`Doom config seed path is not file-like: ${targetPath}`);\n}\n\nfunction cleanupTemporaryFile(temporaryPath: string): void {\n try {\n fs.unlinkSync(temporaryPath);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n }\n}\n\nfunction publishExclusively(targetPath: string, content: string): boolean {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', PRIVATE_FILE_MODE);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, PRIVATE_FILE_MODE);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n try {\n fs.linkSync(temporaryPath, targetPath);\n return true;\n } catch (error) {\n if (errorCode(error) === FILE_ALREADY_EXISTS && classifyTarget(targetPath) === 'file') return false;\n throw error;\n }\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Replaces a seed file with the template, atomically.\n *\n * The rename swaps the directory entry in one step, so a reader either sees the\n * whole old file or the whole new one. Renaming onto a symlinked seed replaces\n * the link rather than writing through it.\n */\nfunction publishForcefully(targetPath: string, content: string): void {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', PRIVATE_FILE_MODE);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, PRIVATE_FILE_MODE);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n fs.renameSync(temporaryPath, targetPath);\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Fills missing global seed files.\n *\n * Existing files are left alone so that re-running init never discards\n * hand-edited config; pass `force` to overwrite them from the template.\n */\nexport function initializeGlobalDoomConfig(\n homeDirectory = os.homedir(),\n options: GlobalDoomInitOptions = {},\n): GlobalDoomInitResult {\n const directory = globalDoomConfigDirectory(homeDirectory);\n ensureGlobalDoomDirectory(directory);\n\n const created: string[] = [];\n const preserved: string[] = [];\n const replaced: string[] = [];\n for (const fileName of GLOBAL_DOOM_SEED_FILES) {\n const targetPath = path.join(directory, fileName);\n const existed = classifyTarget(targetPath) === 'file';\n\n if (options.force) {\n publishForcefully(targetPath, DOOM_CONFIG_TEMPLATES[fileName]);\n (existed ? replaced : created).push(fileName);\n continue;\n }\n if (existed) {\n preserved.push(fileName);\n continue;\n }\n if (publishExclusively(targetPath, DOOM_CONFIG_TEMPLATES[fileName])) created.push(fileName);\n else preserved.push(fileName);\n }\n\n return { directory, created, preserved, replaced };\n}\n"],"mappings":"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"}
|
package/dist/majorModes.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./layeredConfig.cjs");let n=require("node:os");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);const i=`modes.yaml`,a=r.default.join(t.DOOM_DIR,i),o=[`./`,`../`];function s(e){return r.default.isAbsolute(e)||o.some(t=>e.startsWith(t))}const c=[`subagents`],l=[`models`,`excludeTools`],u=[`model`,`thinking`],d=new Set([`off`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`]);function f(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function p(e,t,n){let r=Object.keys(e).filter(e=>!t.includes(e));if(r.length>0)throw Error(`${n} has unsupported field(s): ${r.join(`, `)}`)}function m(e,t){if(t===void 0)return;let n=`Layer "${e}" config in ${a}`;if(!f(t))throw Error(`${n} must be a mapping`);if(p(t,c,n),t.subagents===void 0)return{};if(!f(t.subagents))throw Error(`${n}.subagents must be a mapping`);p(t.subagents,l,`${n}.subagents`);let r=Object.hasOwn(t.subagents,`models`),i=Object.hasOwn(t.subagents,`excludeTools`);if(!r&&!i)throw Error(`${n}.subagents must define models or excludeTools`);let o;if(r){if(!Array.isArray(t.subagents.models)||t.subagents.models.length===0)throw Error(`${n}.subagents.models must be a non-empty array`);o=t.subagents.models.map((e,t)=>{let r=`${n}.subagents.models[${t}]`;if(!f(e))throw Error(`${r} must be a mapping`);if(p(e,u,r),typeof e.model!=`string`||!e.model.trim())throw Error(`${r}.model must be a non-empty string`);if(e.thinking!==void 0&&(typeof e.thinking!=`string`||!d.has(e.thinking)))throw Error(`${r}.thinking must be one of: ${[...d].join(`, `)}`);return{model:e.model.trim(),...e.thinking?{thinking:e.thinking}:{}}})}let s;if(i){let e=t.subagents.excludeTools;if(!Array.isArray(e)||e.length===0)throw Error(`${n}.subagents.excludeTools must be a non-empty array`);s=[...new Set(e.map((e,t)=>{let r=`${n}.subagents.excludeTools[${t}]`;if(typeof e!=`string`||!e.trim())throw Error(`${r} must be a non-empty string`);return e.trim()}))]}return{subagents:{...o?{models:o}:{},...s?{excludeTools:s}:{}}}}function h(e,t){if(!f(t))throw Error(`Layer "${e}" in ${a} must be a mapping`);if(`targets`in t)throw Error(`Layer "${e}" in ${a} uses removed field "targets"; select availability through major modes`);let n=m(e,t.config);return n===void 0?t:{...t,config:n}}function
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./layeredConfig.cjs");let n=require("node:os");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);const i=`modes.yaml`,a=r.default.join(t.DOOM_DIR,i),o=[`./`,`../`];function s(e){return r.default.isAbsolute(e)||o.some(t=>e.startsWith(t))}const c=[`subagents`],l=[`models`,`excludeTools`],u=[`model`,`thinking`],d=new Set([`off`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`]);function f(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function p(e,t,n){let r=Object.keys(e).filter(e=>!t.includes(e));if(r.length>0)throw Error(`${n} has unsupported field(s): ${r.join(`, `)}`)}function m(e,t){if(t===void 0)return;let n=`Layer "${e}" config in ${a}`;if(!f(t))throw Error(`${n} must be a mapping`);if(p(t,c,n),t.subagents===void 0)return{};if(!f(t.subagents))throw Error(`${n}.subagents must be a mapping`);p(t.subagents,l,`${n}.subagents`);let r=Object.hasOwn(t.subagents,`models`),i=Object.hasOwn(t.subagents,`excludeTools`);if(!r&&!i)throw Error(`${n}.subagents must define models or excludeTools`);let o;if(r){if(!Array.isArray(t.subagents.models)||t.subagents.models.length===0)throw Error(`${n}.subagents.models must be a non-empty array`);o=t.subagents.models.map((e,t)=>{let r=`${n}.subagents.models[${t}]`;if(!f(e))throw Error(`${r} must be a mapping`);if(p(e,u,r),typeof e.model!=`string`||!e.model.trim())throw Error(`${r}.model must be a non-empty string`);if(e.thinking!==void 0&&(typeof e.thinking!=`string`||!d.has(e.thinking)))throw Error(`${r}.thinking must be one of: ${[...d].join(`, `)}`);return{model:e.model.trim(),...e.thinking?{thinking:e.thinking}:{}}})}let s;if(i){let e=t.subagents.excludeTools;if(!Array.isArray(e)||e.length===0)throw Error(`${n}.subagents.excludeTools must be a non-empty array`);s=[...new Set(e.map((e,t)=>{let r=`${n}.subagents.excludeTools[${t}]`;if(typeof e!=`string`||!e.trim())throw Error(`${r} must be a non-empty string`);return e.trim()}))]}return{subagents:{...o?{models:o}:{},...s?{excludeTools:s}:{}}}}function h(e){if(typeof e!=`string`||!e.trim())throw Error(`defaultMajorMode in ${a} must be a non-empty string`);return e.trim()}function g(e,t){if(!f(t))throw Error(`Layer "${e}" in ${a} must be a mapping`);if(`targets`in t)throw Error(`Layer "${e}" in ${a} uses removed field "targets"; select availability through major modes`);let n=m(e,t.config);return n===void 0?t:{...t,config:n}}function _(e,r=n.default.homedir()){let o=t.readDoomConfigSources(i,e,r),s={},c={},l=`copilot`,u=!1;for(let e of o){for(let[t,n]of Object.entries(e.document.layers??{}))s[t]={...g(t,n),baseDirectory:e.baseDirectory};Object.hasOwn(e.document,`defaultMajorMode`)&&(l=h(e.document.defaultMajorMode),u=!0),Object.assign(c,e.document.majorMode??{})}for(let[e,t]of Object.entries(c))for(let n of t)if(!s[n])throw Error(`Unknown layer "${n}" in majorMode.${e} of ${a}`);if(u&&!Object.hasOwn(c,l))throw Error(`Unknown default major mode "${l}" in ${a}`);return{layers:s,defaultMajorMode:l,majorMode:c}}function v(e,t){let n,r=new Set,i=!1;for(let a of t){let t=e.layers[a];if(!t)throw Error(`Unknown layer: ${a}`);let o=t.config?.subagents;if(o){if(o.excludeTools!==void 0){i=!0;for(let e of o.excludeTools)r.add(e)}n={...n,...o.models?{models:o.models.map(e=>({...e}))}:{},...i?{excludeTools:[...r]}:{}}}}return n?{subagents:n}:{}}function y(e,t){if(!Object.hasOwn(e.majorMode,t))throw Error(`Unknown major mode: ${t}. Known major modes: ${Object.keys(e.majorMode).sort().join(`, `)}`);return[...e.majorMode[t]]}function b(e,t,n){let r=e.layers[t];if(!r)throw Error(`Unknown layer: ${t}`);let i=[];for(let e of r.extensions??[])i.push(n.ownEntry(e));for(let e of r.packages??[]){let a=typeof e==`string`?e:e.name,o=typeof e!=`string`&&e.optional===!0;if(s(a)){let e=n.localEntry(a,r.baseDirectory);if(e)i.push(e);else if(!o)throw Error(`Layer "${t}" package "${a}" does not resolve under ${r.baseDirectory}. Point it at an extension file, or a directory whose package.json names one.`);continue}if(o){let e=n.optionalPackageEntry(a);e&&i.push(e);continue}i.push(n.packageEntry(a))}return i}function x(e,t){let n=new Set;for(let r of t)for(let t of e.layers[r]?.hookGroups??[])n.add(t);return[...n]}exports.isLocalPackageSpecifier=s,exports.layerEntries=b,exports.layerHookGroups=x,exports.loadMajorModesConfig=_,exports.resolveLayerCustomization=v,exports.resolveLayers=y;
|
|
2
2
|
//# sourceMappingURL=majorModes.cjs.map
|
package/dist/majorModes.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"majorModes.cjs","names":["path","DOOM_DIR","os","readDoomConfigSources"],"sources":["../src/majorModes.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\nimport { DOOM_DIR, readDoomConfigSources } from './layeredConfig.ts';\nimport type { PlanningThinkingLevel } from './types.ts';\n\nexport interface LayerPackage {\n name: string;\n optional?: boolean;\n}\n\nexport interface SubagentLayerModelConfig {\n model: string;\n thinking?: PlanningThinkingLevel;\n}\n\nexport interface SubagentLayerConfig {\n models?: SubagentLayerModelConfig[];\n excludeTools?: string[];\n}\n\nexport interface LayerCustomizationConfig {\n subagents?: SubagentLayerConfig;\n}\n\nexport interface LayerDefinition {\n extensions?: string[];\n /**\n * npm specifiers, or paths relative to the config that declares the layer.\n * A path (`./`, `../`, or absolute) is resolved against the repository root\n * for `.doom/modes.yaml` and against the global `.doom` directory for\n * `~/.pi/.doom/modes.yaml`, which is how an extension that was never\n * published to npm gets loaded.\n */\n packages?: Array<string | LayerPackage>;\n hookGroups?: string[];\n config?: LayerCustomizationConfig;\n}\n\n/** A layer plus the root its relative package paths resolve against. */\nexport interface ResolvedLayerDefinition extends LayerDefinition {\n baseDirectory: string;\n}\n\nexport interface MajorModesConfig {\n layers: Record<string, ResolvedLayerDefinition>;\n /** Named major modes, each one a set of layer names. */\n majorMode: Record<string, string[]>;\n}\n\nexport interface LayerResolvers {\n ownEntry: (name: string) => string;\n packageEntry: (name: string) => string;\n optionalPackageEntry: (name: string) => string | undefined;\n /** Resolves a path-style layer package against the config that declared it. */\n localEntry: (specifier: string, baseDirectory: string) => string | undefined;\n}\n\ninterface ModesDocument {\n layers?: Record<string, unknown>;\n majorMode?: Record<string, string[]>;\n}\n\nconst MODES_FILE = 'modes.yaml';\nconst MAJOR_MODES_RELATIVE_PATH = path.join(DOOM_DIR, MODES_FILE);\nconst RELATIVE_PREFIXES = ['./', '../'] as const;\n\n/**\n * Whether a layer package names a path rather than an installed package.\n *\n * The same rule Node applies to import specifiers, so a bare name is always an\n * npm package and a path always looks like one.\n */\nexport function isLocalPackageSpecifier(specifier: string): boolean {\n return path.isAbsolute(specifier) || RELATIVE_PREFIXES.some((prefix) => specifier.startsWith(prefix));\n}\nconst LAYER_CONFIG_KEYS = ['subagents'] as const;\nconst SUBAGENT_CONFIG_KEYS = ['models', 'excludeTools'] as const;\nconst SUBAGENT_MODEL_KEYS = ['model', 'thinking'] as const;\nconst THINKING_VALUES = new Set<PlanningThinkingLevel>(['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction assertKnownKeys(value: Record<string, unknown>, keys: readonly string[], location: string): void {\n const unsupported = Object.keys(value).filter((key) => !keys.includes(key));\n if (unsupported.length > 0) {\n throw new Error(`${location} has unsupported field(s): ${unsupported.join(', ')}`);\n }\n}\n\nfunction parseLayerCustomization(name: string, value: unknown): LayerCustomizationConfig | undefined {\n if (value === undefined) return undefined;\n const location = `Layer \"${name}\" config in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!isRecord(value)) throw new Error(`${location} must be a mapping`);\n assertKnownKeys(value, LAYER_CONFIG_KEYS, location);\n if (value.subagents === undefined) return {};\n if (!isRecord(value.subagents)) throw new Error(`${location}.subagents must be a mapping`);\n assertKnownKeys(value.subagents, SUBAGENT_CONFIG_KEYS, `${location}.subagents`);\n const hasModels = Object.hasOwn(value.subagents, 'models');\n const hasExcludeTools = Object.hasOwn(value.subagents, 'excludeTools');\n if (!hasModels && !hasExcludeTools) {\n throw new Error(`${location}.subagents must define models or excludeTools`);\n }\n\n let models: SubagentLayerModelConfig[] | undefined;\n if (hasModels) {\n if (!Array.isArray(value.subagents.models) || value.subagents.models.length === 0) {\n throw new Error(`${location}.subagents.models must be a non-empty array`);\n }\n models = value.subagents.models.map((entry, index): SubagentLayerModelConfig => {\n const entryLocation = `${location}.subagents.models[${index}]`;\n if (!isRecord(entry)) throw new Error(`${entryLocation} must be a mapping`);\n assertKnownKeys(entry, SUBAGENT_MODEL_KEYS, entryLocation);\n if (typeof entry.model !== 'string' || !entry.model.trim()) {\n throw new Error(`${entryLocation}.model must be a non-empty string`);\n }\n if (\n entry.thinking !== undefined &&\n (typeof entry.thinking !== 'string' || !THINKING_VALUES.has(entry.thinking as PlanningThinkingLevel))\n ) {\n throw new Error(`${entryLocation}.thinking must be one of: ${[...THINKING_VALUES].join(', ')}`);\n }\n return {\n model: entry.model.trim(),\n ...(entry.thinking ? { thinking: entry.thinking as PlanningThinkingLevel } : {}),\n };\n });\n }\n\n let normalizedExcludeTools: string[] | undefined;\n if (hasExcludeTools) {\n const excludeTools = value.subagents.excludeTools;\n if (!Array.isArray(excludeTools) || excludeTools.length === 0) {\n throw new Error(`${location}.subagents.excludeTools must be a non-empty array`);\n }\n normalizedExcludeTools = [\n ...new Set(\n excludeTools.map((entry, index) => {\n const entryLocation = `${location}.subagents.excludeTools[${index}]`;\n if (typeof entry !== 'string' || !entry.trim()) {\n throw new Error(`${entryLocation} must be a non-empty string`);\n }\n return entry.trim();\n }),\n ),\n ];\n }\n\n return {\n subagents: {\n ...(models ? { models } : {}),\n ...(normalizedExcludeTools ? { excludeTools: normalizedExcludeTools } : {}),\n },\n };\n}\n\nfunction parseLayer(name: string, definition: unknown): LayerDefinition {\n if (!isRecord(definition)) throw new Error(`Layer \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH} must be a mapping`);\n if ('targets' in definition) {\n throw new Error(\n `Layer \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH} uses removed field \"targets\"; select availability through major modes`,\n );\n }\n const config = parseLayerCustomization(name, definition.config);\n return (config === undefined ? definition : { ...definition, config }) as LayerDefinition;\n}\n\n/**\n * Reads layers and major modes from the global and repository configs.\n *\n * The repository is read last, so it replaces a global layer or major mode of\n * the same name. Layers keep the root they were declared under so that a\n * global layer naming `./extensions/x` still points beside the global config\n * once it is loaded from inside some other repository.\n */\nexport function loadMajorModesConfig(repoRoot: string, homeDirectory: string = os.homedir()): MajorModesConfig {\n const sources = readDoomConfigSources<ModesDocument>(MODES_FILE, repoRoot, homeDirectory);\n const layers: Record<string, ResolvedLayerDefinition> = {};\n const majorMode: Record<string, string[]> = {};\n for (const source of sources) {\n for (const [name, definition] of Object.entries(source.document.layers ?? {})) {\n layers[name] = { ...parseLayer(name, definition), baseDirectory: source.baseDirectory };\n }\n Object.assign(majorMode, source.document.majorMode ?? {});\n }\n\n for (const [modeName, names] of Object.entries(majorMode)) {\n for (const name of names) {\n if (!layers[name]) {\n throw new Error(`Unknown layer \"${name}\" in majorMode.${modeName} of ${MAJOR_MODES_RELATIVE_PATH}`);\n }\n }\n }\n return { layers, majorMode };\n}\n\n/** Merges selected layer customization in order, with later models winning and exclusions unioned. */\nexport function resolveLayerCustomization(config: MajorModesConfig, names: string[]): LayerCustomizationConfig {\n let subagents: SubagentLayerConfig | undefined;\n const excludeTools = new Set<string>();\n let hasExcludeTools = false;\n for (const name of names) {\n const layer = config.layers[name];\n if (!layer) throw new Error(`Unknown layer: ${name}`);\n const next = layer.config?.subagents;\n if (!next) continue;\n if (next.excludeTools !== undefined) {\n hasExcludeTools = true;\n for (const tool of next.excludeTools) excludeTools.add(tool);\n }\n subagents = {\n ...subagents,\n ...(next.models ? { models: next.models.map((model) => ({ ...model })) } : {}),\n ...(hasExcludeTools ? { excludeTools: [...excludeTools] } : {}),\n };\n }\n return subagents ? { subagents } : {};\n}\n\n/** Resolves one named major mode into its configured layer components. */\nexport function resolveLayers(config: MajorModesConfig, majorMode: string): string[] {\n if (!Object.hasOwn(config.majorMode, majorMode)) {\n throw new Error(\n `Unknown major mode: ${majorMode}. Known major modes: ${Object.keys(config.majorMode).sort().join(', ')}`,\n );\n }\n return [...config.majorMode[majorMode]!];\n}\n\n/** Resolves one layer's extension and package entries to absolute paths. */\nexport function layerEntries(config: MajorModesConfig, name: string, resolvers: LayerResolvers): string[] {\n const layer = config.layers[name];\n if (!layer) throw new Error(`Unknown layer: ${name}`);\n const entries: string[] = [];\n for (const entry of layer.extensions ?? []) entries.push(resolvers.ownEntry(entry));\n for (const entry of layer.packages ?? []) {\n const specifier = typeof entry === 'string' ? entry : entry.name;\n const optional = typeof entry === 'string' ? false : entry.optional === true;\n if (isLocalPackageSpecifier(specifier)) {\n const resolved = resolvers.localEntry(specifier, layer.baseDirectory);\n if (resolved) entries.push(resolved);\n else if (!optional) {\n throw new Error(\n `Layer \"${name}\" package \"${specifier}\" does not resolve under ${layer.baseDirectory}. ` +\n 'Point it at an extension file, or a directory whose package.json names one.',\n );\n }\n continue;\n }\n if (optional) {\n const resolved = resolvers.optionalPackageEntry(specifier);\n if (resolved) entries.push(resolved);\n continue;\n }\n entries.push(resolvers.packageEntry(specifier));\n }\n return entries;\n}\n\n/** Hook group ids contributed by the selected layers, deduped. */\nexport function layerHookGroups(config: MajorModesConfig, names: string[]): string[] {\n const groups = new Set<string>();\n for (const name of names) {\n for (const group of config.layers[name]?.hookGroups ?? []) groups.add(group);\n }\n return [...groups];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"majorModes.cjs","names":["path","DOOM_DIR","os","readDoomConfigSources"],"sources":["../src/majorModes.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\nimport { DOOM_DIR, readDoomConfigSources } from './layeredConfig.ts';\nimport type { PlanningThinkingLevel } from './types.ts';\n\nexport interface LayerPackage {\n name: string;\n optional?: boolean;\n}\n\nexport interface SubagentLayerModelConfig {\n model: string;\n thinking?: PlanningThinkingLevel;\n}\n\nexport interface SubagentLayerConfig {\n models?: SubagentLayerModelConfig[];\n excludeTools?: string[];\n}\n\nexport interface LayerCustomizationConfig {\n subagents?: SubagentLayerConfig;\n}\n\nexport interface LayerDefinition {\n extensions?: string[];\n /**\n * npm specifiers, or paths relative to the config that declares the layer.\n * A path (`./`, `../`, or absolute) is resolved against the repository root\n * for `.doom/modes.yaml` and against the global `.doom` directory for\n * `~/.pi/.doom/modes.yaml`, which is how an extension that was never\n * published to npm gets loaded.\n */\n packages?: Array<string | LayerPackage>;\n hookGroups?: string[];\n config?: LayerCustomizationConfig;\n}\n\n/** A layer plus the root its relative package paths resolve against. */\nexport interface ResolvedLayerDefinition extends LayerDefinition {\n baseDirectory: string;\n}\n\nexport interface MajorModesConfig {\n layers: Record<string, ResolvedLayerDefinition>;\n /** The mode selected when neither a flag nor the environment names one. */\n defaultMajorMode: string;\n /** Named major modes, each one a set of layer names. */\n majorMode: Record<string, string[]>;\n}\n\nexport interface LayerResolvers {\n ownEntry: (name: string) => string;\n packageEntry: (name: string) => string;\n optionalPackageEntry: (name: string) => string | undefined;\n /** Resolves a path-style layer package against the config that declared it. */\n localEntry: (specifier: string, baseDirectory: string) => string | undefined;\n}\n\ninterface ModesDocument {\n layers?: Record<string, unknown>;\n defaultMajorMode?: unknown;\n majorMode?: Record<string, string[]>;\n}\n\nconst MODES_FILE = 'modes.yaml';\nconst FALLBACK_MAJOR_MODE = 'copilot';\nconst MAJOR_MODES_RELATIVE_PATH = path.join(DOOM_DIR, MODES_FILE);\nconst RELATIVE_PREFIXES = ['./', '../'] as const;\n\n/**\n * Whether a layer package names a path rather than an installed package.\n *\n * The same rule Node applies to import specifiers, so a bare name is always an\n * npm package and a path always looks like one.\n */\nexport function isLocalPackageSpecifier(specifier: string): boolean {\n return path.isAbsolute(specifier) || RELATIVE_PREFIXES.some((prefix) => specifier.startsWith(prefix));\n}\nconst LAYER_CONFIG_KEYS = ['subagents'] as const;\nconst SUBAGENT_CONFIG_KEYS = ['models', 'excludeTools'] as const;\nconst SUBAGENT_MODEL_KEYS = ['model', 'thinking'] as const;\nconst THINKING_VALUES = new Set<PlanningThinkingLevel>(['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction assertKnownKeys(value: Record<string, unknown>, keys: readonly string[], location: string): void {\n const unsupported = Object.keys(value).filter((key) => !keys.includes(key));\n if (unsupported.length > 0) {\n throw new Error(`${location} has unsupported field(s): ${unsupported.join(', ')}`);\n }\n}\n\nfunction parseLayerCustomization(name: string, value: unknown): LayerCustomizationConfig | undefined {\n if (value === undefined) return undefined;\n const location = `Layer \"${name}\" config in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!isRecord(value)) throw new Error(`${location} must be a mapping`);\n assertKnownKeys(value, LAYER_CONFIG_KEYS, location);\n if (value.subagents === undefined) return {};\n if (!isRecord(value.subagents)) throw new Error(`${location}.subagents must be a mapping`);\n assertKnownKeys(value.subagents, SUBAGENT_CONFIG_KEYS, `${location}.subagents`);\n const hasModels = Object.hasOwn(value.subagents, 'models');\n const hasExcludeTools = Object.hasOwn(value.subagents, 'excludeTools');\n if (!hasModels && !hasExcludeTools) {\n throw new Error(`${location}.subagents must define models or excludeTools`);\n }\n\n let models: SubagentLayerModelConfig[] | undefined;\n if (hasModels) {\n if (!Array.isArray(value.subagents.models) || value.subagents.models.length === 0) {\n throw new Error(`${location}.subagents.models must be a non-empty array`);\n }\n models = value.subagents.models.map((entry, index): SubagentLayerModelConfig => {\n const entryLocation = `${location}.subagents.models[${index}]`;\n if (!isRecord(entry)) throw new Error(`${entryLocation} must be a mapping`);\n assertKnownKeys(entry, SUBAGENT_MODEL_KEYS, entryLocation);\n if (typeof entry.model !== 'string' || !entry.model.trim()) {\n throw new Error(`${entryLocation}.model must be a non-empty string`);\n }\n if (\n entry.thinking !== undefined &&\n (typeof entry.thinking !== 'string' || !THINKING_VALUES.has(entry.thinking as PlanningThinkingLevel))\n ) {\n throw new Error(`${entryLocation}.thinking must be one of: ${[...THINKING_VALUES].join(', ')}`);\n }\n return {\n model: entry.model.trim(),\n ...(entry.thinking ? { thinking: entry.thinking as PlanningThinkingLevel } : {}),\n };\n });\n }\n\n let normalizedExcludeTools: string[] | undefined;\n if (hasExcludeTools) {\n const excludeTools = value.subagents.excludeTools;\n if (!Array.isArray(excludeTools) || excludeTools.length === 0) {\n throw new Error(`${location}.subagents.excludeTools must be a non-empty array`);\n }\n normalizedExcludeTools = [\n ...new Set(\n excludeTools.map((entry, index) => {\n const entryLocation = `${location}.subagents.excludeTools[${index}]`;\n if (typeof entry !== 'string' || !entry.trim()) {\n throw new Error(`${entryLocation} must be a non-empty string`);\n }\n return entry.trim();\n }),\n ),\n ];\n }\n\n return {\n subagents: {\n ...(models ? { models } : {}),\n ...(normalizedExcludeTools ? { excludeTools: normalizedExcludeTools } : {}),\n },\n };\n}\n\nfunction parseDefaultMajorMode(value: unknown): string {\n if (typeof value !== 'string' || !value.trim()) {\n throw new Error(`defaultMajorMode in ${MAJOR_MODES_RELATIVE_PATH} must be a non-empty string`);\n }\n return value.trim();\n}\n\nfunction parseLayer(name: string, definition: unknown): LayerDefinition {\n if (!isRecord(definition)) throw new Error(`Layer \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH} must be a mapping`);\n if ('targets' in definition) {\n throw new Error(\n `Layer \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH} uses removed field \"targets\"; select availability through major modes`,\n );\n }\n const config = parseLayerCustomization(name, definition.config);\n return (config === undefined ? definition : { ...definition, config }) as LayerDefinition;\n}\n\n/**\n * Reads layers and major modes from the global and repository configs.\n *\n * The repository is read last, so it replaces a global layer or major mode of\n * the same name. Layers keep the root they were declared under so that a\n * global layer naming `./extensions/x` still points beside the global config\n * once it is loaded from inside some other repository.\n */\nexport function loadMajorModesConfig(repoRoot: string, homeDirectory: string = os.homedir()): MajorModesConfig {\n const sources = readDoomConfigSources<ModesDocument>(MODES_FILE, repoRoot, homeDirectory);\n const layers: Record<string, ResolvedLayerDefinition> = {};\n const majorMode: Record<string, string[]> = {};\n let defaultMajorMode = FALLBACK_MAJOR_MODE;\n let hasConfiguredDefault = false;\n for (const source of sources) {\n for (const [name, definition] of Object.entries(source.document.layers ?? {})) {\n layers[name] = { ...parseLayer(name, definition), baseDirectory: source.baseDirectory };\n }\n if (Object.hasOwn(source.document, 'defaultMajorMode')) {\n defaultMajorMode = parseDefaultMajorMode(source.document.defaultMajorMode);\n hasConfiguredDefault = true;\n }\n Object.assign(majorMode, source.document.majorMode ?? {});\n }\n\n for (const [modeName, names] of Object.entries(majorMode)) {\n for (const name of names) {\n if (!layers[name]) {\n throw new Error(`Unknown layer \"${name}\" in majorMode.${modeName} of ${MAJOR_MODES_RELATIVE_PATH}`);\n }\n }\n }\n if (hasConfiguredDefault && !Object.hasOwn(majorMode, defaultMajorMode)) {\n throw new Error(`Unknown default major mode \"${defaultMajorMode}\" in ${MAJOR_MODES_RELATIVE_PATH}`);\n }\n return { layers, defaultMajorMode, majorMode };\n}\n\n/** Merges selected layer customization in order, with later models winning and exclusions unioned. */\nexport function resolveLayerCustomization(config: MajorModesConfig, names: string[]): LayerCustomizationConfig {\n let subagents: SubagentLayerConfig | undefined;\n const excludeTools = new Set<string>();\n let hasExcludeTools = false;\n for (const name of names) {\n const layer = config.layers[name];\n if (!layer) throw new Error(`Unknown layer: ${name}`);\n const next = layer.config?.subagents;\n if (!next) continue;\n if (next.excludeTools !== undefined) {\n hasExcludeTools = true;\n for (const tool of next.excludeTools) excludeTools.add(tool);\n }\n subagents = {\n ...subagents,\n ...(next.models ? { models: next.models.map((model) => ({ ...model })) } : {}),\n ...(hasExcludeTools ? { excludeTools: [...excludeTools] } : {}),\n };\n }\n return subagents ? { subagents } : {};\n}\n\n/** Resolves one named major mode into its configured layer components. */\nexport function resolveLayers(config: MajorModesConfig, majorMode: string): string[] {\n if (!Object.hasOwn(config.majorMode, majorMode)) {\n throw new Error(\n `Unknown major mode: ${majorMode}. Known major modes: ${Object.keys(config.majorMode).sort().join(', ')}`,\n );\n }\n return [...config.majorMode[majorMode]!];\n}\n\n/** Resolves one layer's extension and package entries to absolute paths. */\nexport function layerEntries(config: MajorModesConfig, name: string, resolvers: LayerResolvers): string[] {\n const layer = config.layers[name];\n if (!layer) throw new Error(`Unknown layer: ${name}`);\n const entries: string[] = [];\n for (const entry of layer.extensions ?? []) entries.push(resolvers.ownEntry(entry));\n for (const entry of layer.packages ?? []) {\n const specifier = typeof entry === 'string' ? entry : entry.name;\n const optional = typeof entry === 'string' ? false : entry.optional === true;\n if (isLocalPackageSpecifier(specifier)) {\n const resolved = resolvers.localEntry(specifier, layer.baseDirectory);\n if (resolved) entries.push(resolved);\n else if (!optional) {\n throw new Error(\n `Layer \"${name}\" package \"${specifier}\" does not resolve under ${layer.baseDirectory}. ` +\n 'Point it at an extension file, or a directory whose package.json names one.',\n );\n }\n continue;\n }\n if (optional) {\n const resolved = resolvers.optionalPackageEntry(specifier);\n if (resolved) entries.push(resolved);\n continue;\n }\n entries.push(resolvers.packageEntry(specifier));\n }\n return entries;\n}\n\n/** Hook group ids contributed by the selected layers, deduped. */\nexport function layerHookGroups(config: MajorModesConfig, names: string[]): string[] {\n const groups = new Set<string>();\n for (const name of names) {\n for (const group of config.layers[name]?.hookGroups ?? []) groups.add(group);\n }\n return [...groups];\n}\n"],"mappings":"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"}
|
package/dist/majorModes.d.cts
CHANGED
|
@@ -34,6 +34,8 @@ interface ResolvedLayerDefinition extends LayerDefinition {
|
|
|
34
34
|
}
|
|
35
35
|
interface MajorModesConfig {
|
|
36
36
|
layers: Record<string, ResolvedLayerDefinition>;
|
|
37
|
+
/** The mode selected when neither a flag nor the environment names one. */
|
|
38
|
+
defaultMajorMode: string;
|
|
37
39
|
/** Named major modes, each one a set of layer names. */
|
|
38
40
|
majorMode: Record<string, string[]>;
|
|
39
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"majorModes.d.cts","names":[],"sources":["../src/majorModes.ts"],"mappings":";;UAKiB;EACf;EACA;;UAGe;EACf;EACA,WAAW;;UAGI;EACf,SAAS;EACT;;UAGe;EACf,YAAY;;UAGG;EACf;;;;;;;;EAQA,WAAW,eAAe;EAC1B;EACA,SAAS;;;UAIM,gCAAgC;EAC/C;;UAGe;EACf,QAAQ,eAAe;;EAEvB,WAAW;;UAGI;EACf,WAAW;EACX,eAAe;EACf,uBAAuB;;EAEvB,aAAa,mBAAmB;;;;;;;;
|
|
1
|
+
{"version":3,"file":"majorModes.d.cts","names":[],"sources":["../src/majorModes.ts"],"mappings":";;UAKiB;EACf;EACA;;UAGe;EACf;EACA,WAAW;;UAGI;EACf,SAAS;EACT;;UAGe;EACf,YAAY;;UAGG;EACf;;;;;;;;EAQA,WAAW,eAAe;EAC1B;EACA,SAAS;;;UAIM,gCAAgC;EAC/C;;UAGe;EACf,QAAQ,eAAe;;EAEvB;;EAEA,WAAW;;UAGI;EACf,WAAW;EACX,eAAe;EACf,uBAAuB;;EAEvB,aAAa,mBAAmB;;;;;;;;iBAoBlB,wBAAwB;;;;;;;;;iBA+GxB,qBAAqB,kBAAkB,yBAAuC;;iBA+B9E,0BAA0B,QAAQ,kBAAkB,kBAAkB;;iBAuBtE,cAAc,QAAQ,kBAAkB;;iBAUxC,aAAa,QAAQ,kBAAkB,cAAc,WAAW;;iBA8BhE,gBAAgB,QAAQ,kBAAkB"}
|
package/dist/majorModes.d.mts
CHANGED
|
@@ -34,6 +34,8 @@ interface ResolvedLayerDefinition extends LayerDefinition {
|
|
|
34
34
|
}
|
|
35
35
|
interface MajorModesConfig {
|
|
36
36
|
layers: Record<string, ResolvedLayerDefinition>;
|
|
37
|
+
/** The mode selected when neither a flag nor the environment names one. */
|
|
38
|
+
defaultMajorMode: string;
|
|
37
39
|
/** Named major modes, each one a set of layer names. */
|
|
38
40
|
majorMode: Record<string, string[]>;
|
|
39
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"majorModes.d.mts","names":[],"sources":["../src/majorModes.ts"],"mappings":";;UAKiB;EACf;EACA;;UAGe;EACf;EACA,WAAW;;UAGI;EACf,SAAS;EACT;;UAGe;EACf,YAAY;;UAGG;EACf;;;;;;;;EAQA,WAAW,eAAe;EAC1B;EACA,SAAS;;;UAIM,gCAAgC;EAC/C;;UAGe;EACf,QAAQ,eAAe;;EAEvB,WAAW;;UAGI;EACf,WAAW;EACX,eAAe;EACf,uBAAuB;;EAEvB,aAAa,mBAAmB;;;;;;;;
|
|
1
|
+
{"version":3,"file":"majorModes.d.mts","names":[],"sources":["../src/majorModes.ts"],"mappings":";;UAKiB;EACf;EACA;;UAGe;EACf;EACA,WAAW;;UAGI;EACf,SAAS;EACT;;UAGe;EACf,YAAY;;UAGG;EACf;;;;;;;;EAQA,WAAW,eAAe;EAC1B;EACA,SAAS;;;UAIM,gCAAgC;EAC/C;;UAGe;EACf,QAAQ,eAAe;;EAEvB;;EAEA,WAAW;;UAGI;EACf,WAAW;EACX,eAAe;EACf,uBAAuB;;EAEvB,aAAa,mBAAmB;;;;;;;;iBAoBlB,wBAAwB;;;;;;;;;iBA+GxB,qBAAqB,kBAAkB,yBAAuC;;iBA+B9E,0BAA0B,QAAQ,kBAAkB,kBAAkB;;iBAuBtE,cAAc,QAAQ,kBAAkB;;iBAUxC,aAAa,QAAQ,kBAAkB,cAAc,WAAW;;iBA8BhE,gBAAgB,QAAQ,kBAAkB"}
|
package/dist/majorModes.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{DOOM_DIR as e,readDoomConfigSources as t}from"./layeredConfig.mjs";import n from"node:os";import r from"node:path";const i=`modes.yaml`,a=r.join(e,i),o=[`./`,`../`];function s(e){return r.isAbsolute(e)||o.some(t=>e.startsWith(t))}const c=[`subagents`],l=[`models`,`excludeTools`],u=[`model`,`thinking`],d=new Set([`off`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`]);function f(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function p(e,t,n){let r=Object.keys(e).filter(e=>!t.includes(e));if(r.length>0)throw Error(`${n} has unsupported field(s): ${r.join(`, `)}`)}function m(e,t){if(t===void 0)return;let n=`Layer "${e}" config in ${a}`;if(!f(t))throw Error(`${n} must be a mapping`);if(p(t,c,n),t.subagents===void 0)return{};if(!f(t.subagents))throw Error(`${n}.subagents must be a mapping`);p(t.subagents,l,`${n}.subagents`);let r=Object.hasOwn(t.subagents,`models`),i=Object.hasOwn(t.subagents,`excludeTools`);if(!r&&!i)throw Error(`${n}.subagents must define models or excludeTools`);let o;if(r){if(!Array.isArray(t.subagents.models)||t.subagents.models.length===0)throw Error(`${n}.subagents.models must be a non-empty array`);o=t.subagents.models.map((e,t)=>{let r=`${n}.subagents.models[${t}]`;if(!f(e))throw Error(`${r} must be a mapping`);if(p(e,u,r),typeof e.model!=`string`||!e.model.trim())throw Error(`${r}.model must be a non-empty string`);if(e.thinking!==void 0&&(typeof e.thinking!=`string`||!d.has(e.thinking)))throw Error(`${r}.thinking must be one of: ${[...d].join(`, `)}`);return{model:e.model.trim(),...e.thinking?{thinking:e.thinking}:{}}})}let s;if(i){let e=t.subagents.excludeTools;if(!Array.isArray(e)||e.length===0)throw Error(`${n}.subagents.excludeTools must be a non-empty array`);s=[...new Set(e.map((e,t)=>{let r=`${n}.subagents.excludeTools[${t}]`;if(typeof e!=`string`||!e.trim())throw Error(`${r} must be a non-empty string`);return e.trim()}))]}return{subagents:{...o?{models:o}:{},...s?{excludeTools:s}:{}}}}function h(e,t){if(!f(t))throw Error(`Layer "${e}" in ${a} must be a mapping`);if(`targets`in t)throw Error(`Layer "${e}" in ${a} uses removed field "targets"; select availability through major modes`);let n=m(e,t.config);return n===void 0?t:{...t,config:n}}function
|
|
1
|
+
import{DOOM_DIR as e,readDoomConfigSources as t}from"./layeredConfig.mjs";import n from"node:os";import r from"node:path";const i=`modes.yaml`,a=r.join(e,i),o=[`./`,`../`];function s(e){return r.isAbsolute(e)||o.some(t=>e.startsWith(t))}const c=[`subagents`],l=[`models`,`excludeTools`],u=[`model`,`thinking`],d=new Set([`off`,`minimal`,`low`,`medium`,`high`,`xhigh`,`max`]);function f(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function p(e,t,n){let r=Object.keys(e).filter(e=>!t.includes(e));if(r.length>0)throw Error(`${n} has unsupported field(s): ${r.join(`, `)}`)}function m(e,t){if(t===void 0)return;let n=`Layer "${e}" config in ${a}`;if(!f(t))throw Error(`${n} must be a mapping`);if(p(t,c,n),t.subagents===void 0)return{};if(!f(t.subagents))throw Error(`${n}.subagents must be a mapping`);p(t.subagents,l,`${n}.subagents`);let r=Object.hasOwn(t.subagents,`models`),i=Object.hasOwn(t.subagents,`excludeTools`);if(!r&&!i)throw Error(`${n}.subagents must define models or excludeTools`);let o;if(r){if(!Array.isArray(t.subagents.models)||t.subagents.models.length===0)throw Error(`${n}.subagents.models must be a non-empty array`);o=t.subagents.models.map((e,t)=>{let r=`${n}.subagents.models[${t}]`;if(!f(e))throw Error(`${r} must be a mapping`);if(p(e,u,r),typeof e.model!=`string`||!e.model.trim())throw Error(`${r}.model must be a non-empty string`);if(e.thinking!==void 0&&(typeof e.thinking!=`string`||!d.has(e.thinking)))throw Error(`${r}.thinking must be one of: ${[...d].join(`, `)}`);return{model:e.model.trim(),...e.thinking?{thinking:e.thinking}:{}}})}let s;if(i){let e=t.subagents.excludeTools;if(!Array.isArray(e)||e.length===0)throw Error(`${n}.subagents.excludeTools must be a non-empty array`);s=[...new Set(e.map((e,t)=>{let r=`${n}.subagents.excludeTools[${t}]`;if(typeof e!=`string`||!e.trim())throw Error(`${r} must be a non-empty string`);return e.trim()}))]}return{subagents:{...o?{models:o}:{},...s?{excludeTools:s}:{}}}}function h(e){if(typeof e!=`string`||!e.trim())throw Error(`defaultMajorMode in ${a} must be a non-empty string`);return e.trim()}function g(e,t){if(!f(t))throw Error(`Layer "${e}" in ${a} must be a mapping`);if(`targets`in t)throw Error(`Layer "${e}" in ${a} uses removed field "targets"; select availability through major modes`);let n=m(e,t.config);return n===void 0?t:{...t,config:n}}function _(e,r=n.homedir()){let o=t(i,e,r),s={},c={},l=`copilot`,u=!1;for(let e of o){for(let[t,n]of Object.entries(e.document.layers??{}))s[t]={...g(t,n),baseDirectory:e.baseDirectory};Object.hasOwn(e.document,`defaultMajorMode`)&&(l=h(e.document.defaultMajorMode),u=!0),Object.assign(c,e.document.majorMode??{})}for(let[e,t]of Object.entries(c))for(let n of t)if(!s[n])throw Error(`Unknown layer "${n}" in majorMode.${e} of ${a}`);if(u&&!Object.hasOwn(c,l))throw Error(`Unknown default major mode "${l}" in ${a}`);return{layers:s,defaultMajorMode:l,majorMode:c}}function v(e,t){let n,r=new Set,i=!1;for(let a of t){let t=e.layers[a];if(!t)throw Error(`Unknown layer: ${a}`);let o=t.config?.subagents;if(o){if(o.excludeTools!==void 0){i=!0;for(let e of o.excludeTools)r.add(e)}n={...n,...o.models?{models:o.models.map(e=>({...e}))}:{},...i?{excludeTools:[...r]}:{}}}}return n?{subagents:n}:{}}function y(e,t){if(!Object.hasOwn(e.majorMode,t))throw Error(`Unknown major mode: ${t}. Known major modes: ${Object.keys(e.majorMode).sort().join(`, `)}`);return[...e.majorMode[t]]}function b(e,t,n){let r=e.layers[t];if(!r)throw Error(`Unknown layer: ${t}`);let i=[];for(let e of r.extensions??[])i.push(n.ownEntry(e));for(let e of r.packages??[]){let a=typeof e==`string`?e:e.name,o=typeof e!=`string`&&e.optional===!0;if(s(a)){let e=n.localEntry(a,r.baseDirectory);if(e)i.push(e);else if(!o)throw Error(`Layer "${t}" package "${a}" does not resolve under ${r.baseDirectory}. Point it at an extension file, or a directory whose package.json names one.`);continue}if(o){let e=n.optionalPackageEntry(a);e&&i.push(e);continue}i.push(n.packageEntry(a))}return i}function x(e,t){let n=new Set;for(let r of t)for(let t of e.layers[r]?.hookGroups??[])n.add(t);return[...n]}export{s as isLocalPackageSpecifier,b as layerEntries,x as layerHookGroups,_ as loadMajorModesConfig,v as resolveLayerCustomization,y as resolveLayers};
|
|
2
2
|
//# sourceMappingURL=majorModes.mjs.map
|
package/dist/majorModes.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"majorModes.mjs","names":[],"sources":["../src/majorModes.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\nimport { DOOM_DIR, readDoomConfigSources } from './layeredConfig.ts';\nimport type { PlanningThinkingLevel } from './types.ts';\n\nexport interface LayerPackage {\n name: string;\n optional?: boolean;\n}\n\nexport interface SubagentLayerModelConfig {\n model: string;\n thinking?: PlanningThinkingLevel;\n}\n\nexport interface SubagentLayerConfig {\n models?: SubagentLayerModelConfig[];\n excludeTools?: string[];\n}\n\nexport interface LayerCustomizationConfig {\n subagents?: SubagentLayerConfig;\n}\n\nexport interface LayerDefinition {\n extensions?: string[];\n /**\n * npm specifiers, or paths relative to the config that declares the layer.\n * A path (`./`, `../`, or absolute) is resolved against the repository root\n * for `.doom/modes.yaml` and against the global `.doom` directory for\n * `~/.pi/.doom/modes.yaml`, which is how an extension that was never\n * published to npm gets loaded.\n */\n packages?: Array<string | LayerPackage>;\n hookGroups?: string[];\n config?: LayerCustomizationConfig;\n}\n\n/** A layer plus the root its relative package paths resolve against. */\nexport interface ResolvedLayerDefinition extends LayerDefinition {\n baseDirectory: string;\n}\n\nexport interface MajorModesConfig {\n layers: Record<string, ResolvedLayerDefinition>;\n /** Named major modes, each one a set of layer names. */\n majorMode: Record<string, string[]>;\n}\n\nexport interface LayerResolvers {\n ownEntry: (name: string) => string;\n packageEntry: (name: string) => string;\n optionalPackageEntry: (name: string) => string | undefined;\n /** Resolves a path-style layer package against the config that declared it. */\n localEntry: (specifier: string, baseDirectory: string) => string | undefined;\n}\n\ninterface ModesDocument {\n layers?: Record<string, unknown>;\n majorMode?: Record<string, string[]>;\n}\n\nconst MODES_FILE = 'modes.yaml';\nconst MAJOR_MODES_RELATIVE_PATH = path.join(DOOM_DIR, MODES_FILE);\nconst RELATIVE_PREFIXES = ['./', '../'] as const;\n\n/**\n * Whether a layer package names a path rather than an installed package.\n *\n * The same rule Node applies to import specifiers, so a bare name is always an\n * npm package and a path always looks like one.\n */\nexport function isLocalPackageSpecifier(specifier: string): boolean {\n return path.isAbsolute(specifier) || RELATIVE_PREFIXES.some((prefix) => specifier.startsWith(prefix));\n}\nconst LAYER_CONFIG_KEYS = ['subagents'] as const;\nconst SUBAGENT_CONFIG_KEYS = ['models', 'excludeTools'] as const;\nconst SUBAGENT_MODEL_KEYS = ['model', 'thinking'] as const;\nconst THINKING_VALUES = new Set<PlanningThinkingLevel>(['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction assertKnownKeys(value: Record<string, unknown>, keys: readonly string[], location: string): void {\n const unsupported = Object.keys(value).filter((key) => !keys.includes(key));\n if (unsupported.length > 0) {\n throw new Error(`${location} has unsupported field(s): ${unsupported.join(', ')}`);\n }\n}\n\nfunction parseLayerCustomization(name: string, value: unknown): LayerCustomizationConfig | undefined {\n if (value === undefined) return undefined;\n const location = `Layer \"${name}\" config in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!isRecord(value)) throw new Error(`${location} must be a mapping`);\n assertKnownKeys(value, LAYER_CONFIG_KEYS, location);\n if (value.subagents === undefined) return {};\n if (!isRecord(value.subagents)) throw new Error(`${location}.subagents must be a mapping`);\n assertKnownKeys(value.subagents, SUBAGENT_CONFIG_KEYS, `${location}.subagents`);\n const hasModels = Object.hasOwn(value.subagents, 'models');\n const hasExcludeTools = Object.hasOwn(value.subagents, 'excludeTools');\n if (!hasModels && !hasExcludeTools) {\n throw new Error(`${location}.subagents must define models or excludeTools`);\n }\n\n let models: SubagentLayerModelConfig[] | undefined;\n if (hasModels) {\n if (!Array.isArray(value.subagents.models) || value.subagents.models.length === 0) {\n throw new Error(`${location}.subagents.models must be a non-empty array`);\n }\n models = value.subagents.models.map((entry, index): SubagentLayerModelConfig => {\n const entryLocation = `${location}.subagents.models[${index}]`;\n if (!isRecord(entry)) throw new Error(`${entryLocation} must be a mapping`);\n assertKnownKeys(entry, SUBAGENT_MODEL_KEYS, entryLocation);\n if (typeof entry.model !== 'string' || !entry.model.trim()) {\n throw new Error(`${entryLocation}.model must be a non-empty string`);\n }\n if (\n entry.thinking !== undefined &&\n (typeof entry.thinking !== 'string' || !THINKING_VALUES.has(entry.thinking as PlanningThinkingLevel))\n ) {\n throw new Error(`${entryLocation}.thinking must be one of: ${[...THINKING_VALUES].join(', ')}`);\n }\n return {\n model: entry.model.trim(),\n ...(entry.thinking ? { thinking: entry.thinking as PlanningThinkingLevel } : {}),\n };\n });\n }\n\n let normalizedExcludeTools: string[] | undefined;\n if (hasExcludeTools) {\n const excludeTools = value.subagents.excludeTools;\n if (!Array.isArray(excludeTools) || excludeTools.length === 0) {\n throw new Error(`${location}.subagents.excludeTools must be a non-empty array`);\n }\n normalizedExcludeTools = [\n ...new Set(\n excludeTools.map((entry, index) => {\n const entryLocation = `${location}.subagents.excludeTools[${index}]`;\n if (typeof entry !== 'string' || !entry.trim()) {\n throw new Error(`${entryLocation} must be a non-empty string`);\n }\n return entry.trim();\n }),\n ),\n ];\n }\n\n return {\n subagents: {\n ...(models ? { models } : {}),\n ...(normalizedExcludeTools ? { excludeTools: normalizedExcludeTools } : {}),\n },\n };\n}\n\nfunction parseLayer(name: string, definition: unknown): LayerDefinition {\n if (!isRecord(definition)) throw new Error(`Layer \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH} must be a mapping`);\n if ('targets' in definition) {\n throw new Error(\n `Layer \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH} uses removed field \"targets\"; select availability through major modes`,\n );\n }\n const config = parseLayerCustomization(name, definition.config);\n return (config === undefined ? definition : { ...definition, config }) as LayerDefinition;\n}\n\n/**\n * Reads layers and major modes from the global and repository configs.\n *\n * The repository is read last, so it replaces a global layer or major mode of\n * the same name. Layers keep the root they were declared under so that a\n * global layer naming `./extensions/x` still points beside the global config\n * once it is loaded from inside some other repository.\n */\nexport function loadMajorModesConfig(repoRoot: string, homeDirectory: string = os.homedir()): MajorModesConfig {\n const sources = readDoomConfigSources<ModesDocument>(MODES_FILE, repoRoot, homeDirectory);\n const layers: Record<string, ResolvedLayerDefinition> = {};\n const majorMode: Record<string, string[]> = {};\n for (const source of sources) {\n for (const [name, definition] of Object.entries(source.document.layers ?? {})) {\n layers[name] = { ...parseLayer(name, definition), baseDirectory: source.baseDirectory };\n }\n Object.assign(majorMode, source.document.majorMode ?? {});\n }\n\n for (const [modeName, names] of Object.entries(majorMode)) {\n for (const name of names) {\n if (!layers[name]) {\n throw new Error(`Unknown layer \"${name}\" in majorMode.${modeName} of ${MAJOR_MODES_RELATIVE_PATH}`);\n }\n }\n }\n return { layers, majorMode };\n}\n\n/** Merges selected layer customization in order, with later models winning and exclusions unioned. */\nexport function resolveLayerCustomization(config: MajorModesConfig, names: string[]): LayerCustomizationConfig {\n let subagents: SubagentLayerConfig | undefined;\n const excludeTools = new Set<string>();\n let hasExcludeTools = false;\n for (const name of names) {\n const layer = config.layers[name];\n if (!layer) throw new Error(`Unknown layer: ${name}`);\n const next = layer.config?.subagents;\n if (!next) continue;\n if (next.excludeTools !== undefined) {\n hasExcludeTools = true;\n for (const tool of next.excludeTools) excludeTools.add(tool);\n }\n subagents = {\n ...subagents,\n ...(next.models ? { models: next.models.map((model) => ({ ...model })) } : {}),\n ...(hasExcludeTools ? { excludeTools: [...excludeTools] } : {}),\n };\n }\n return subagents ? { subagents } : {};\n}\n\n/** Resolves one named major mode into its configured layer components. */\nexport function resolveLayers(config: MajorModesConfig, majorMode: string): string[] {\n if (!Object.hasOwn(config.majorMode, majorMode)) {\n throw new Error(\n `Unknown major mode: ${majorMode}. Known major modes: ${Object.keys(config.majorMode).sort().join(', ')}`,\n );\n }\n return [...config.majorMode[majorMode]!];\n}\n\n/** Resolves one layer's extension and package entries to absolute paths. */\nexport function layerEntries(config: MajorModesConfig, name: string, resolvers: LayerResolvers): string[] {\n const layer = config.layers[name];\n if (!layer) throw new Error(`Unknown layer: ${name}`);\n const entries: string[] = [];\n for (const entry of layer.extensions ?? []) entries.push(resolvers.ownEntry(entry));\n for (const entry of layer.packages ?? []) {\n const specifier = typeof entry === 'string' ? entry : entry.name;\n const optional = typeof entry === 'string' ? false : entry.optional === true;\n if (isLocalPackageSpecifier(specifier)) {\n const resolved = resolvers.localEntry(specifier, layer.baseDirectory);\n if (resolved) entries.push(resolved);\n else if (!optional) {\n throw new Error(\n `Layer \"${name}\" package \"${specifier}\" does not resolve under ${layer.baseDirectory}. ` +\n 'Point it at an extension file, or a directory whose package.json names one.',\n );\n }\n continue;\n }\n if (optional) {\n const resolved = resolvers.optionalPackageEntry(specifier);\n if (resolved) entries.push(resolved);\n continue;\n }\n entries.push(resolvers.packageEntry(specifier));\n }\n return entries;\n}\n\n/** Hook group ids contributed by the selected layers, deduped. */\nexport function layerHookGroups(config: MajorModesConfig, names: string[]): string[] {\n const groups = new Set<string>();\n for (const name of names) {\n for (const group of config.layers[name]?.hookGroups ?? []) groups.add(group);\n }\n return [...groups];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"majorModes.mjs","names":[],"sources":["../src/majorModes.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\nimport { DOOM_DIR, readDoomConfigSources } from './layeredConfig.ts';\nimport type { PlanningThinkingLevel } from './types.ts';\n\nexport interface LayerPackage {\n name: string;\n optional?: boolean;\n}\n\nexport interface SubagentLayerModelConfig {\n model: string;\n thinking?: PlanningThinkingLevel;\n}\n\nexport interface SubagentLayerConfig {\n models?: SubagentLayerModelConfig[];\n excludeTools?: string[];\n}\n\nexport interface LayerCustomizationConfig {\n subagents?: SubagentLayerConfig;\n}\n\nexport interface LayerDefinition {\n extensions?: string[];\n /**\n * npm specifiers, or paths relative to the config that declares the layer.\n * A path (`./`, `../`, or absolute) is resolved against the repository root\n * for `.doom/modes.yaml` and against the global `.doom` directory for\n * `~/.pi/.doom/modes.yaml`, which is how an extension that was never\n * published to npm gets loaded.\n */\n packages?: Array<string | LayerPackage>;\n hookGroups?: string[];\n config?: LayerCustomizationConfig;\n}\n\n/** A layer plus the root its relative package paths resolve against. */\nexport interface ResolvedLayerDefinition extends LayerDefinition {\n baseDirectory: string;\n}\n\nexport interface MajorModesConfig {\n layers: Record<string, ResolvedLayerDefinition>;\n /** The mode selected when neither a flag nor the environment names one. */\n defaultMajorMode: string;\n /** Named major modes, each one a set of layer names. */\n majorMode: Record<string, string[]>;\n}\n\nexport interface LayerResolvers {\n ownEntry: (name: string) => string;\n packageEntry: (name: string) => string;\n optionalPackageEntry: (name: string) => string | undefined;\n /** Resolves a path-style layer package against the config that declared it. */\n localEntry: (specifier: string, baseDirectory: string) => string | undefined;\n}\n\ninterface ModesDocument {\n layers?: Record<string, unknown>;\n defaultMajorMode?: unknown;\n majorMode?: Record<string, string[]>;\n}\n\nconst MODES_FILE = 'modes.yaml';\nconst FALLBACK_MAJOR_MODE = 'copilot';\nconst MAJOR_MODES_RELATIVE_PATH = path.join(DOOM_DIR, MODES_FILE);\nconst RELATIVE_PREFIXES = ['./', '../'] as const;\n\n/**\n * Whether a layer package names a path rather than an installed package.\n *\n * The same rule Node applies to import specifiers, so a bare name is always an\n * npm package and a path always looks like one.\n */\nexport function isLocalPackageSpecifier(specifier: string): boolean {\n return path.isAbsolute(specifier) || RELATIVE_PREFIXES.some((prefix) => specifier.startsWith(prefix));\n}\nconst LAYER_CONFIG_KEYS = ['subagents'] as const;\nconst SUBAGENT_CONFIG_KEYS = ['models', 'excludeTools'] as const;\nconst SUBAGENT_MODEL_KEYS = ['model', 'thinking'] as const;\nconst THINKING_VALUES = new Set<PlanningThinkingLevel>(['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction assertKnownKeys(value: Record<string, unknown>, keys: readonly string[], location: string): void {\n const unsupported = Object.keys(value).filter((key) => !keys.includes(key));\n if (unsupported.length > 0) {\n throw new Error(`${location} has unsupported field(s): ${unsupported.join(', ')}`);\n }\n}\n\nfunction parseLayerCustomization(name: string, value: unknown): LayerCustomizationConfig | undefined {\n if (value === undefined) return undefined;\n const location = `Layer \"${name}\" config in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!isRecord(value)) throw new Error(`${location} must be a mapping`);\n assertKnownKeys(value, LAYER_CONFIG_KEYS, location);\n if (value.subagents === undefined) return {};\n if (!isRecord(value.subagents)) throw new Error(`${location}.subagents must be a mapping`);\n assertKnownKeys(value.subagents, SUBAGENT_CONFIG_KEYS, `${location}.subagents`);\n const hasModels = Object.hasOwn(value.subagents, 'models');\n const hasExcludeTools = Object.hasOwn(value.subagents, 'excludeTools');\n if (!hasModels && !hasExcludeTools) {\n throw new Error(`${location}.subagents must define models or excludeTools`);\n }\n\n let models: SubagentLayerModelConfig[] | undefined;\n if (hasModels) {\n if (!Array.isArray(value.subagents.models) || value.subagents.models.length === 0) {\n throw new Error(`${location}.subagents.models must be a non-empty array`);\n }\n models = value.subagents.models.map((entry, index): SubagentLayerModelConfig => {\n const entryLocation = `${location}.subagents.models[${index}]`;\n if (!isRecord(entry)) throw new Error(`${entryLocation} must be a mapping`);\n assertKnownKeys(entry, SUBAGENT_MODEL_KEYS, entryLocation);\n if (typeof entry.model !== 'string' || !entry.model.trim()) {\n throw new Error(`${entryLocation}.model must be a non-empty string`);\n }\n if (\n entry.thinking !== undefined &&\n (typeof entry.thinking !== 'string' || !THINKING_VALUES.has(entry.thinking as PlanningThinkingLevel))\n ) {\n throw new Error(`${entryLocation}.thinking must be one of: ${[...THINKING_VALUES].join(', ')}`);\n }\n return {\n model: entry.model.trim(),\n ...(entry.thinking ? { thinking: entry.thinking as PlanningThinkingLevel } : {}),\n };\n });\n }\n\n let normalizedExcludeTools: string[] | undefined;\n if (hasExcludeTools) {\n const excludeTools = value.subagents.excludeTools;\n if (!Array.isArray(excludeTools) || excludeTools.length === 0) {\n throw new Error(`${location}.subagents.excludeTools must be a non-empty array`);\n }\n normalizedExcludeTools = [\n ...new Set(\n excludeTools.map((entry, index) => {\n const entryLocation = `${location}.subagents.excludeTools[${index}]`;\n if (typeof entry !== 'string' || !entry.trim()) {\n throw new Error(`${entryLocation} must be a non-empty string`);\n }\n return entry.trim();\n }),\n ),\n ];\n }\n\n return {\n subagents: {\n ...(models ? { models } : {}),\n ...(normalizedExcludeTools ? { excludeTools: normalizedExcludeTools } : {}),\n },\n };\n}\n\nfunction parseDefaultMajorMode(value: unknown): string {\n if (typeof value !== 'string' || !value.trim()) {\n throw new Error(`defaultMajorMode in ${MAJOR_MODES_RELATIVE_PATH} must be a non-empty string`);\n }\n return value.trim();\n}\n\nfunction parseLayer(name: string, definition: unknown): LayerDefinition {\n if (!isRecord(definition)) throw new Error(`Layer \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH} must be a mapping`);\n if ('targets' in definition) {\n throw new Error(\n `Layer \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH} uses removed field \"targets\"; select availability through major modes`,\n );\n }\n const config = parseLayerCustomization(name, definition.config);\n return (config === undefined ? definition : { ...definition, config }) as LayerDefinition;\n}\n\n/**\n * Reads layers and major modes from the global and repository configs.\n *\n * The repository is read last, so it replaces a global layer or major mode of\n * the same name. Layers keep the root they were declared under so that a\n * global layer naming `./extensions/x` still points beside the global config\n * once it is loaded from inside some other repository.\n */\nexport function loadMajorModesConfig(repoRoot: string, homeDirectory: string = os.homedir()): MajorModesConfig {\n const sources = readDoomConfigSources<ModesDocument>(MODES_FILE, repoRoot, homeDirectory);\n const layers: Record<string, ResolvedLayerDefinition> = {};\n const majorMode: Record<string, string[]> = {};\n let defaultMajorMode = FALLBACK_MAJOR_MODE;\n let hasConfiguredDefault = false;\n for (const source of sources) {\n for (const [name, definition] of Object.entries(source.document.layers ?? {})) {\n layers[name] = { ...parseLayer(name, definition), baseDirectory: source.baseDirectory };\n }\n if (Object.hasOwn(source.document, 'defaultMajorMode')) {\n defaultMajorMode = parseDefaultMajorMode(source.document.defaultMajorMode);\n hasConfiguredDefault = true;\n }\n Object.assign(majorMode, source.document.majorMode ?? {});\n }\n\n for (const [modeName, names] of Object.entries(majorMode)) {\n for (const name of names) {\n if (!layers[name]) {\n throw new Error(`Unknown layer \"${name}\" in majorMode.${modeName} of ${MAJOR_MODES_RELATIVE_PATH}`);\n }\n }\n }\n if (hasConfiguredDefault && !Object.hasOwn(majorMode, defaultMajorMode)) {\n throw new Error(`Unknown default major mode \"${defaultMajorMode}\" in ${MAJOR_MODES_RELATIVE_PATH}`);\n }\n return { layers, defaultMajorMode, majorMode };\n}\n\n/** Merges selected layer customization in order, with later models winning and exclusions unioned. */\nexport function resolveLayerCustomization(config: MajorModesConfig, names: string[]): LayerCustomizationConfig {\n let subagents: SubagentLayerConfig | undefined;\n const excludeTools = new Set<string>();\n let hasExcludeTools = false;\n for (const name of names) {\n const layer = config.layers[name];\n if (!layer) throw new Error(`Unknown layer: ${name}`);\n const next = layer.config?.subagents;\n if (!next) continue;\n if (next.excludeTools !== undefined) {\n hasExcludeTools = true;\n for (const tool of next.excludeTools) excludeTools.add(tool);\n }\n subagents = {\n ...subagents,\n ...(next.models ? { models: next.models.map((model) => ({ ...model })) } : {}),\n ...(hasExcludeTools ? { excludeTools: [...excludeTools] } : {}),\n };\n }\n return subagents ? { subagents } : {};\n}\n\n/** Resolves one named major mode into its configured layer components. */\nexport function resolveLayers(config: MajorModesConfig, majorMode: string): string[] {\n if (!Object.hasOwn(config.majorMode, majorMode)) {\n throw new Error(\n `Unknown major mode: ${majorMode}. Known major modes: ${Object.keys(config.majorMode).sort().join(', ')}`,\n );\n }\n return [...config.majorMode[majorMode]!];\n}\n\n/** Resolves one layer's extension and package entries to absolute paths. */\nexport function layerEntries(config: MajorModesConfig, name: string, resolvers: LayerResolvers): string[] {\n const layer = config.layers[name];\n if (!layer) throw new Error(`Unknown layer: ${name}`);\n const entries: string[] = [];\n for (const entry of layer.extensions ?? []) entries.push(resolvers.ownEntry(entry));\n for (const entry of layer.packages ?? []) {\n const specifier = typeof entry === 'string' ? entry : entry.name;\n const optional = typeof entry === 'string' ? false : entry.optional === true;\n if (isLocalPackageSpecifier(specifier)) {\n const resolved = resolvers.localEntry(specifier, layer.baseDirectory);\n if (resolved) entries.push(resolved);\n else if (!optional) {\n throw new Error(\n `Layer \"${name}\" package \"${specifier}\" does not resolve under ${layer.baseDirectory}. ` +\n 'Point it at an extension file, or a directory whose package.json names one.',\n );\n }\n continue;\n }\n if (optional) {\n const resolved = resolvers.optionalPackageEntry(specifier);\n if (resolved) entries.push(resolved);\n continue;\n }\n entries.push(resolvers.packageEntry(specifier));\n }\n return entries;\n}\n\n/** Hook group ids contributed by the selected layers, deduped. */\nexport function layerHookGroups(config: MajorModesConfig, names: string[]): string[] {\n const groups = new Set<string>();\n for (const name of names) {\n for (const group of config.layers[name]?.hookGroups ?? []) groups.add(group);\n }\n return [...groups];\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-config",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.2",
|
|
4
4
|
"description": "Shared Doom configuration loading and dependency injection",
|
|
5
5
|
"homepage": "https://agimon.ai",
|
|
6
6
|
"license": "MIT",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"inversify": "8.2.1",
|
|
120
120
|
"reflect-metadata": "0.2.2",
|
|
121
121
|
"yaml": "2.9.0",
|
|
122
|
-
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.
|
|
122
|
+
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.2"
|
|
123
123
|
},
|
|
124
124
|
"devDependencies": {
|
|
125
125
|
"@earendil-works/pi-coding-agent": "0.84.1",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## Sources
|
|
4
4
|
|
|
5
5
|
- `.doom/modes.yaml` defines layers and the major modes built from them. A layer may contribute extension entry names, package extension specifiers, and hook group IDs. A major mode is a named set of layers, and a session runs under exactly one.
|
|
6
|
-
- `.doom/domains.yaml` defines content bundles. A domain may contribute whole plugins or filtered plugin parts, an MCP allowlist, and shared-skill behavior. Aliases expand to domain names.
|
|
6
|
+
- `.doom/domains.yaml` defines content bundles. A domain may contribute whole plugins or filtered plugin parts, an MCP allowlist, and shared-skill behavior. Aliases expand to domain names. Its optional `defaultDomains` array selects any number of domains when neither flags nor `DOOMPI_DOMAINS` names them.
|
|
7
7
|
- `.doom/profiles.yaml` defines personas and environment defaults. Each persona path must remain inside `agents/`.
|
|
8
8
|
- Every `.doom` document is read from `~/.pi/.doom/` first and `<repoRoot>/.doom/` second; a repository entry replaces the global one of the same name. Relative paths inside a document resolve against the config that declares it: the repository root, or `~/.pi/.doom` for the global copy.
|
|
9
9
|
|