@agimon-ai/doompi-config 0.0.1-alpha.26 → 0.0.1-alpha.28

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.
@@ -11,27 +11,27 @@ import { HarnessState } from "../types/config.cjs";
11
11
  declare const FILE_ONLY_STATE_FIELDS: ReadonlySet<keyof HarnessState>;
12
12
  type HarnessStateParseReporter = (key: string, error: unknown) => void;
13
13
  declare const HARNESS_STATE_KEYS: {
14
- readonly root: "DOOMPI_ROOT";
15
- readonly majorMode: "DOOMPI_MAJOR_MODE";
16
- readonly temporaryDirectory: "DOOMPI_TEMP_DIR";
17
- readonly domains: "DOOMPI_DOMAINS";
18
- readonly layers: "DOOMPI_LAYERS";
19
- readonly compositionFingerprint: "DOOMPI_COMPOSITION_FINGERPRINT";
20
- readonly profile: "DOOMPI_PROFILE";
21
- readonly profileEnvironment: "DOOMPI_PROFILE_ENV";
22
- readonly hookGroups: "DOOMPI_HOOK_GROUPS";
23
- readonly skillDirectories: "DOOMPI_SKILL_DIRS";
24
- readonly agentDirectories: "DOOMPI_AGENT_DIRS";
25
- readonly additionalDirectories: "DOOMPI_ADDITIONAL_DIRS";
26
- readonly childExtensions: "DOOMPI_CHILD_EXTENSIONS";
27
- readonly pluginDirectories: "DOOMPI_PLUGIN_DIRS";
28
- readonly pluginHooks: "DOOMPI_PLUGIN_HOOKS";
29
- readonly mcpConfigPath: "DOOMPI_MCP_CONFIG";
30
- readonly personaFile: "DOOMPI_PERSONA_FILE";
31
- readonly hooks: "DOOMPI_HOOKS_ENABLED";
32
- readonly agents: "DOOMPI_AGENTS_ENABLED";
33
- readonly mcp: "DOOMPI_MCP_ENABLED";
34
- readonly allowProtectedWrites: "DOOMPI_ALLOW_PROTECTED_WRITES";
14
+ readonly root: 'DOOMPI_ROOT';
15
+ readonly majorMode: 'DOOMPI_MAJOR_MODE';
16
+ readonly temporaryDirectory: 'DOOMPI_TEMP_DIR';
17
+ readonly domains: 'DOOMPI_DOMAINS';
18
+ readonly layers: 'DOOMPI_LAYERS';
19
+ readonly compositionFingerprint: 'DOOMPI_COMPOSITION_FINGERPRINT';
20
+ readonly profile: 'DOOMPI_PROFILE';
21
+ readonly profileEnvironment: 'DOOMPI_PROFILE_ENV';
22
+ readonly hookGroups: 'DOOMPI_HOOK_GROUPS';
23
+ readonly skillDirectories: 'DOOMPI_SKILL_DIRS';
24
+ readonly agentDirectories: 'DOOMPI_AGENT_DIRS';
25
+ readonly additionalDirectories: 'DOOMPI_ADDITIONAL_DIRS';
26
+ readonly childExtensions: 'DOOMPI_CHILD_EXTENSIONS';
27
+ readonly pluginDirectories: 'DOOMPI_PLUGIN_DIRS';
28
+ readonly pluginHooks: 'DOOMPI_PLUGIN_HOOKS';
29
+ readonly mcpConfigPath: 'DOOMPI_MCP_CONFIG';
30
+ readonly personaFile: 'DOOMPI_PERSONA_FILE';
31
+ readonly hooks: 'DOOMPI_HOOKS_ENABLED';
32
+ readonly agents: 'DOOMPI_AGENTS_ENABLED';
33
+ readonly mcp: 'DOOMPI_MCP_ENABLED';
34
+ readonly allowProtectedWrites: 'DOOMPI_ALLOW_PROTECTED_WRITES';
35
35
  };
36
36
  declare function readHarnessState(environment?: NodeJS.ProcessEnv, report?: HarnessStateParseReporter): HarnessState;
37
37
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"harnessState.d.cts","names":[],"sources":["../../src/adapters/harnessState.ts"],"mappings":";;;;;;;;;;cAwBa,wBAAwB,kBAAkB;KAM3C,6BAA6B,aAAa;cAEzC;;;;;;;;;;;;;;;;;;;;;;;iBAiGG,iBACd,cAAa,OAAO,YACpB,SAAS,4BACR;;;;;;;;;;;iBAsCa,0BACd,OAAO,QAAQ,eACf,cAAa,OAAO,aACnB,OAAO"}
1
+ {"version":3,"file":"harnessState.d.cts","names":[],"sources":["../../src/adapters/harnessState.ts"],"mappings":";;;;;;;;;;cAwBa,wBAAwB,kBAAkB;KAM3C,6BAA6B,aAAa;cAEzC;WACX;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;iBA4Ec,iBACd,cAAa,OAAO,YACpB,SAAS,4BACR;;;;;;;;;;;iBAsCa,0BACd,OAAO,QAAQ,eACf,cAAa,OAAO,aACnB,OAAO"}
@@ -11,27 +11,27 @@ import { HarnessState } from "../types/config.mjs";
11
11
  declare const FILE_ONLY_STATE_FIELDS: ReadonlySet<keyof HarnessState>;
12
12
  type HarnessStateParseReporter = (key: string, error: unknown) => void;
13
13
  declare const HARNESS_STATE_KEYS: {
14
- readonly root: "DOOMPI_ROOT";
15
- readonly majorMode: "DOOMPI_MAJOR_MODE";
16
- readonly temporaryDirectory: "DOOMPI_TEMP_DIR";
17
- readonly domains: "DOOMPI_DOMAINS";
18
- readonly layers: "DOOMPI_LAYERS";
19
- readonly compositionFingerprint: "DOOMPI_COMPOSITION_FINGERPRINT";
20
- readonly profile: "DOOMPI_PROFILE";
21
- readonly profileEnvironment: "DOOMPI_PROFILE_ENV";
22
- readonly hookGroups: "DOOMPI_HOOK_GROUPS";
23
- readonly skillDirectories: "DOOMPI_SKILL_DIRS";
24
- readonly agentDirectories: "DOOMPI_AGENT_DIRS";
25
- readonly additionalDirectories: "DOOMPI_ADDITIONAL_DIRS";
26
- readonly childExtensions: "DOOMPI_CHILD_EXTENSIONS";
27
- readonly pluginDirectories: "DOOMPI_PLUGIN_DIRS";
28
- readonly pluginHooks: "DOOMPI_PLUGIN_HOOKS";
29
- readonly mcpConfigPath: "DOOMPI_MCP_CONFIG";
30
- readonly personaFile: "DOOMPI_PERSONA_FILE";
31
- readonly hooks: "DOOMPI_HOOKS_ENABLED";
32
- readonly agents: "DOOMPI_AGENTS_ENABLED";
33
- readonly mcp: "DOOMPI_MCP_ENABLED";
34
- readonly allowProtectedWrites: "DOOMPI_ALLOW_PROTECTED_WRITES";
14
+ readonly root: 'DOOMPI_ROOT';
15
+ readonly majorMode: 'DOOMPI_MAJOR_MODE';
16
+ readonly temporaryDirectory: 'DOOMPI_TEMP_DIR';
17
+ readonly domains: 'DOOMPI_DOMAINS';
18
+ readonly layers: 'DOOMPI_LAYERS';
19
+ readonly compositionFingerprint: 'DOOMPI_COMPOSITION_FINGERPRINT';
20
+ readonly profile: 'DOOMPI_PROFILE';
21
+ readonly profileEnvironment: 'DOOMPI_PROFILE_ENV';
22
+ readonly hookGroups: 'DOOMPI_HOOK_GROUPS';
23
+ readonly skillDirectories: 'DOOMPI_SKILL_DIRS';
24
+ readonly agentDirectories: 'DOOMPI_AGENT_DIRS';
25
+ readonly additionalDirectories: 'DOOMPI_ADDITIONAL_DIRS';
26
+ readonly childExtensions: 'DOOMPI_CHILD_EXTENSIONS';
27
+ readonly pluginDirectories: 'DOOMPI_PLUGIN_DIRS';
28
+ readonly pluginHooks: 'DOOMPI_PLUGIN_HOOKS';
29
+ readonly mcpConfigPath: 'DOOMPI_MCP_CONFIG';
30
+ readonly personaFile: 'DOOMPI_PERSONA_FILE';
31
+ readonly hooks: 'DOOMPI_HOOKS_ENABLED';
32
+ readonly agents: 'DOOMPI_AGENTS_ENABLED';
33
+ readonly mcp: 'DOOMPI_MCP_ENABLED';
34
+ readonly allowProtectedWrites: 'DOOMPI_ALLOW_PROTECTED_WRITES';
35
35
  };
36
36
  declare function readHarnessState(environment?: NodeJS.ProcessEnv, report?: HarnessStateParseReporter): HarnessState;
37
37
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"harnessState.d.mts","names":[],"sources":["../../src/adapters/harnessState.ts"],"mappings":";;;;;;;;;;cAwBa,wBAAwB,kBAAkB;KAM3C,6BAA6B,aAAa;cAEzC;;;;;;;;;;;;;;;;;;;;;;;iBAiGG,iBACd,cAAa,OAAO,YACpB,SAAS,4BACR;;;;;;;;;;;iBAsCa,0BACd,OAAO,QAAQ,eACf,cAAa,OAAO,aACnB,OAAO"}
1
+ {"version":3,"file":"harnessState.d.mts","names":[],"sources":["../../src/adapters/harnessState.ts"],"mappings":";;;;;;;;;;cAwBa,wBAAwB,kBAAkB;KAM3C,6BAA6B,aAAa;cAEzC;WACX;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;iBA4Ec,iBACd,cAAa,OAAO,YACpB,SAAS,4BACR;;;;;;;;;;;iBAsCa,0BACd,OAAO,QAAQ,eACf,cAAa,OAAO,aACnB,OAAO"}
@@ -35,9 +35,23 @@ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./config.cjs");l
35
35
  #
36
36
  # Use a package mapping when that package accepts configuration. Configuration
37
37
  # belongs under the package entry that consumes it, not beside packages.
38
- # DoomPi's Help, Hooks, Goal, Voice, Runner, File Edit, Autocompact, Loop, Plan,
39
- # Workflow, Log, and MCP packages are distribution defaults. Do not restate
40
- # them here unless you need to attach package-owned configuration.
38
+ # The top-level default bundle loads before every major mode. Replace this whole
39
+ # packages list to customize the distribution defaults. DoomPi core packages
40
+ # remain active independently of this list.
41
+ default:
42
+ packages:
43
+ - '@agimon-ai/doompi-help'
44
+ - '@agimon-ai/doompi-hook'
45
+ - '@agimon-ai/doompi-goal'
46
+ - '@agimon-ai/doompi-voice'
47
+ - '@agimon-ai/doompi-runner'
48
+ - '@agimon-ai/doompi-file-edit'
49
+ - '@agimon-ai/doompi-autocompact'
50
+ - '@agimon-ai/doompi-loop'
51
+ - '@agimon-ai/doompi-plan'
52
+ - '@agimon-ai/doompi-workflow'
53
+ - '@agimon-ai/doompi-log'
54
+ - '@agimon-ai/doompi-mcp'
41
55
  layers:
42
56
  team:
43
57
  packages:
@@ -139,8 +153,22 @@ profiles:
139
153
  # Select one with: doompi --profile writer
140
154
  `},d={"config.yaml":u[`config.yaml`],"modes.yaml":`# Repository-local layers and major modes. These entries override personal
141
155
  # entries with the same name from ~/.pi/.doom/modes.yaml.
142
- # DoomPi's standard distribution packages are already active; declare only
143
- # additional or configured packages here.
156
+ # This repository-local default replaces the personal top-level default as one
157
+ # whole package list. Edit it to customize the standalone package baseline.
158
+ default:
159
+ packages:
160
+ - '@agimon-ai/doompi-help'
161
+ - '@agimon-ai/doompi-hook'
162
+ - '@agimon-ai/doompi-goal'
163
+ - '@agimon-ai/doompi-voice'
164
+ - '@agimon-ai/doompi-runner'
165
+ - '@agimon-ai/doompi-file-edit'
166
+ - '@agimon-ai/doompi-autocompact'
167
+ - '@agimon-ai/doompi-loop'
168
+ - '@agimon-ai/doompi-plan'
169
+ - '@agimon-ai/doompi-workflow'
170
+ - '@agimon-ai/doompi-log'
171
+ - '@agimon-ai/doompi-mcp'
144
172
  layers:
145
173
  team:
146
174
  packages:
@@ -1 +1 @@
1
- {"version":3,"file":"init.cjs","names":["fs","path","randomUUID","os","globalDoomConfigDirectory"],"sources":["../../src/adapters/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 REPOSITORY_DIRECTORY_MODE = 0o755;\nconst REPOSITORY_FILE_MODE = 0o644;\nconst DOOM_DIRECTORY = '.doom';\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 = ['config.yaml', 'modes.yaml', 'domains.yaml', 'profiles.yaml'] as const;\n\ntype DoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];\n\n/** Portable defaults for a new global Doom configuration directory. */\nexport const DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, 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# utteranceIdleMs: 3000\n# tts:\n# engine: macos-say\n# voice: Samantha\n# rate: 190\n`,\n 'modes.yaml': `# A layer is a reusable named bundle of packages, built-in or local extensions,\n# and hook groups. Major modes below choose which layers load together for a session.\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# Add a layer by giving it a unique name under layers. Local Pi scripts belong\n# under extensions, while installed Pi packages belong under packages:\n# review-tools:\n# extensions:\n# - './extensions/local-review.ts'\n# packages:\n# - '@scope/review-extension'\n# A bare extension name selects a DoomPi built-in. A path beginning with ./ or\n# ../ loads a custom script or extension directory. A bare package loads its\n# ordered package.json pi.extensions entries, while an exported subpath selects\n# one adapter. Relative paths resolve beside this file here (~/.pi/.doom), or\n# from the repository root in .doom/modes.yaml.\n#\n# Use a package mapping when that package accepts configuration. Configuration\n# belongs under the package entry that consumes it, not beside packages.\n# DoomPi's Help, Hooks, Goal, Voice, Runner, File Edit, Autocompact, Loop, Plan,\n# Workflow, Log, and MCP packages are distribution defaults. Do not restate\n# them here unless you need to attach package-owned configuration.\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n # Optional Team configuration. Uncomment and choose models available to Pi.\n # config:\n # models:\n # - model: openai-codex/gpt-5.6-luna\n # thinking: xhigh\n # # Tool names excluded from subagents launched by this package. These\n # # remove model-visible tools while preserving hidden team transport.\n # excludeTools: [ask_user_question, intercom, subagent]\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\n# A major mode explains when it should be used and names its ordered layers.\n# Add another entry under majorMode, then select it with\n# doompi --major-mode <name>. Change defaultMajorMode to make it the default.\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with Team delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n # review:\n # description: Focused review mode for inspecting changes and reporting risks.\n # layers: [team, task, ask-user]\n`,\n 'domains.yaml': `# Plugins are named once in this catalog, then selected by one or more domains.\n# DoomPi automatically inspects personal and repository Codex marketplaces.\n# Each configured root may also be a folder whose direct children are plugins;\n# use roots: [plugins] to discover a repository plugin folder without one entry\n# per plugin. Discovery never scans recursively.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\n # Local paths resolve beside this file in ~/.pi/.doom. Repository paths in a\n # repository .doom/domains.yaml resolve from that repository root.\n # entries:\n # development:\n # source: local\n # path: plugins/development\n # remote-review:\n # source: url\n # url: https://github.com/acme/review-plugin.git\n # ref: v1.2.0\n # published-review:\n # source: npm\n # package: '@acme/review-plugin'\n # version: 1.2.0\n# Domains carry descriptions and refer to catalog names, not filesystem paths.\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\n # development:\n # description: TypeScript implementation and review tools.\n # plugins:\n # - development\n # # Mapping form can load only selected resources from a shared plugin.\n # - name: remote-review\n # skills: [typescript]\n # agents: [reviewer]\n # hooks: false\n # mcp: true\n # # Set false only when this domain should omit shared .claude/skills.\n # sharedSkills: false\n# Select it with doompi --domains development, or add it to defaultDomains.\n#\n# Aliases let one selection enable multiple domains. Replace the empty mapping\n# below with entries like the commented example.\naliases: {}\n# aliases:\n# work: [default, development]\n`,\n 'profiles.yaml': `# Profiles select a persona and optional environment defaults for a session.\n# Each configured root may itself be a profile or contain direct-child profile\n# folders. A folder is discovered when it contains profile.md, SOUL.md, or\n# AGENTS.md; discovery never scans recursively.\nprofiles:\n roots: []\n entries: {}\n # Roots resolve beside this file in ~/.pi/.doom. Repository roots in a\n # repository .doom/profiles.yaml resolve from that repository root.\n # roots: [agents/acme]\n # Explicit entries override an automatically discovered folder of the same\n # name. Environment values must be strings; already exported values win.\n # entries:\n # writer:\n # persona: agents/special/writer\n # env:\n # BRAND: acme\n # TONE: concise\n# Doom Pi concatenates profile.md, SOUL.md, then AGENTS.md for the persona.\n# Select one with: doompi --profile writer\n`,\n};\n\n/** Standalone defaults for a repository that is trying DoomPi through DPI. */\nexport const REPOSITORY_DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, string>> = {\n 'config.yaml': DOOM_CONFIG_TEMPLATES['config.yaml'],\n 'modes.yaml': `# Repository-local layers and major modes. These entries override personal\n# entries with the same name from ~/.pi/.doom/modes.yaml.\n# DoomPi's standard distribution packages are already active; declare only\n# additional or configured packages here.\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n`,\n 'domains.yaml': `# Repository-local plugin catalog and domain selection.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\naliases: {}\n`,\n 'profiles.yaml': `# Repository-local persona roots and environment defaults.\nprofiles:\n roots: []\n entries: {}\n`,\n};\n\n/** Result of filling a Doom 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\nexport type RepositoryDoomInitOptions = GlobalDoomInitOptions;\nexport type RepositoryDoomInitResult = GlobalDoomInitResult;\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, mode: number): 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 });\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 ensureDoomDirectory(directory: string, mode: number): void {\n ensureDirectory(path.dirname(directory), mode);\n ensureDirectory(directory, mode);\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, mode: number): boolean {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, 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, mode: number): void {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, 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\nfunction initializeDoomConfig(\n directory: string,\n templates: Readonly<Record<DoomSeedFile, string>>,\n options: GlobalDoomInitOptions,\n directoryMode: number,\n fileMode: number,\n): GlobalDoomInitResult {\n ensureDoomDirectory(directory, directoryMode);\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, templates[fileName], fileMode);\n (existed ? replaced : created).push(fileName);\n continue;\n }\n if (existed) {\n preserved.push(fileName);\n continue;\n }\n if (publishExclusively(targetPath, templates[fileName], fileMode)) created.push(fileName);\n else preserved.push(fileName);\n }\n\n return { directory, created, preserved, replaced };\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 return initializeDoomConfig(\n globalDoomConfigDirectory(homeDirectory),\n DOOM_CONFIG_TEMPLATES,\n options,\n PRIVATE_DIRECTORY_MODE,\n PRIVATE_FILE_MODE,\n );\n}\n\n/** Seeds repository-local configuration without creating or changing Pi settings. */\nexport function initializeRepositoryDoomConfig(\n repositoryRoot = process.cwd(),\n options: RepositoryDoomInitOptions = {},\n): RepositoryDoomInitResult {\n return initializeDoomConfig(\n path.join(path.resolve(repositoryRoot), DOOM_DIRECTORY),\n REPOSITORY_DOOM_CONFIG_TEMPLATES,\n options,\n REPOSITORY_DIRECTORY_MODE,\n REPOSITORY_FILE_MODE,\n );\n}\n"],"mappings":"wOAOA,MAKM,EAAgB,OAChB,EAAsB,SACtB,EAAiB,SAGV,EAAyB,CAAC,cAAe,aAAc,eAAgB,eAAe,EAKtF,EAAgE,CAC3E,cAAe;;;;;;;;;;;;;;;EAgBf,aAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0Dd,eAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgDhB,gBAAiB;;;;;;;;;;;;;;;;;;;;CAqBnB,EAGa,EAA2E,CACtF,cAAe,EAAsB,eACrC,aAAc;;;;;;;;;;;;;;;;;;;;;;EAuBd,eAAgB;;;;;;;;;;EAWhB,gBAAiB;;;;CAKnB,EAwBA,SAAS,EAAU,EAAoC,CACrD,GAAI,EAAE,aAAiB,OAAQ,OAC/B,IAAM,EAAQ,EAAgC,KAC9C,OAAO,OAAO,GAAS,SAAW,EAAO,IAAA,EAC3C,CAEA,SAAS,EAAgB,EAAmB,EAAoB,CAC9D,IAAI,EACJ,GAAI,CACF,EAAQA,EAAAA,QAAG,UAAU,CAAS,CAChC,OAAS,EAAO,CACd,GAAI,EAAU,CAAK,IAAM,EAAgB,MAAM,EAC/C,GAAI,CACF,EAAA,QAAG,UAAU,EAAW,CAAE,MAAK,CAAC,EAChC,EAAQA,EAAAA,QAAG,UAAU,CAAS,CAChC,OAAS,EAAY,CACnB,GAAI,EAAU,CAAU,IAAM,EAAqB,MAAM,EACzD,EAAQA,EAAAA,QAAG,UAAU,CAAS,CAChC,CACF,CAEA,GAAI,EAAM,eAAe,EACvB,MAAU,MAAM,uDAAuD,GAAW,EAEpF,GAAI,CAAC,EAAM,YAAY,EACrB,MAAU,MAAM,wCAAwC,GAAW,CAEvE,CAEA,SAAS,EAAoB,EAAmB,EAAoB,CAClE,EAAgBC,EAAAA,QAAK,QAAQ,CAAS,EAAG,CAAI,EAC7C,EAAgB,EAAW,CAAI,CACjC,CAEA,SAAS,EAAe,EAAwC,CAC9D,IAAI,EACJ,GAAI,CACF,EAAQD,EAAAA,QAAG,UAAU,CAAU,CACjC,OAAS,EAAO,CACd,GAAI,EAAU,CAAK,IAAM,EAAgB,MAAO,UAChD,MAAM,CACR,CAEA,GAAI,EAAM,OAAO,EAAG,MAAO,OAC3B,GAAI,EAAM,eAAe,EACvB,GAAI,CACF,GAAIA,EAAAA,QAAG,SAAS,CAAU,CAAC,CAAC,OAAO,EAAG,MAAO,MAC/C,OAAS,EAAO,CACd,MAAU,MAAM,8CAA8C,IAAc,CAAE,MAAO,CAAM,CAAC,CAC9F,CAEF,MAAU,MAAM,2CAA2C,GAAY,CACzE,CAEA,SAAS,EAAqB,EAA6B,CACzD,GAAI,CACF,EAAA,QAAG,WAAW,CAAa,CAC7B,OAAS,EAAO,CACd,GAAI,EAAU,CAAK,IAAM,EAAgB,MAAM,CACjD,CACF,CAEA,SAAS,EAAmB,EAAoB,EAAiB,EAAuB,CACtF,IAAM,EAAgB,GAAG,EAAW,GAAG,QAAQ,IAAI,IAAA,EAAGE,EAAAA,WAAAA,CAAW,EAAE,MAC/D,EACJ,GAAI,CACF,EAAiBF,EAAAA,QAAG,SAAS,EAAe,KAAM,CAAI,EACtD,EAAA,QAAG,cAAc,EAAgB,EAAS,CAAa,EACvD,EAAA,QAAG,WAAW,EAAgB,CAAI,EAClC,EAAA,QAAG,UAAU,CAAc,EAC3B,IAAM,EAAoB,EAC1B,EAAiB,IAAA,GACjB,EAAA,QAAG,UAAU,CAAiB,EAE9B,GAAI,CAEF,OADA,EAAA,QAAG,SAAS,EAAe,CAAU,EAC9B,EACT,OAAS,EAAO,CACd,GAAI,EAAU,CAAK,IAAM,GAAuB,EAAe,CAAU,IAAM,OAAQ,MAAO,GAC9F,MAAM,CACR,CACF,QAAU,CACR,GAAI,IAAmB,IAAA,GAAW,CAChC,IAAM,EAAoB,EAC1B,EAAiB,IAAA,GACjB,EAAA,QAAG,UAAU,CAAiB,CAChC,CACA,EAAqB,CAAa,CACpC,CACF,CASA,SAAS,EAAkB,EAAoB,EAAiB,EAAoB,CAClF,IAAM,EAAgB,GAAG,EAAW,GAAG,QAAQ,IAAI,IAAA,EAAGE,EAAAA,WAAAA,CAAW,EAAE,MAC/D,EACJ,GAAI,CACF,EAAiBF,EAAAA,QAAG,SAAS,EAAe,KAAM,CAAI,EACtD,EAAA,QAAG,cAAc,EAAgB,EAAS,CAAa,EACvD,EAAA,QAAG,WAAW,EAAgB,CAAI,EAClC,EAAA,QAAG,UAAU,CAAc,EAC3B,IAAM,EAAoB,EAC1B,EAAiB,IAAA,GACjB,EAAA,QAAG,UAAU,CAAiB,EAE9B,EAAA,QAAG,WAAW,EAAe,CAAU,CACzC,QAAU,CACR,GAAI,IAAmB,IAAA,GAAW,CAChC,IAAM,EAAoB,EAC1B,EAAiB,IAAA,GACjB,EAAA,QAAG,UAAU,CAAiB,CAChC,CACA,EAAqB,CAAa,CACpC,CACF,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EACsB,CACtB,EAAoB,EAAW,CAAa,EAE5C,IAAM,EAAoB,CAAC,EACrB,EAAsB,CAAC,EACvB,EAAqB,CAAC,EAC5B,IAAK,IAAM,KAAY,EAAwB,CAC7C,IAAM,EAAaC,EAAAA,QAAK,KAAK,EAAW,CAAQ,EAC1C,EAAU,EAAe,CAAU,IAAM,OAE/C,GAAI,EAAQ,MAAO,CACjB,EAAkB,EAAY,EAAU,GAAW,CAAQ,GAC1D,EAAU,EAAW,EAAA,CAAS,KAAK,CAAQ,EAC5C,QACF,CACA,GAAI,EAAS,CACX,EAAU,KAAK,CAAQ,EACvB,QACF,CACI,EAAmB,EAAY,EAAU,GAAW,CAAQ,EAAG,EAAQ,KAAK,CAAQ,EACnF,EAAU,KAAK,CAAQ,CAC9B,CAEA,MAAO,CAAE,YAAW,UAAS,YAAW,UAAS,CACnD,CAQA,SAAgB,EACd,EAAgBE,EAAAA,QAAG,QAAQ,EAC3B,EAAiC,CAAC,EACZ,CACtB,OAAO,EACLC,EAAAA,0BAA0B,CAAa,EACvC,EACA,EACA,IACA,GACF,CACF,CAGA,SAAgB,EACd,EAAiB,QAAQ,IAAI,EAC7B,EAAqC,CAAC,EACZ,CAC1B,OAAO,EACLH,EAAAA,QAAK,KAAKA,EAAAA,QAAK,QAAQ,CAAc,EAAG,OAAc,EACtD,EACA,EACA,IACA,GACF,CACF"}
1
+ {"version":3,"file":"init.cjs","names":["fs","path","randomUUID","os","globalDoomConfigDirectory"],"sources":["../../src/adapters/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 REPOSITORY_DIRECTORY_MODE = 0o755;\nconst REPOSITORY_FILE_MODE = 0o644;\nconst DOOM_DIRECTORY = '.doom';\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 = ['config.yaml', 'modes.yaml', 'domains.yaml', 'profiles.yaml'] as const;\n\ntype DoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];\n\n/** Portable defaults for a new global Doom configuration directory. */\nexport const DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, 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# utteranceIdleMs: 3000\n# tts:\n# engine: macos-say\n# voice: Samantha\n# rate: 190\n`,\n 'modes.yaml': `# A layer is a reusable named bundle of packages, built-in or local extensions,\n# and hook groups. Major modes below choose which layers load together for a session.\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# Add a layer by giving it a unique name under layers. Local Pi scripts belong\n# under extensions, while installed Pi packages belong under packages:\n# review-tools:\n# extensions:\n# - './extensions/local-review.ts'\n# packages:\n# - '@scope/review-extension'\n# A bare extension name selects a DoomPi built-in. A path beginning with ./ or\n# ../ loads a custom script or extension directory. A bare package loads its\n# ordered package.json pi.extensions entries, while an exported subpath selects\n# one adapter. Relative paths resolve beside this file here (~/.pi/.doom), or\n# from the repository root in .doom/modes.yaml.\n#\n# Use a package mapping when that package accepts configuration. Configuration\n# belongs under the package entry that consumes it, not beside packages.\n# The top-level default bundle loads before every major mode. Replace this whole\n# packages list to customize the distribution defaults. DoomPi core packages\n# remain active independently of this list.\ndefault:\n packages:\n - '@agimon-ai/doompi-help'\n - '@agimon-ai/doompi-hook'\n - '@agimon-ai/doompi-goal'\n - '@agimon-ai/doompi-voice'\n - '@agimon-ai/doompi-runner'\n - '@agimon-ai/doompi-file-edit'\n - '@agimon-ai/doompi-autocompact'\n - '@agimon-ai/doompi-loop'\n - '@agimon-ai/doompi-plan'\n - '@agimon-ai/doompi-workflow'\n - '@agimon-ai/doompi-log'\n - '@agimon-ai/doompi-mcp'\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n # Optional Team configuration. Uncomment and choose models available to Pi.\n # config:\n # models:\n # - model: openai-codex/gpt-5.6-luna\n # thinking: xhigh\n # # Tool names excluded from subagents launched by this package. These\n # # remove model-visible tools while preserving hidden team transport.\n # excludeTools: [ask_user_question, intercom, subagent]\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\n# A major mode explains when it should be used and names its ordered layers.\n# Add another entry under majorMode, then select it with\n# doompi --major-mode <name>. Change defaultMajorMode to make it the default.\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with Team delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n # review:\n # description: Focused review mode for inspecting changes and reporting risks.\n # layers: [team, task, ask-user]\n`,\n 'domains.yaml': `# Plugins are named once in this catalog, then selected by one or more domains.\n# DoomPi automatically inspects personal and repository Codex marketplaces.\n# Each configured root may also be a folder whose direct children are plugins;\n# use roots: [plugins] to discover a repository plugin folder without one entry\n# per plugin. Discovery never scans recursively.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\n # Local paths resolve beside this file in ~/.pi/.doom. Repository paths in a\n # repository .doom/domains.yaml resolve from that repository root.\n # entries:\n # development:\n # source: local\n # path: plugins/development\n # remote-review:\n # source: url\n # url: https://github.com/acme/review-plugin.git\n # ref: v1.2.0\n # published-review:\n # source: npm\n # package: '@acme/review-plugin'\n # version: 1.2.0\n# Domains carry descriptions and refer to catalog names, not filesystem paths.\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\n # development:\n # description: TypeScript implementation and review tools.\n # plugins:\n # - development\n # # Mapping form can load only selected resources from a shared plugin.\n # - name: remote-review\n # skills: [typescript]\n # agents: [reviewer]\n # hooks: false\n # mcp: true\n # # Set false only when this domain should omit shared .claude/skills.\n # sharedSkills: false\n# Select it with doompi --domains development, or add it to defaultDomains.\n#\n# Aliases let one selection enable multiple domains. Replace the empty mapping\n# below with entries like the commented example.\naliases: {}\n# aliases:\n# work: [default, development]\n`,\n 'profiles.yaml': `# Profiles select a persona and optional environment defaults for a session.\n# Each configured root may itself be a profile or contain direct-child profile\n# folders. A folder is discovered when it contains profile.md, SOUL.md, or\n# AGENTS.md; discovery never scans recursively.\nprofiles:\n roots: []\n entries: {}\n # Roots resolve beside this file in ~/.pi/.doom. Repository roots in a\n # repository .doom/profiles.yaml resolve from that repository root.\n # roots: [agents/acme]\n # Explicit entries override an automatically discovered folder of the same\n # name. Environment values must be strings; already exported values win.\n # entries:\n # writer:\n # persona: agents/special/writer\n # env:\n # BRAND: acme\n # TONE: concise\n# Doom Pi concatenates profile.md, SOUL.md, then AGENTS.md for the persona.\n# Select one with: doompi --profile writer\n`,\n};\n\n/** Standalone defaults for a repository that is trying DoomPi through DPI. */\nexport const REPOSITORY_DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, string>> = {\n 'config.yaml': DOOM_CONFIG_TEMPLATES['config.yaml'],\n 'modes.yaml': `# Repository-local layers and major modes. These entries override personal\n# entries with the same name from ~/.pi/.doom/modes.yaml.\n# This repository-local default replaces the personal top-level default as one\n# whole package list. Edit it to customize the standalone package baseline.\ndefault:\n packages:\n - '@agimon-ai/doompi-help'\n - '@agimon-ai/doompi-hook'\n - '@agimon-ai/doompi-goal'\n - '@agimon-ai/doompi-voice'\n - '@agimon-ai/doompi-runner'\n - '@agimon-ai/doompi-file-edit'\n - '@agimon-ai/doompi-autocompact'\n - '@agimon-ai/doompi-loop'\n - '@agimon-ai/doompi-plan'\n - '@agimon-ai/doompi-workflow'\n - '@agimon-ai/doompi-log'\n - '@agimon-ai/doompi-mcp'\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n`,\n 'domains.yaml': `# Repository-local plugin catalog and domain selection.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\naliases: {}\n`,\n 'profiles.yaml': `# Repository-local persona roots and environment defaults.\nprofiles:\n roots: []\n entries: {}\n`,\n};\n\n/** Result of filling a Doom 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\nexport type RepositoryDoomInitOptions = GlobalDoomInitOptions;\nexport type RepositoryDoomInitResult = GlobalDoomInitResult;\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, mode: number): 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 });\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 ensureDoomDirectory(directory: string, mode: number): void {\n ensureDirectory(path.dirname(directory), mode);\n ensureDirectory(directory, mode);\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, mode: number): boolean {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, 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, mode: number): void {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, 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\nfunction initializeDoomConfig(\n directory: string,\n templates: Readonly<Record<DoomSeedFile, string>>,\n options: GlobalDoomInitOptions,\n directoryMode: number,\n fileMode: number,\n): GlobalDoomInitResult {\n ensureDoomDirectory(directory, directoryMode);\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, templates[fileName], fileMode);\n (existed ? replaced : created).push(fileName);\n continue;\n }\n if (existed) {\n preserved.push(fileName);\n continue;\n }\n if (publishExclusively(targetPath, templates[fileName], fileMode)) created.push(fileName);\n else preserved.push(fileName);\n }\n\n return { directory, created, preserved, replaced };\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 return initializeDoomConfig(\n globalDoomConfigDirectory(homeDirectory),\n DOOM_CONFIG_TEMPLATES,\n options,\n PRIVATE_DIRECTORY_MODE,\n PRIVATE_FILE_MODE,\n );\n}\n\n/** Seeds repository-local configuration without creating or changing Pi settings. */\nexport function initializeRepositoryDoomConfig(\n repositoryRoot = process.cwd(),\n options: RepositoryDoomInitOptions = {},\n): RepositoryDoomInitResult {\n return initializeDoomConfig(\n path.join(path.resolve(repositoryRoot), DOOM_DIRECTORY),\n REPOSITORY_DOOM_CONFIG_TEMPLATES,\n options,\n REPOSITORY_DIRECTORY_MODE,\n REPOSITORY_FILE_MODE,\n );\n}\n"],"mappings":"wOAOA,MAKM,EAAgB,OAChB,EAAsB,SACtB,EAAiB,SAGV,EAAyB,CAAC,cAAe,aAAc,eAAgB,eAAe,EAKtF,EAAgE,CAC3E,cAAe;;;;;;;;;;;;;;;EAgBf,aAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwEd,eAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgDhB,gBAAiB;;;;;;;;;;;;;;;;;;;;CAqBnB,EAGa,EAA2E,CACtF,cAAe,EAAsB,eACrC,aAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqCd,eAAgB;;;;;;;;;;EAWhB,gBAAiB;;;;CAKnB,EAwBA,SAAS,EAAU,EAAoC,CACrD,GAAI,EAAE,aAAiB,OAAQ,OAC/B,IAAM,EAAQ,EAAgC,KAC9C,OAAO,OAAO,GAAS,SAAW,EAAO,IAAA,EAC3C,CAEA,SAAS,EAAgB,EAAmB,EAAoB,CAC9D,IAAI,EACJ,GAAI,CACF,EAAQA,EAAAA,QAAG,UAAU,CAAS,CAChC,OAAS,EAAO,CACd,GAAI,EAAU,CAAK,IAAM,EAAgB,MAAM,EAC/C,GAAI,CACF,EAAA,QAAG,UAAU,EAAW,CAAE,MAAK,CAAC,EAChC,EAAQA,EAAAA,QAAG,UAAU,CAAS,CAChC,OAAS,EAAY,CACnB,GAAI,EAAU,CAAU,IAAM,EAAqB,MAAM,EACzD,EAAQA,EAAAA,QAAG,UAAU,CAAS,CAChC,CACF,CAEA,GAAI,EAAM,eAAe,EACvB,MAAU,MAAM,uDAAuD,GAAW,EAEpF,GAAI,CAAC,EAAM,YAAY,EACrB,MAAU,MAAM,wCAAwC,GAAW,CAEvE,CAEA,SAAS,EAAoB,EAAmB,EAAoB,CAClE,EAAgBC,EAAAA,QAAK,QAAQ,CAAS,EAAG,CAAI,EAC7C,EAAgB,EAAW,CAAI,CACjC,CAEA,SAAS,EAAe,EAAwC,CAC9D,IAAI,EACJ,GAAI,CACF,EAAQD,EAAAA,QAAG,UAAU,CAAU,CACjC,OAAS,EAAO,CACd,GAAI,EAAU,CAAK,IAAM,EAAgB,MAAO,UAChD,MAAM,CACR,CAEA,GAAI,EAAM,OAAO,EAAG,MAAO,OAC3B,GAAI,EAAM,eAAe,EACvB,GAAI,CACF,GAAIA,EAAAA,QAAG,SAAS,CAAU,CAAC,CAAC,OAAO,EAAG,MAAO,MAC/C,OAAS,EAAO,CACd,MAAU,MAAM,8CAA8C,IAAc,CAAE,MAAO,CAAM,CAAC,CAC9F,CAEF,MAAU,MAAM,2CAA2C,GAAY,CACzE,CAEA,SAAS,EAAqB,EAA6B,CACzD,GAAI,CACF,EAAA,QAAG,WAAW,CAAa,CAC7B,OAAS,EAAO,CACd,GAAI,EAAU,CAAK,IAAM,EAAgB,MAAM,CACjD,CACF,CAEA,SAAS,EAAmB,EAAoB,EAAiB,EAAuB,CACtF,IAAM,EAAgB,GAAG,EAAW,GAAG,QAAQ,IAAI,IAAA,EAAGE,EAAAA,WAAAA,CAAW,EAAE,MAC/D,EACJ,GAAI,CACF,EAAiBF,EAAAA,QAAG,SAAS,EAAe,KAAM,CAAI,EACtD,EAAA,QAAG,cAAc,EAAgB,EAAS,CAAa,EACvD,EAAA,QAAG,WAAW,EAAgB,CAAI,EAClC,EAAA,QAAG,UAAU,CAAc,EAC3B,IAAM,EAAoB,EAC1B,EAAiB,IAAA,GACjB,EAAA,QAAG,UAAU,CAAiB,EAE9B,GAAI,CAEF,OADA,EAAA,QAAG,SAAS,EAAe,CAAU,EAC9B,EACT,OAAS,EAAO,CACd,GAAI,EAAU,CAAK,IAAM,GAAuB,EAAe,CAAU,IAAM,OAAQ,MAAO,GAC9F,MAAM,CACR,CACF,QAAU,CACR,GAAI,IAAmB,IAAA,GAAW,CAChC,IAAM,EAAoB,EAC1B,EAAiB,IAAA,GACjB,EAAA,QAAG,UAAU,CAAiB,CAChC,CACA,EAAqB,CAAa,CACpC,CACF,CASA,SAAS,EAAkB,EAAoB,EAAiB,EAAoB,CAClF,IAAM,EAAgB,GAAG,EAAW,GAAG,QAAQ,IAAI,IAAA,EAAGE,EAAAA,WAAAA,CAAW,EAAE,MAC/D,EACJ,GAAI,CACF,EAAiBF,EAAAA,QAAG,SAAS,EAAe,KAAM,CAAI,EACtD,EAAA,QAAG,cAAc,EAAgB,EAAS,CAAa,EACvD,EAAA,QAAG,WAAW,EAAgB,CAAI,EAClC,EAAA,QAAG,UAAU,CAAc,EAC3B,IAAM,EAAoB,EAC1B,EAAiB,IAAA,GACjB,EAAA,QAAG,UAAU,CAAiB,EAE9B,EAAA,QAAG,WAAW,EAAe,CAAU,CACzC,QAAU,CACR,GAAI,IAAmB,IAAA,GAAW,CAChC,IAAM,EAAoB,EAC1B,EAAiB,IAAA,GACjB,EAAA,QAAG,UAAU,CAAiB,CAChC,CACA,EAAqB,CAAa,CACpC,CACF,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EACsB,CACtB,EAAoB,EAAW,CAAa,EAE5C,IAAM,EAAoB,CAAC,EACrB,EAAsB,CAAC,EACvB,EAAqB,CAAC,EAC5B,IAAK,IAAM,KAAY,EAAwB,CAC7C,IAAM,EAAaC,EAAAA,QAAK,KAAK,EAAW,CAAQ,EAC1C,EAAU,EAAe,CAAU,IAAM,OAE/C,GAAI,EAAQ,MAAO,CACjB,EAAkB,EAAY,EAAU,GAAW,CAAQ,GAC1D,EAAU,EAAW,EAAA,CAAS,KAAK,CAAQ,EAC5C,QACF,CACA,GAAI,EAAS,CACX,EAAU,KAAK,CAAQ,EACvB,QACF,CACI,EAAmB,EAAY,EAAU,GAAW,CAAQ,EAAG,EAAQ,KAAK,CAAQ,EACnF,EAAU,KAAK,CAAQ,CAC9B,CAEA,MAAO,CAAE,YAAW,UAAS,YAAW,UAAS,CACnD,CAQA,SAAgB,EACd,EAAgBE,EAAAA,QAAG,QAAQ,EAC3B,EAAiC,CAAC,EACZ,CACtB,OAAO,EACLC,EAAAA,0BAA0B,CAAa,EACvC,EACA,EACA,IACA,GACF,CACF,CAGA,SAAgB,EACd,EAAiB,QAAQ,IAAI,EAC7B,EAAqC,CAAC,EACZ,CAC1B,OAAO,EACLH,EAAAA,QAAK,KAAKA,EAAAA,QAAK,QAAQ,CAAc,EAAG,OAAc,EACtD,EACA,EACA,IACA,GACF,CACF"}
@@ -1,6 +1,6 @@
1
1
  //#region src/adapters/init.d.ts
2
2
  /** The global seed files are processed in this fixed order. */
3
- declare const GLOBAL_DOOM_SEED_FILES: readonly ["config.yaml", "modes.yaml", "domains.yaml", "profiles.yaml"];
3
+ declare const GLOBAL_DOOM_SEED_FILES: readonly ['config.yaml', 'modes.yaml', 'domains.yaml', 'profiles.yaml'];
4
4
  type DoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];
5
5
  /** Portable defaults for a new global Doom configuration directory. */
6
6
  declare const DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, string>>;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.cts","names":[],"sources":["../../src/adapters/init.ts"],"mappings":";;cAiBa;KAER,uBAAuB;;cAGf,uBAAuB,SAAS,OAAO;;cAmJvC,kCAAkC,SAAS,OAAO;;UA4C9C;EACf;EACA;EACA;;EAEA;;UAGe;;;;;;;EAOf;;KAGU,4BAA4B;KAC5B,2BAA2B;;;;;;;iBAiKvB,2BACd,wBACA,UAAS,wBACR;;iBAWa,+BACd,yBACA,UAAS,4BACR"}
1
+ {"version":3,"file":"init.d.cts","names":[],"sources":["../../src/adapters/init.ts"],"mappings":";;cAiBa;KAER,uBAAuB;;cAGf,uBAAuB,SAAS,OAAO;;cAiKvC,kCAAkC,SAAS,OAAO;;UA0D9C;EACf;EACA;EACA;;EAEA;;UAGe;;;;;;;EAOf;;KAGU,4BAA4B;KAC5B,2BAA2B;;;;;;;iBAiKvB,2BACd,wBACA,UAAS,wBACR;;iBAWa,+BACd,yBACA,UAAS,4BACR"}
@@ -1,6 +1,6 @@
1
1
  //#region src/adapters/init.d.ts
2
2
  /** The global seed files are processed in this fixed order. */
3
- declare const GLOBAL_DOOM_SEED_FILES: readonly ["config.yaml", "modes.yaml", "domains.yaml", "profiles.yaml"];
3
+ declare const GLOBAL_DOOM_SEED_FILES: readonly ['config.yaml', 'modes.yaml', 'domains.yaml', 'profiles.yaml'];
4
4
  type DoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];
5
5
  /** Portable defaults for a new global Doom configuration directory. */
6
6
  declare const DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, string>>;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.mts","names":[],"sources":["../../src/adapters/init.ts"],"mappings":";;cAiBa;KAER,uBAAuB;;cAGf,uBAAuB,SAAS,OAAO;;cAmJvC,kCAAkC,SAAS,OAAO;;UA4C9C;EACf;EACA;EACA;;EAEA;;UAGe;;;;;;;EAOf;;KAGU,4BAA4B;KAC5B,2BAA2B;;;;;;;iBAiKvB,2BACd,wBACA,UAAS,wBACR;;iBAWa,+BACd,yBACA,UAAS,4BACR"}
1
+ {"version":3,"file":"init.d.mts","names":[],"sources":["../../src/adapters/init.ts"],"mappings":";;cAiBa;KAER,uBAAuB;;cAGf,uBAAuB,SAAS,OAAO;;cAiKvC,kCAAkC,SAAS,OAAO;;UA0D9C;EACf;EACA;EACA;;EAEA;;UAGe;;;;;;;EAOf;;KAGU,4BAA4B;KAC5B,2BAA2B;;;;;;;iBAiKvB,2BACd,wBACA,UAAS,wBACR;;iBAWa,+BACd,yBACA,UAAS,4BACR"}
@@ -35,9 +35,23 @@ import{globalDoomConfigDirectory as e}from"./config.mjs";import{randomUUID as t}
35
35
  #
36
36
  # Use a package mapping when that package accepts configuration. Configuration
37
37
  # belongs under the package entry that consumes it, not beside packages.
38
- # DoomPi's Help, Hooks, Goal, Voice, Runner, File Edit, Autocompact, Loop, Plan,
39
- # Workflow, Log, and MCP packages are distribution defaults. Do not restate
40
- # them here unless you need to attach package-owned configuration.
38
+ # The top-level default bundle loads before every major mode. Replace this whole
39
+ # packages list to customize the distribution defaults. DoomPi core packages
40
+ # remain active independently of this list.
41
+ default:
42
+ packages:
43
+ - '@agimon-ai/doompi-help'
44
+ - '@agimon-ai/doompi-hook'
45
+ - '@agimon-ai/doompi-goal'
46
+ - '@agimon-ai/doompi-voice'
47
+ - '@agimon-ai/doompi-runner'
48
+ - '@agimon-ai/doompi-file-edit'
49
+ - '@agimon-ai/doompi-autocompact'
50
+ - '@agimon-ai/doompi-loop'
51
+ - '@agimon-ai/doompi-plan'
52
+ - '@agimon-ai/doompi-workflow'
53
+ - '@agimon-ai/doompi-log'
54
+ - '@agimon-ai/doompi-mcp'
41
55
  layers:
42
56
  team:
43
57
  packages:
@@ -139,8 +153,22 @@ profiles:
139
153
  # Select one with: doompi --profile writer
140
154
  `},u={"config.yaml":l[`config.yaml`],"modes.yaml":`# Repository-local layers and major modes. These entries override personal
141
155
  # entries with the same name from ~/.pi/.doom/modes.yaml.
142
- # DoomPi's standard distribution packages are already active; declare only
143
- # additional or configured packages here.
156
+ # This repository-local default replaces the personal top-level default as one
157
+ # whole package list. Edit it to customize the standalone package baseline.
158
+ default:
159
+ packages:
160
+ - '@agimon-ai/doompi-help'
161
+ - '@agimon-ai/doompi-hook'
162
+ - '@agimon-ai/doompi-goal'
163
+ - '@agimon-ai/doompi-voice'
164
+ - '@agimon-ai/doompi-runner'
165
+ - '@agimon-ai/doompi-file-edit'
166
+ - '@agimon-ai/doompi-autocompact'
167
+ - '@agimon-ai/doompi-loop'
168
+ - '@agimon-ai/doompi-plan'
169
+ - '@agimon-ai/doompi-workflow'
170
+ - '@agimon-ai/doompi-log'
171
+ - '@agimon-ai/doompi-mcp'
144
172
  layers:
145
173
  team:
146
174
  packages:
@@ -1 +1 @@
1
- {"version":3,"file":"init.mjs","names":[],"sources":["../../src/adapters/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 REPOSITORY_DIRECTORY_MODE = 0o755;\nconst REPOSITORY_FILE_MODE = 0o644;\nconst DOOM_DIRECTORY = '.doom';\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 = ['config.yaml', 'modes.yaml', 'domains.yaml', 'profiles.yaml'] as const;\n\ntype DoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];\n\n/** Portable defaults for a new global Doom configuration directory. */\nexport const DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, 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# utteranceIdleMs: 3000\n# tts:\n# engine: macos-say\n# voice: Samantha\n# rate: 190\n`,\n 'modes.yaml': `# A layer is a reusable named bundle of packages, built-in or local extensions,\n# and hook groups. Major modes below choose which layers load together for a session.\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# Add a layer by giving it a unique name under layers. Local Pi scripts belong\n# under extensions, while installed Pi packages belong under packages:\n# review-tools:\n# extensions:\n# - './extensions/local-review.ts'\n# packages:\n# - '@scope/review-extension'\n# A bare extension name selects a DoomPi built-in. A path beginning with ./ or\n# ../ loads a custom script or extension directory. A bare package loads its\n# ordered package.json pi.extensions entries, while an exported subpath selects\n# one adapter. Relative paths resolve beside this file here (~/.pi/.doom), or\n# from the repository root in .doom/modes.yaml.\n#\n# Use a package mapping when that package accepts configuration. Configuration\n# belongs under the package entry that consumes it, not beside packages.\n# DoomPi's Help, Hooks, Goal, Voice, Runner, File Edit, Autocompact, Loop, Plan,\n# Workflow, Log, and MCP packages are distribution defaults. Do not restate\n# them here unless you need to attach package-owned configuration.\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n # Optional Team configuration. Uncomment and choose models available to Pi.\n # config:\n # models:\n # - model: openai-codex/gpt-5.6-luna\n # thinking: xhigh\n # # Tool names excluded from subagents launched by this package. These\n # # remove model-visible tools while preserving hidden team transport.\n # excludeTools: [ask_user_question, intercom, subagent]\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\n# A major mode explains when it should be used and names its ordered layers.\n# Add another entry under majorMode, then select it with\n# doompi --major-mode <name>. Change defaultMajorMode to make it the default.\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with Team delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n # review:\n # description: Focused review mode for inspecting changes and reporting risks.\n # layers: [team, task, ask-user]\n`,\n 'domains.yaml': `# Plugins are named once in this catalog, then selected by one or more domains.\n# DoomPi automatically inspects personal and repository Codex marketplaces.\n# Each configured root may also be a folder whose direct children are plugins;\n# use roots: [plugins] to discover a repository plugin folder without one entry\n# per plugin. Discovery never scans recursively.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\n # Local paths resolve beside this file in ~/.pi/.doom. Repository paths in a\n # repository .doom/domains.yaml resolve from that repository root.\n # entries:\n # development:\n # source: local\n # path: plugins/development\n # remote-review:\n # source: url\n # url: https://github.com/acme/review-plugin.git\n # ref: v1.2.0\n # published-review:\n # source: npm\n # package: '@acme/review-plugin'\n # version: 1.2.0\n# Domains carry descriptions and refer to catalog names, not filesystem paths.\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\n # development:\n # description: TypeScript implementation and review tools.\n # plugins:\n # - development\n # # Mapping form can load only selected resources from a shared plugin.\n # - name: remote-review\n # skills: [typescript]\n # agents: [reviewer]\n # hooks: false\n # mcp: true\n # # Set false only when this domain should omit shared .claude/skills.\n # sharedSkills: false\n# Select it with doompi --domains development, or add it to defaultDomains.\n#\n# Aliases let one selection enable multiple domains. Replace the empty mapping\n# below with entries like the commented example.\naliases: {}\n# aliases:\n# work: [default, development]\n`,\n 'profiles.yaml': `# Profiles select a persona and optional environment defaults for a session.\n# Each configured root may itself be a profile or contain direct-child profile\n# folders. A folder is discovered when it contains profile.md, SOUL.md, or\n# AGENTS.md; discovery never scans recursively.\nprofiles:\n roots: []\n entries: {}\n # Roots resolve beside this file in ~/.pi/.doom. Repository roots in a\n # repository .doom/profiles.yaml resolve from that repository root.\n # roots: [agents/acme]\n # Explicit entries override an automatically discovered folder of the same\n # name. Environment values must be strings; already exported values win.\n # entries:\n # writer:\n # persona: agents/special/writer\n # env:\n # BRAND: acme\n # TONE: concise\n# Doom Pi concatenates profile.md, SOUL.md, then AGENTS.md for the persona.\n# Select one with: doompi --profile writer\n`,\n};\n\n/** Standalone defaults for a repository that is trying DoomPi through DPI. */\nexport const REPOSITORY_DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, string>> = {\n 'config.yaml': DOOM_CONFIG_TEMPLATES['config.yaml'],\n 'modes.yaml': `# Repository-local layers and major modes. These entries override personal\n# entries with the same name from ~/.pi/.doom/modes.yaml.\n# DoomPi's standard distribution packages are already active; declare only\n# additional or configured packages here.\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n`,\n 'domains.yaml': `# Repository-local plugin catalog and domain selection.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\naliases: {}\n`,\n 'profiles.yaml': `# Repository-local persona roots and environment defaults.\nprofiles:\n roots: []\n entries: {}\n`,\n};\n\n/** Result of filling a Doom 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\nexport type RepositoryDoomInitOptions = GlobalDoomInitOptions;\nexport type RepositoryDoomInitResult = GlobalDoomInitResult;\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, mode: number): 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 });\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 ensureDoomDirectory(directory: string, mode: number): void {\n ensureDirectory(path.dirname(directory), mode);\n ensureDirectory(directory, mode);\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, mode: number): boolean {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, 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, mode: number): void {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, 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\nfunction initializeDoomConfig(\n directory: string,\n templates: Readonly<Record<DoomSeedFile, string>>,\n options: GlobalDoomInitOptions,\n directoryMode: number,\n fileMode: number,\n): GlobalDoomInitResult {\n ensureDoomDirectory(directory, directoryMode);\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, templates[fileName], fileMode);\n (existed ? replaced : created).push(fileName);\n continue;\n }\n if (existed) {\n preserved.push(fileName);\n continue;\n }\n if (publishExclusively(targetPath, templates[fileName], fileMode)) created.push(fileName);\n else preserved.push(fileName);\n }\n\n return { directory, created, preserved, replaced };\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 return initializeDoomConfig(\n globalDoomConfigDirectory(homeDirectory),\n DOOM_CONFIG_TEMPLATES,\n options,\n PRIVATE_DIRECTORY_MODE,\n PRIVATE_FILE_MODE,\n );\n}\n\n/** Seeds repository-local configuration without creating or changing Pi settings. */\nexport function initializeRepositoryDoomConfig(\n repositoryRoot = process.cwd(),\n options: RepositoryDoomInitOptions = {},\n): RepositoryDoomInitResult {\n return initializeDoomConfig(\n path.join(path.resolve(repositoryRoot), DOOM_DIRECTORY),\n REPOSITORY_DOOM_CONFIG_TEMPLATES,\n options,\n REPOSITORY_DIRECTORY_MODE,\n REPOSITORY_FILE_MODE,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"init.mjs","names":[],"sources":["../../src/adapters/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 REPOSITORY_DIRECTORY_MODE = 0o755;\nconst REPOSITORY_FILE_MODE = 0o644;\nconst DOOM_DIRECTORY = '.doom';\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 = ['config.yaml', 'modes.yaml', 'domains.yaml', 'profiles.yaml'] as const;\n\ntype DoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];\n\n/** Portable defaults for a new global Doom configuration directory. */\nexport const DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, 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# utteranceIdleMs: 3000\n# tts:\n# engine: macos-say\n# voice: Samantha\n# rate: 190\n`,\n 'modes.yaml': `# A layer is a reusable named bundle of packages, built-in or local extensions,\n# and hook groups. Major modes below choose which layers load together for a session.\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# Add a layer by giving it a unique name under layers. Local Pi scripts belong\n# under extensions, while installed Pi packages belong under packages:\n# review-tools:\n# extensions:\n# - './extensions/local-review.ts'\n# packages:\n# - '@scope/review-extension'\n# A bare extension name selects a DoomPi built-in. A path beginning with ./ or\n# ../ loads a custom script or extension directory. A bare package loads its\n# ordered package.json pi.extensions entries, while an exported subpath selects\n# one adapter. Relative paths resolve beside this file here (~/.pi/.doom), or\n# from the repository root in .doom/modes.yaml.\n#\n# Use a package mapping when that package accepts configuration. Configuration\n# belongs under the package entry that consumes it, not beside packages.\n# The top-level default bundle loads before every major mode. Replace this whole\n# packages list to customize the distribution defaults. DoomPi core packages\n# remain active independently of this list.\ndefault:\n packages:\n - '@agimon-ai/doompi-help'\n - '@agimon-ai/doompi-hook'\n - '@agimon-ai/doompi-goal'\n - '@agimon-ai/doompi-voice'\n - '@agimon-ai/doompi-runner'\n - '@agimon-ai/doompi-file-edit'\n - '@agimon-ai/doompi-autocompact'\n - '@agimon-ai/doompi-loop'\n - '@agimon-ai/doompi-plan'\n - '@agimon-ai/doompi-workflow'\n - '@agimon-ai/doompi-log'\n - '@agimon-ai/doompi-mcp'\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n # Optional Team configuration. Uncomment and choose models available to Pi.\n # config:\n # models:\n # - model: openai-codex/gpt-5.6-luna\n # thinking: xhigh\n # # Tool names excluded from subagents launched by this package. These\n # # remove model-visible tools while preserving hidden team transport.\n # excludeTools: [ask_user_question, intercom, subagent]\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\n# A major mode explains when it should be used and names its ordered layers.\n# Add another entry under majorMode, then select it with\n# doompi --major-mode <name>. Change defaultMajorMode to make it the default.\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with Team delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n # review:\n # description: Focused review mode for inspecting changes and reporting risks.\n # layers: [team, task, ask-user]\n`,\n 'domains.yaml': `# Plugins are named once in this catalog, then selected by one or more domains.\n# DoomPi automatically inspects personal and repository Codex marketplaces.\n# Each configured root may also be a folder whose direct children are plugins;\n# use roots: [plugins] to discover a repository plugin folder without one entry\n# per plugin. Discovery never scans recursively.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\n # Local paths resolve beside this file in ~/.pi/.doom. Repository paths in a\n # repository .doom/domains.yaml resolve from that repository root.\n # entries:\n # development:\n # source: local\n # path: plugins/development\n # remote-review:\n # source: url\n # url: https://github.com/acme/review-plugin.git\n # ref: v1.2.0\n # published-review:\n # source: npm\n # package: '@acme/review-plugin'\n # version: 1.2.0\n# Domains carry descriptions and refer to catalog names, not filesystem paths.\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\n # development:\n # description: TypeScript implementation and review tools.\n # plugins:\n # - development\n # # Mapping form can load only selected resources from a shared plugin.\n # - name: remote-review\n # skills: [typescript]\n # agents: [reviewer]\n # hooks: false\n # mcp: true\n # # Set false only when this domain should omit shared .claude/skills.\n # sharedSkills: false\n# Select it with doompi --domains development, or add it to defaultDomains.\n#\n# Aliases let one selection enable multiple domains. Replace the empty mapping\n# below with entries like the commented example.\naliases: {}\n# aliases:\n# work: [default, development]\n`,\n 'profiles.yaml': `# Profiles select a persona and optional environment defaults for a session.\n# Each configured root may itself be a profile or contain direct-child profile\n# folders. A folder is discovered when it contains profile.md, SOUL.md, or\n# AGENTS.md; discovery never scans recursively.\nprofiles:\n roots: []\n entries: {}\n # Roots resolve beside this file in ~/.pi/.doom. Repository roots in a\n # repository .doom/profiles.yaml resolve from that repository root.\n # roots: [agents/acme]\n # Explicit entries override an automatically discovered folder of the same\n # name. Environment values must be strings; already exported values win.\n # entries:\n # writer:\n # persona: agents/special/writer\n # env:\n # BRAND: acme\n # TONE: concise\n# Doom Pi concatenates profile.md, SOUL.md, then AGENTS.md for the persona.\n# Select one with: doompi --profile writer\n`,\n};\n\n/** Standalone defaults for a repository that is trying DoomPi through DPI. */\nexport const REPOSITORY_DOOM_CONFIG_TEMPLATES: Readonly<Record<DoomSeedFile, string>> = {\n 'config.yaml': DOOM_CONFIG_TEMPLATES['config.yaml'],\n 'modes.yaml': `# Repository-local layers and major modes. These entries override personal\n# entries with the same name from ~/.pi/.doom/modes.yaml.\n# This repository-local default replaces the personal top-level default as one\n# whole package list. Edit it to customize the standalone package baseline.\ndefault:\n packages:\n - '@agimon-ai/doompi-help'\n - '@agimon-ai/doompi-hook'\n - '@agimon-ai/doompi-goal'\n - '@agimon-ai/doompi-voice'\n - '@agimon-ai/doompi-runner'\n - '@agimon-ai/doompi-file-edit'\n - '@agimon-ai/doompi-autocompact'\n - '@agimon-ai/doompi-loop'\n - '@agimon-ai/doompi-plan'\n - '@agimon-ai/doompi-workflow'\n - '@agimon-ai/doompi-log'\n - '@agimon-ai/doompi-mcp'\nlayers:\n team:\n packages:\n - name: '@agimon-ai/doompi-team'\n ask-user:\n packages:\n - '@agimon-ai/doompi-user-feedback'\n task:\n packages:\n - '@agimon-ai/doompi-task'\ndefaultMajorMode: copilot\nmajorMode:\n minimal:\n description: Lean mode with Team delegation and persistent tasks.\n layers: [team, task]\n copilot:\n description: General-purpose coding mode with delegation, persistent tasks, supervised commands, and structured user feedback.\n layers: [team, ask-user, task]\n`,\n 'domains.yaml': `# Repository-local plugin catalog and domain selection.\ndefaultDomains: [default]\nplugins:\n roots: []\n entries: {}\ndomains:\n default:\n description: Shared skills and repository MCP only.\n plugins: []\naliases: {}\n`,\n 'profiles.yaml': `# Repository-local persona roots and environment defaults.\nprofiles:\n roots: []\n entries: {}\n`,\n};\n\n/** Result of filling a Doom 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\nexport type RepositoryDoomInitOptions = GlobalDoomInitOptions;\nexport type RepositoryDoomInitResult = GlobalDoomInitResult;\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, mode: number): 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 });\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 ensureDoomDirectory(directory: string, mode: number): void {\n ensureDirectory(path.dirname(directory), mode);\n ensureDirectory(directory, mode);\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, mode: number): boolean {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, 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, mode: number): void {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', mode);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, 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\nfunction initializeDoomConfig(\n directory: string,\n templates: Readonly<Record<DoomSeedFile, string>>,\n options: GlobalDoomInitOptions,\n directoryMode: number,\n fileMode: number,\n): GlobalDoomInitResult {\n ensureDoomDirectory(directory, directoryMode);\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, templates[fileName], fileMode);\n (existed ? replaced : created).push(fileName);\n continue;\n }\n if (existed) {\n preserved.push(fileName);\n continue;\n }\n if (publishExclusively(targetPath, templates[fileName], fileMode)) created.push(fileName);\n else preserved.push(fileName);\n }\n\n return { directory, created, preserved, replaced };\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 return initializeDoomConfig(\n globalDoomConfigDirectory(homeDirectory),\n DOOM_CONFIG_TEMPLATES,\n options,\n PRIVATE_DIRECTORY_MODE,\n PRIVATE_FILE_MODE,\n );\n}\n\n/** Seeds repository-local configuration without creating or changing Pi settings. */\nexport function initializeRepositoryDoomConfig(\n repositoryRoot = process.cwd(),\n options: RepositoryDoomInitOptions = {},\n): RepositoryDoomInitResult {\n return initializeDoomConfig(\n path.join(path.resolve(repositoryRoot), DOOM_DIRECTORY),\n REPOSITORY_DOOM_CONFIG_TEMPLATES,\n options,\n REPOSITORY_DIRECTORY_MODE,\n REPOSITORY_FILE_MODE,\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./layeredConfig.cjs");let n=require("node:path");n=e.__toESM(n,1);let r=require("node:os");r=e.__toESM(r,1);const i=`modes.yaml`,a=n.default.join(t.DOOM_DIR,i),o=[`./`,`../`];function s(e){return n.default.isAbsolute(e)||o.some(t=>e.startsWith(t))}const c=[`name`,`optional`,`config`],l=[`description`,`layers`];function u(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function d(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 f(e,t,n){let r=`Layer "${e}" package[${n}] in ${a}`;if(typeof t==`string`){if(!t.trim())throw Error(`${r} must be a non-empty string or mapping`);return t.trim()}if(!u(t))throw Error(`${r} must be a non-empty string or mapping`);if(d(t,c,r),typeof t.name!=`string`||!t.name.trim())throw Error(`${r}.name must be a non-empty string`);if(t.optional!==void 0&&typeof t.optional!=`boolean`)throw Error(`${r}.optional must be a boolean`);if(t.config!==void 0&&!u(t.config))throw Error(`${r}.config must be a mapping`);return{name:t.name.trim(),...t.optional===!0?{optional:!0}:{},...t.config?{config:{...t.config}}:{}}}function p(e,t){if(t===void 0)return;let n=`Layer "${e}" extensions in ${a}`;if(!Array.isArray(t))throw Error(`${n} must be an array`);return t.map((e,t)=>{if(typeof e!=`string`||!e.trim())throw Error(`${n}[${t}] must be a non-empty string`);return e.trim()})}function m(e,t){if(t!==void 0){if(!Array.isArray(t))throw Error(`Layer "${e}" packages in ${a} must be an array`);return t.map((t,n)=>f(e,t,n))}}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){let n=`Major mode "${e}" layers in ${a}`;if(!Array.isArray(t))throw Error(`${n} must be an array`);return t.map((e,t)=>{if(typeof e!=`string`||!e.trim())throw Error(`${n}[${t}] must be a non-empty string`);return e.trim()})}function _(e){return e.length>0?`Uses layers: ${e.join(`, `)}.`:`Uses Doom Pi core extensions only.`}function v(e,t){if(Array.isArray(t)){let n=g(e,t);return{description:_(n),layers:n}}let n=`Major mode "${e}" in ${a}`;if(!u(t))throw Error(`${n} must be a mapping with description and layers`);if(d(t,l,n),typeof t.description!=`string`||!t.description.trim())throw Error(`${n}.description must be a non-empty string`);return{description:t.description.trim(),layers:g(e,t.layers)}}function y(e){if(e===void 0)return{};if(!u(e))throw Error(`majorMode in ${a} must be a mapping`);return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,t===null?null:v(e,t)]))}function b(e,t){if(!u(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`);if(`config`in t)throw Error(`Layer "${e}" in ${a} uses unsupported field "config"; move it under the package entry that owns it`);let n=p(e,t.extensions),r=m(e,t.packages);return{...t,...n===void 0?{}:{extensions:n},...r===void 0?{}:{packages:r}}}function x(e){if(e===void 0)return{};if(!u(e))throw Error(`layers in ${a} must be a mapping`);return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,t===null?null:b(e,t)]))}function S(e,n=r.default.homedir()){let o=t.readDoomConfigSources(i,e,n),s={},c={},l={},u=`copilot`,d,f=!1;for(let e of o){for(let[t,n]of Object.entries(x(e.document.layers))){if(n===null){delete s[t];continue}s[t]={...n,baseDirectory:e.baseDirectory,filePath:e.filePath}}Object.hasOwn(e.document,`defaultMajorMode`)&&(u=h(e.document.defaultMajorMode),d={filePath:e.filePath,baseDirectory:e.baseDirectory},f=!0);for(let[t,n]of Object.entries(y(e.document.majorMode))){if(n===null){delete c[t],delete l[t];continue}c[t]=n,l[t]={filePath:e.filePath,baseDirectory:e.baseDirectory}}}for(let[e,t]of Object.entries(c))for(let n of t.layers)if(!s[n])throw Error(`Unknown layer "${n}" in majorMode.${e} of ${a}`);if(f&&!Object.hasOwn(c,u))throw Error(`Unknown default major mode "${u}" in ${a}`);return{layers:s,defaultMajorMode:u,...d?{defaultMajorModeSource:d}:{},majorMode:c,majorModeSources:l}}function C(e,t,n){let r=n.trim();if(!r)throw Error(`Package name must be a non-empty string`);let i=[];for(let n of t){let t=e.layers[n];if(!t)throw Error(`Unknown layer: ${n}`);for(let e of t.packages??[])typeof e!=`string`&&e.config!==void 0&&(e.name!==r&&!e.name.startsWith(`${r}/`)||i.push({layer:n,specifier:e.name,config:{...e.config},baseDirectory:t.baseDirectory,...t.filePath?{filePath:t.filePath}:{}}))}return i}function w(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].layers]}function T(e,t,n){let r=e.localEntries?.(t,n);if(r!==void 0)return r;let i=e.localEntry(t,n);return i?[i]:void 0}function E(e,t,n){let r=e.layers[t];if(!r)throw Error(`Unknown layer: ${t}`);let i=[];for(let e of r.extensions??[]){if(!s(e)){i.push(n.ownEntry(e));continue}let a=T(n,e,r.baseDirectory);if(!a?.length)throw Error(`Layer "${t}" extension "${e}" does not resolve under ${r.baseDirectory}. Point it at a Pi extension file or directory.`);i.push(...a)}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=T(n,a,r.baseDirectory);if(e?.length)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 declares pi.extensions.`);continue}if(o){let e=n.optionalPackageEntries?.(a);if(e===void 0){let t=n.optionalPackageEntry(a);e=t?[t]:void 0}e&&i.push(...e);continue}let c=n.packageEntries?.(a)??[n.packageEntry(a)];if(c.length===0)throw Error(`Layer "${t}" package "${a}" declares no Pi extensions.`);i.push(...c)}return i}function D(e,t){let n=new Set;for(let r of t)for(let t of e.layers[r]?.hookGroups??[])n.add(t);return[...n]}function O(e,t,n){return n?[...t]:t.filter(t=>(e.layers[t]?.hookGroups?.length??0)===0)}exports.filterHookDisabledLayers=O,exports.isLocalPackageSpecifier=s,exports.layerEntries=E,exports.layerHookGroups=D,exports.loadMajorModesConfig=S,exports.resolveLayers=w,exports.resolvePackageConfigurations=C;
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./layeredConfig.cjs");let n=require("node:path");n=e.__toESM(n,1);let r=require("node:os");r=e.__toESM(r,1);const i=`modes.yaml`,a=n.default.join(t.DOOM_DIR,i),o=[`./`,`../`],s=[`packages`];function c(e){return n.default.isAbsolute(e)||o.some(t=>e.startsWith(t))}const l=[`name`,`optional`,`config`],u=[`description`,`layers`];function d(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function f(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 p(e,t,n){let r=`Layer "${e}" package[${n}] in ${a}`;if(typeof t==`string`){if(!t.trim())throw Error(`${r} must be a non-empty string or mapping`);return t.trim()}if(!d(t))throw Error(`${r} must be a non-empty string or mapping`);if(f(t,l,r),typeof t.name!=`string`||!t.name.trim())throw Error(`${r}.name must be a non-empty string`);if(t.optional!==void 0&&typeof t.optional!=`boolean`)throw Error(`${r}.optional must be a boolean`);if(t.config!==void 0&&!d(t.config))throw Error(`${r}.config must be a mapping`);return{name:t.name.trim(),...t.optional===!0?{optional:!0}:{},...t.config?{config:{...t.config}}:{}}}function m(e,t){if(t===void 0)return;let n=`Layer "${e}" extensions in ${a}`;if(!Array.isArray(t))throw Error(`${n} must be an array`);return t.map((e,t)=>{if(typeof e!=`string`||!e.trim())throw Error(`${n}[${t}] must be a non-empty string`);return e.trim()})}function h(e,t){if(t!==void 0){if(!Array.isArray(t))throw Error(`Layer "${e}" packages in ${a} must be an array`);return t.map((t,n)=>p(e,t,n))}}function g(e){if(typeof e!=`string`||!e.trim())throw Error(`defaultMajorMode in ${a} must be a non-empty string`);return e.trim()}function _(e,t){let n=`Major mode "${e}" layers in ${a}`;if(!Array.isArray(t))throw Error(`${n} must be an array`);return t.map((e,t)=>{if(typeof e!=`string`||!e.trim())throw Error(`${n}[${t}] must be a non-empty string`);return e.trim()})}function v(e){return e.length>0?`Uses layers: ${e.join(`, `)}.`:`Uses Doom Pi core extensions only.`}function y(e,t){if(Array.isArray(t)){let n=_(e,t);return{description:v(n),layers:n}}let n=`Major mode "${e}" in ${a}`;if(!d(t))throw Error(`${n} must be a mapping with description and layers`);if(f(t,u,n),typeof t.description!=`string`||!t.description.trim())throw Error(`${n}.description must be a non-empty string`);return{description:t.description.trim(),layers:_(e,t.layers)}}function b(e){if(e===void 0)return{};if(!d(e))throw Error(`majorMode in ${a} must be a mapping`);return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,t===null?null:y(e,t)]))}function x(e,t){if(!d(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`);if(`config`in t)throw Error(`Layer "${e}" in ${a} uses unsupported field "config"; move it under the package entry that owns it`);let n=m(e,t.extensions),r=h(e,t.packages);return{...t,...n===void 0?{}:{extensions:n},...r===void 0?{}:{packages:r}}}function S(e){if(e===void 0)return{};if(!d(e))throw Error(`layers in ${a} must be a mapping`);return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,t===null?null:x(e,t)]))}function C(e){let t=`default in ${a}`;if(!d(e))throw Error(`${t} must be a mapping with packages`);if(f(e,s,t),!Object.hasOwn(e,`packages`))throw Error(`Layer "default" packages in ${a} must be an array`);return{packages:h(`default`,e.packages)}}function w(e,n=r.default.homedir()){let o=t.readDoomConfigSources(i,e,n),s,c={},l={},u={},d=`copilot`,f,p=!1;for(let e of o){Object.hasOwn(e.document,`default`)&&(s={...C(e.document.default),baseDirectory:e.baseDirectory,filePath:e.filePath});for(let[t,n]of Object.entries(S(e.document.layers))){if(n===null){delete c[t];continue}c[t]={...n,baseDirectory:e.baseDirectory,filePath:e.filePath}}Object.hasOwn(e.document,`defaultMajorMode`)&&(d=g(e.document.defaultMajorMode),f={filePath:e.filePath,baseDirectory:e.baseDirectory},p=!0);for(let[t,n]of Object.entries(b(e.document.majorMode))){if(n===null){delete l[t],delete u[t];continue}l[t]=n,u[t]={filePath:e.filePath,baseDirectory:e.baseDirectory}}}for(let[e,t]of Object.entries(l))for(let n of t.layers)if(!c[n])throw Error(`Unknown layer "${n}" in majorMode.${e} of ${a}`);if(p&&!Object.hasOwn(l,d))throw Error(`Unknown default major mode "${d}" in ${a}`);return{...s?{default:s}:{},layers:c,defaultMajorMode:d,...f?{defaultMajorModeSource:f}:{},majorMode:l,majorModeSources:u}}function T(e,t,n){let r=n.trim();if(!r)throw Error(`Package name must be a non-empty string`);let i=[],a=[];e.default&&a.push({name:`default`,definition:e.default});for(let n of t){let t=e.layers[n];if(!t)throw Error(`Unknown layer: ${n}`);a.push({name:n,definition:t})}for(let{name:e,definition:t}of a)for(let n of t.packages??[])typeof n!=`string`&&n.config!==void 0&&(n.name!==r&&!n.name.startsWith(`${r}/`)||i.push({layer:e,specifier:n.name,config:{...n.config},baseDirectory:t.baseDirectory,...t.filePath?{filePath:t.filePath}:{}}));return i}function E(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].layers]}function D(e,t,n){let r=e.localEntries?.(t,n);if(r!==void 0)return r;let i=e.localEntry(t,n);return i?[i]:void 0}function O(e,t,n){let r=e.layers[t];if(!r)throw Error(`Unknown layer: ${t}`);let i=[];for(let e of r.extensions??[]){if(!c(e)){i.push(n.ownEntry(e));continue}let a=D(n,e,r.baseDirectory);if(!a?.length)throw Error(`Layer "${t}" extension "${e}" does not resolve under ${r.baseDirectory}. Point it at a Pi extension file or directory.`);i.push(...a)}for(let e of r.packages??[]){let a=typeof e==`string`?e:e.name,o=typeof e!=`string`&&e.optional===!0;if(c(a)){let e=D(n,a,r.baseDirectory);if(e?.length)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 declares pi.extensions.`);continue}if(o){let e=n.optionalPackageEntries?.(a);if(e===void 0){let t=n.optionalPackageEntry(a);e=t?[t]:void 0}e&&i.push(...e);continue}let s=n.packageEntries?.(a)??[n.packageEntry(a)];if(s.length===0)throw Error(`Layer "${t}" package "${a}" declares no Pi extensions.`);i.push(...s)}return i}function k(e,t){let n=new Set;for(let r of t)for(let t of e.layers[r]?.hookGroups??[])n.add(t);return[...n]}function A(e,t,n){return n?[...t]:t.filter(t=>(e.layers[t]?.hookGroups?.length??0)===0)}exports.filterHookDisabledLayers=A,exports.isLocalPackageSpecifier=c,exports.layerEntries=O,exports.layerHookGroups=k,exports.loadMajorModesConfig=w,exports.resolveLayers=E,exports.resolvePackageConfigurations=T;
2
2
  //# sourceMappingURL=majorModes.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"majorModes.cjs","names":["path","DOOM_DIR","os","readDoomConfigSources"],"sources":["../../src/adapters/majorModes.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\nimport { DOOM_DIR, type DoomConfigProvenance, readDoomConfigSources } from './layeredConfig.ts';\n\nexport type LayerPackageConfig = Record<string, unknown>;\n\n/** One package selected by a layer, plus configuration owned by that package. */\nexport interface LayerPackage {\n name: string;\n optional?: boolean;\n config?: LayerPackageConfig;\n}\n\n/** One configured package found while walking selected layers in order. */\nexport interface ResolvedPackageConfiguration {\n layer: string;\n specifier: string;\n config: LayerPackageConfig;\n baseDirectory: string;\n filePath?: string;\n}\n\nexport interface LayerDefinition {\n /**\n * DoomPi built-in names, or Pi-compatible local extension paths.\n * Bare names resolve from DoomPi's own extension entries. Paths beginning\n * with `./` or `../`, plus absolute paths, resolve against the config that\n * declares the layer and may name a script file or extension directory.\n */\n extensions?: string[];\n /**\n * npm specifiers, or local package paths retained for compatibility.\n * Relative paths resolve against the repository root for `.doom/modes.yaml`\n * and against the global `.doom` directory for\n * `~/.pi/.doom/modes.yaml`.\n */\n packages?: Array<string | LayerPackage>;\n hookGroups?: string[];\n}\n\n/** A layer plus the root its relative extension and package paths resolve against. */\nexport interface ResolvedLayerDefinition extends LayerDefinition {\n baseDirectory: string;\n /** Absolute path of the modes file that declared this layer. */\n filePath?: string;\n}\n\nexport interface MajorModeDefinition {\n /** Human-readable purpose used by pickers and agent-facing catalogs. */\n description: string;\n /** Ordered layer names activated by this mode. */\n layers: 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 /** Source that last declared the default, absent for the built-in fallback. */\n defaultMajorModeSource?: DoomConfigProvenance;\n /** Named major modes, including their purpose and ordered layers. */\n majorMode: Record<string, MajorModeDefinition>;\n /** Declaring source for each surviving named mode. */\n majorModeSources?: Record<string, DoomConfigProvenance>;\n}\n\nexport interface LayerResolvers {\n ownEntry: (name: string) => string;\n packageEntry: (name: string) => string;\n optionalPackageEntry: (name: string) => string | undefined;\n /** Resolves every standard package.json pi.extensions entry for a package root. */\n packageEntries?: (name: string) => string[];\n /** Optional counterpart to packageEntries. */\n optionalPackageEntries?: (name: string) => string[] | undefined;\n /** Resolves one path-style layer entry against the config that declared it. */\n localEntry: (specifier: string, baseDirectory: string) => string | undefined;\n /** Resolves every Pi extension entry contributed by a local file or directory. */\n localEntries?: (specifier: string, baseDirectory: string) => string[] | undefined;\n}\n\ninterface ModesDocument {\n layers?: unknown;\n defaultMajorMode?: unknown;\n majorMode?: unknown;\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 value names a path rather than a built-in or installed package.\n *\n * The same rule Node applies to import specifiers, so a bare name is always a\n * logical name 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_PACKAGE_KEYS = ['name', 'optional', 'config'] as const;\nconst MAJOR_MODE_KEYS = ['description', 'layers'] as const;\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 parseLayerPackage(layerName: string, value: unknown, index: number): string | LayerPackage {\n const location = `Layer \"${layerName}\" package[${index}] in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (typeof value === 'string') {\n if (!value.trim()) throw new Error(`${location} must be a non-empty string or mapping`);\n return value.trim();\n }\n if (!isRecord(value)) throw new Error(`${location} must be a non-empty string or mapping`);\n assertKnownKeys(value, LAYER_PACKAGE_KEYS, location);\n if (typeof value.name !== 'string' || !value.name.trim()) {\n throw new Error(`${location}.name must be a non-empty string`);\n }\n if (value.optional !== undefined && typeof value.optional !== 'boolean') {\n throw new Error(`${location}.optional must be a boolean`);\n }\n if (value.config !== undefined && !isRecord(value.config)) {\n throw new Error(`${location}.config must be a mapping`);\n }\n return {\n name: value.name.trim(),\n ...(value.optional === true ? { optional: true } : {}),\n ...(value.config ? { config: { ...value.config } } : {}),\n };\n}\n\nfunction parseLayerExtensions(layerName: string, value: unknown): string[] | undefined {\n if (value === undefined) return undefined;\n const location = `Layer \"${layerName}\" extensions in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!Array.isArray(value)) throw new Error(`${location} must be an array`);\n return value.map((entry, index) => {\n if (typeof entry !== 'string' || !entry.trim()) {\n throw new Error(`${location}[${index}] must be a non-empty string`);\n }\n return entry.trim();\n });\n}\n\nfunction parseLayerPackages(layerName: string, value: unknown): Array<string | LayerPackage> | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value)) {\n throw new Error(`Layer \"${layerName}\" packages in ${MAJOR_MODES_RELATIVE_PATH} must be an array`);\n }\n return value.map((entry, index) => parseLayerPackage(layerName, entry, index));\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 parseMajorModeLayers(modeName: string, value: unknown): string[] {\n const location = `Major mode \"${modeName}\" layers in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!Array.isArray(value)) throw new Error(`${location} must be an array`);\n return value.map((layerName, index) => {\n if (typeof layerName !== 'string' || !layerName.trim()) {\n throw new Error(`${location}[${index}] must be a non-empty string`);\n }\n return layerName.trim();\n });\n}\n\nfunction legacyMajorModeDescription(layers: readonly string[]): string {\n return layers.length > 0 ? `Uses layers: ${layers.join(', ')}.` : 'Uses Doom Pi core extensions only.';\n}\n\nfunction parseMajorMode(name: string, value: unknown): MajorModeDefinition {\n if (Array.isArray(value)) {\n const layers = parseMajorModeLayers(name, value);\n return { description: legacyMajorModeDescription(layers), layers };\n }\n\n const location = `Major mode \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!isRecord(value)) throw new Error(`${location} must be a mapping with description and layers`);\n assertKnownKeys(value, MAJOR_MODE_KEYS, location);\n if (typeof value.description !== 'string' || !value.description.trim()) {\n throw new Error(`${location}.description must be a non-empty string`);\n }\n return {\n description: value.description.trim(),\n layers: parseMajorModeLayers(name, value.layers),\n };\n}\n\nfunction parseMajorModes(value: unknown): Record<string, MajorModeDefinition | null> {\n if (value === undefined) return {};\n if (!isRecord(value)) throw new Error(`majorMode in ${MAJOR_MODES_RELATIVE_PATH} must be a mapping`);\n return Object.fromEntries(\n Object.entries(value).map(([name, definition]) => [\n name,\n definition === null ? null : parseMajorMode(name, definition),\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 if ('config' in definition) {\n throw new Error(\n `Layer \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH} uses unsupported field \"config\"; move it under the package entry that owns it`,\n );\n }\n const extensions = parseLayerExtensions(name, definition.extensions);\n const packages = parseLayerPackages(name, definition.packages);\n return {\n ...definition,\n ...(extensions === undefined ? {} : { extensions }),\n ...(packages === undefined ? {} : { packages }),\n } as LayerDefinition;\n}\n\nfunction parseLayers(value: unknown): Record<string, LayerDefinition | null> {\n if (value === undefined) return {};\n if (!isRecord(value)) throw new Error(`layers in ${MAJOR_MODES_RELATIVE_PATH} must be a mapping`);\n return Object.fromEntries(\n Object.entries(value).map(([name, definition]) => [\n name,\n definition === null ? null : parseLayer(name, definition),\n ]),\n );\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, MajorModeDefinition> = {};\n const majorModeSources: Record<string, DoomConfigProvenance> = {};\n let defaultMajorMode = FALLBACK_MAJOR_MODE;\n let defaultMajorModeSource: DoomConfigProvenance | undefined;\n let hasConfiguredDefault = false;\n for (const source of sources) {\n for (const [name, definition] of Object.entries(parseLayers(source.document.layers))) {\n if (definition === null) {\n delete layers[name];\n continue;\n }\n layers[name] = {\n ...definition,\n baseDirectory: source.baseDirectory,\n filePath: source.filePath,\n };\n }\n if (Object.hasOwn(source.document, 'defaultMajorMode')) {\n defaultMajorMode = parseDefaultMajorMode(source.document.defaultMajorMode);\n defaultMajorModeSource = { filePath: source.filePath, baseDirectory: source.baseDirectory };\n hasConfiguredDefault = true;\n }\n for (const [name, definition] of Object.entries(parseMajorModes(source.document.majorMode))) {\n if (definition === null) {\n delete majorMode[name];\n delete majorModeSources[name];\n continue;\n }\n majorMode[name] = definition;\n majorModeSources[name] = { filePath: source.filePath, baseDirectory: source.baseDirectory };\n }\n }\n\n for (const [modeName, definition] of Object.entries(majorMode)) {\n for (const name of definition.layers) {\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 {\n layers,\n defaultMajorMode,\n ...(defaultMajorModeSource ? { defaultMajorModeSource } : {}),\n majorMode,\n majorModeSources,\n };\n}\n\n/** Finds configuration owned by one package while preserving layer and package order. */\nexport function resolvePackageConfigurations(\n config: MajorModesConfig,\n names: string[],\n packageName: string,\n): ResolvedPackageConfiguration[] {\n const normalizedPackageName = packageName.trim();\n if (!normalizedPackageName) throw new Error('Package name must be a non-empty string');\n const configurations: ResolvedPackageConfiguration[] = [];\n for (const name of names) {\n const layer = config.layers[name];\n if (!layer) throw new Error(`Unknown layer: ${name}`);\n for (const entry of layer.packages ?? []) {\n if (typeof entry === 'string' || entry.config === undefined) continue;\n if (entry.name !== normalizedPackageName && !entry.name.startsWith(`${normalizedPackageName}/`)) continue;\n configurations.push({\n layer: name,\n specifier: entry.name,\n config: { ...entry.config },\n baseDirectory: layer.baseDirectory,\n ...(layer.filePath ? { filePath: layer.filePath } : {}),\n });\n }\n }\n return configurations;\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]!.layers];\n}\n\nfunction resolveLocalEntries(\n resolvers: LayerResolvers,\n specifier: string,\n baseDirectory: string,\n): string[] | undefined {\n const resolved = resolvers.localEntries?.(specifier, baseDirectory);\n if (resolved !== undefined) return resolved;\n const single = resolvers.localEntry(specifier, baseDirectory);\n return single ? [single] : undefined;\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 ?? []) {\n if (!isLocalPackageSpecifier(entry)) {\n entries.push(resolvers.ownEntry(entry));\n continue;\n }\n const resolved = resolveLocalEntries(resolvers, entry, layer.baseDirectory);\n if (!resolved?.length) {\n throw new Error(\n `Layer \"${name}\" extension \"${entry}\" does not resolve under ${layer.baseDirectory}. ` +\n 'Point it at a Pi extension file or directory.',\n );\n }\n entries.push(...resolved);\n }\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 = resolveLocalEntries(resolvers, specifier, layer.baseDirectory);\n if (resolved?.length) 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 declares pi.extensions.',\n );\n }\n continue;\n }\n if (optional) {\n let resolved = resolvers.optionalPackageEntries?.(specifier);\n if (resolved === undefined) {\n const single = resolvers.optionalPackageEntry(specifier);\n resolved = single ? [single] : undefined;\n }\n if (resolved) entries.push(...resolved);\n continue;\n }\n const resolved = resolvers.packageEntries?.(specifier) ?? [resolvers.packageEntry(specifier)];\n if (resolved.length === 0) {\n throw new Error(`Layer \"${name}\" package \"${specifier}\" declares no Pi extensions.`);\n }\n entries.push(...resolved);\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\n/**\n * Suppress layers that exist to contribute hook groups when hooks are disabled.\n *\n * Lives beside layerHookGroups because it is the inverse question about the same\n * field, and because every caller that resolves layers has to ask it.\n */\nexport function filterHookDisabledLayers(\n config: MajorModesConfig,\n layers: readonly string[],\n hooks: boolean,\n): string[] {\n if (hooks) return [...layers];\n return layers.filter((layer) => (config.layers[layer]?.hookGroups?.length ?? 0) === 0);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"majorModes.cjs","names":["path","DOOM_DIR","os","readDoomConfigSources"],"sources":["../../src/adapters/majorModes.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\nimport { DOOM_DIR, type DoomConfigProvenance, readDoomConfigSources } from './layeredConfig.ts';\n\nexport type LayerPackageConfig = Record<string, unknown>;\n\n/** One package selected by a layer, plus configuration owned by that package. */\nexport interface LayerPackage {\n name: string;\n optional?: boolean;\n config?: LayerPackageConfig;\n}\n\n/** One configured package found while walking selected layers in order. */\nexport interface ResolvedPackageConfiguration {\n layer: string;\n specifier: string;\n config: LayerPackageConfig;\n baseDirectory: string;\n filePath?: string;\n}\n\nexport interface LayerDefinition {\n /**\n * DoomPi built-in names, or Pi-compatible local extension paths.\n * Bare names resolve from DoomPi's own extension entries. Paths beginning\n * with `./` or `../`, plus absolute paths, resolve against the config that\n * declares the layer and may name a script file or extension directory.\n */\n extensions?: string[];\n /**\n * npm specifiers, or local package paths retained for compatibility.\n * Relative paths resolve against the repository root for `.doom/modes.yaml`\n * and against the global `.doom` directory for\n * `~/.pi/.doom/modes.yaml`.\n */\n packages?: Array<string | LayerPackage>;\n hookGroups?: string[];\n}\n\n/** A layer plus the root its relative extension and package paths resolve against. */\nexport interface ResolvedLayerDefinition extends LayerDefinition {\n baseDirectory: string;\n /** Absolute path of the modes file that declared this layer. */\n filePath?: string;\n}\n\nexport interface MajorModeDefinition {\n /** Human-readable purpose used by pickers and agent-facing catalogs. */\n description: string;\n /** Ordered layer names activated by this mode. */\n layers: string[];\n}\n\nexport interface MajorModesConfig {\n /** Unconditional package bundle loaded before packages from selected named layers. */\n default?: ResolvedLayerDefinition;\n layers: Record<string, ResolvedLayerDefinition>;\n /** The mode selected when neither a flag nor the environment names one. */\n defaultMajorMode: string;\n /** Source that last declared the default, absent for the built-in fallback. */\n defaultMajorModeSource?: DoomConfigProvenance;\n /** Named major modes, including their purpose and ordered layers. */\n majorMode: Record<string, MajorModeDefinition>;\n /** Declaring source for each surviving named mode. */\n majorModeSources?: Record<string, DoomConfigProvenance>;\n}\n\nexport interface LayerResolvers {\n ownEntry: (name: string) => string;\n packageEntry: (name: string) => string;\n optionalPackageEntry: (name: string) => string | undefined;\n /** Resolves every standard package.json pi.extensions entry for a package root. */\n packageEntries?: (name: string) => string[];\n /** Optional counterpart to packageEntries. */\n optionalPackageEntries?: (name: string) => string[] | undefined;\n /** Resolves one path-style layer entry against the config that declared it. */\n localEntry: (specifier: string, baseDirectory: string) => string | undefined;\n /** Resolves every Pi extension entry contributed by a local file or directory. */\n localEntries?: (specifier: string, baseDirectory: string) => string[] | undefined;\n}\n\ninterface ModesDocument {\n default?: unknown;\n layers?: unknown;\n defaultMajorMode?: unknown;\n majorMode?: unknown;\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;\nconst DEFAULT_KEYS = ['packages'] as const;\n\n/**\n * Whether a layer value names a path rather than a built-in or installed package.\n *\n * The same rule Node applies to import specifiers, so a bare name is always a\n * logical name 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_PACKAGE_KEYS = ['name', 'optional', 'config'] as const;\nconst MAJOR_MODE_KEYS = ['description', 'layers'] as const;\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 parseLayerPackage(layerName: string, value: unknown, index: number): string | LayerPackage {\n const location = `Layer \"${layerName}\" package[${index}] in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (typeof value === 'string') {\n if (!value.trim()) throw new Error(`${location} must be a non-empty string or mapping`);\n return value.trim();\n }\n if (!isRecord(value)) throw new Error(`${location} must be a non-empty string or mapping`);\n assertKnownKeys(value, LAYER_PACKAGE_KEYS, location);\n if (typeof value.name !== 'string' || !value.name.trim()) {\n throw new Error(`${location}.name must be a non-empty string`);\n }\n if (value.optional !== undefined && typeof value.optional !== 'boolean') {\n throw new Error(`${location}.optional must be a boolean`);\n }\n if (value.config !== undefined && !isRecord(value.config)) {\n throw new Error(`${location}.config must be a mapping`);\n }\n return {\n name: value.name.trim(),\n ...(value.optional === true ? { optional: true } : {}),\n ...(value.config ? { config: { ...value.config } } : {}),\n };\n}\n\nfunction parseLayerExtensions(layerName: string, value: unknown): string[] | undefined {\n if (value === undefined) return undefined;\n const location = `Layer \"${layerName}\" extensions in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!Array.isArray(value)) throw new Error(`${location} must be an array`);\n return value.map((entry, index) => {\n if (typeof entry !== 'string' || !entry.trim()) {\n throw new Error(`${location}[${index}] must be a non-empty string`);\n }\n return entry.trim();\n });\n}\n\nfunction parseLayerPackages(layerName: string, value: unknown): Array<string | LayerPackage> | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value)) {\n throw new Error(`Layer \"${layerName}\" packages in ${MAJOR_MODES_RELATIVE_PATH} must be an array`);\n }\n return value.map((entry, index) => parseLayerPackage(layerName, entry, index));\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 parseMajorModeLayers(modeName: string, value: unknown): string[] {\n const location = `Major mode \"${modeName}\" layers in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!Array.isArray(value)) throw new Error(`${location} must be an array`);\n return value.map((layerName, index) => {\n if (typeof layerName !== 'string' || !layerName.trim()) {\n throw new Error(`${location}[${index}] must be a non-empty string`);\n }\n return layerName.trim();\n });\n}\n\nfunction legacyMajorModeDescription(layers: readonly string[]): string {\n return layers.length > 0 ? `Uses layers: ${layers.join(', ')}.` : 'Uses Doom Pi core extensions only.';\n}\n\nfunction parseMajorMode(name: string, value: unknown): MajorModeDefinition {\n if (Array.isArray(value)) {\n const layers = parseMajorModeLayers(name, value);\n return { description: legacyMajorModeDescription(layers), layers };\n }\n\n const location = `Major mode \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!isRecord(value)) throw new Error(`${location} must be a mapping with description and layers`);\n assertKnownKeys(value, MAJOR_MODE_KEYS, location);\n if (typeof value.description !== 'string' || !value.description.trim()) {\n throw new Error(`${location}.description must be a non-empty string`);\n }\n return {\n description: value.description.trim(),\n layers: parseMajorModeLayers(name, value.layers),\n };\n}\n\nfunction parseMajorModes(value: unknown): Record<string, MajorModeDefinition | null> {\n if (value === undefined) return {};\n if (!isRecord(value)) throw new Error(`majorMode in ${MAJOR_MODES_RELATIVE_PATH} must be a mapping`);\n return Object.fromEntries(\n Object.entries(value).map(([name, definition]) => [\n name,\n definition === null ? null : parseMajorMode(name, definition),\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 if ('config' in definition) {\n throw new Error(\n `Layer \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH} uses unsupported field \"config\"; move it under the package entry that owns it`,\n );\n }\n const extensions = parseLayerExtensions(name, definition.extensions);\n const packages = parseLayerPackages(name, definition.packages);\n return {\n ...definition,\n ...(extensions === undefined ? {} : { extensions }),\n ...(packages === undefined ? {} : { packages }),\n } as LayerDefinition;\n}\n\nfunction parseLayers(value: unknown): Record<string, LayerDefinition | null> {\n if (value === undefined) return {};\n if (!isRecord(value)) throw new Error(`layers in ${MAJOR_MODES_RELATIVE_PATH} must be a mapping`);\n return Object.fromEntries(\n Object.entries(value).map(([name, definition]) => [\n name,\n definition === null ? null : parseLayer(name, definition),\n ]),\n );\n}\n\nfunction parseDefault(value: unknown): LayerDefinition {\n const location = `default in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!isRecord(value)) throw new Error(`${location} must be a mapping with packages`);\n assertKnownKeys(value, DEFAULT_KEYS, location);\n if (!Object.hasOwn(value, 'packages')) {\n throw new Error(`Layer \"default\" packages in ${MAJOR_MODES_RELATIVE_PATH} must be an array`);\n }\n const packages = parseLayerPackages('default', value.packages);\n return { packages };\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 let defaultDefinition: ResolvedLayerDefinition | undefined;\n const layers: Record<string, ResolvedLayerDefinition> = {};\n const majorMode: Record<string, MajorModeDefinition> = {};\n const majorModeSources: Record<string, DoomConfigProvenance> = {};\n let defaultMajorMode = FALLBACK_MAJOR_MODE;\n let defaultMajorModeSource: DoomConfigProvenance | undefined;\n let hasConfiguredDefault = false;\n for (const source of sources) {\n if (Object.hasOwn(source.document, 'default')) {\n defaultDefinition = {\n ...parseDefault(source.document.default),\n baseDirectory: source.baseDirectory,\n filePath: source.filePath,\n };\n }\n for (const [name, definition] of Object.entries(parseLayers(source.document.layers))) {\n if (definition === null) {\n delete layers[name];\n continue;\n }\n layers[name] = {\n ...definition,\n baseDirectory: source.baseDirectory,\n filePath: source.filePath,\n };\n }\n if (Object.hasOwn(source.document, 'defaultMajorMode')) {\n defaultMajorMode = parseDefaultMajorMode(source.document.defaultMajorMode);\n defaultMajorModeSource = { filePath: source.filePath, baseDirectory: source.baseDirectory };\n hasConfiguredDefault = true;\n }\n for (const [name, definition] of Object.entries(parseMajorModes(source.document.majorMode))) {\n if (definition === null) {\n delete majorMode[name];\n delete majorModeSources[name];\n continue;\n }\n majorMode[name] = definition;\n majorModeSources[name] = { filePath: source.filePath, baseDirectory: source.baseDirectory };\n }\n }\n\n for (const [modeName, definition] of Object.entries(majorMode)) {\n for (const name of definition.layers) {\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 {\n ...(defaultDefinition ? { default: defaultDefinition } : {}),\n layers,\n defaultMajorMode,\n ...(defaultMajorModeSource ? { defaultMajorModeSource } : {}),\n majorMode,\n majorModeSources,\n };\n}\n\n/** Finds configuration owned by one package while preserving layer and package order. */\nexport function resolvePackageConfigurations(\n config: MajorModesConfig,\n names: string[],\n packageName: string,\n): ResolvedPackageConfiguration[] {\n const normalizedPackageName = packageName.trim();\n if (!normalizedPackageName) throw new Error('Package name must be a non-empty string');\n const configurations: ResolvedPackageConfiguration[] = [];\n const definitions: Array<{ name: string; definition: ResolvedLayerDefinition }> = [];\n if (config.default) definitions.push({ name: 'default', definition: config.default });\n for (const name of names) {\n const definition = config.layers[name];\n if (!definition) throw new Error(`Unknown layer: ${name}`);\n definitions.push({ name, definition });\n }\n for (const { name, definition } of definitions) {\n for (const entry of definition.packages ?? []) {\n if (typeof entry === 'string' || entry.config === undefined) continue;\n if (entry.name !== normalizedPackageName && !entry.name.startsWith(`${normalizedPackageName}/`)) continue;\n configurations.push({\n layer: name,\n specifier: entry.name,\n config: { ...entry.config },\n baseDirectory: definition.baseDirectory,\n ...(definition.filePath ? { filePath: definition.filePath } : {}),\n });\n }\n }\n return configurations;\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]!.layers];\n}\n\nfunction resolveLocalEntries(\n resolvers: LayerResolvers,\n specifier: string,\n baseDirectory: string,\n): string[] | undefined {\n const resolved = resolvers.localEntries?.(specifier, baseDirectory);\n if (resolved !== undefined) return resolved;\n const single = resolvers.localEntry(specifier, baseDirectory);\n return single ? [single] : undefined;\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 ?? []) {\n if (!isLocalPackageSpecifier(entry)) {\n entries.push(resolvers.ownEntry(entry));\n continue;\n }\n const resolved = resolveLocalEntries(resolvers, entry, layer.baseDirectory);\n if (!resolved?.length) {\n throw new Error(\n `Layer \"${name}\" extension \"${entry}\" does not resolve under ${layer.baseDirectory}. ` +\n 'Point it at a Pi extension file or directory.',\n );\n }\n entries.push(...resolved);\n }\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 = resolveLocalEntries(resolvers, specifier, layer.baseDirectory);\n if (resolved?.length) 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 declares pi.extensions.',\n );\n }\n continue;\n }\n if (optional) {\n let resolved = resolvers.optionalPackageEntries?.(specifier);\n if (resolved === undefined) {\n const single = resolvers.optionalPackageEntry(specifier);\n resolved = single ? [single] : undefined;\n }\n if (resolved) entries.push(...resolved);\n continue;\n }\n const resolved = resolvers.packageEntries?.(specifier) ?? [resolvers.packageEntry(specifier)];\n if (resolved.length === 0) {\n throw new Error(`Layer \"${name}\" package \"${specifier}\" declares no Pi extensions.`);\n }\n entries.push(...resolved);\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\n/**\n * Suppress layers that exist to contribute hook groups when hooks are disabled.\n *\n * Lives beside layerHookGroups because it is the inverse question about the same\n * field, and because every caller that resolves layers has to ask it.\n */\nexport function filterHookDisabledLayers(\n config: MajorModesConfig,\n layers: readonly string[],\n hooks: boolean,\n): string[] {\n if (hooks) return [...layers];\n return layers.filter((layer) => (config.layers[layer]?.hookGroups?.length ?? 0) === 0);\n}\n"],"mappings":"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"}
@@ -45,6 +45,8 @@ interface MajorModeDefinition {
45
45
  layers: string[];
46
46
  }
47
47
  interface MajorModesConfig {
48
+ /** Unconditional package bundle loaded before packages from selected named layers. */
49
+ default?: ResolvedLayerDefinition;
48
50
  layers: Record<string, ResolvedLayerDefinition>;
49
51
  /** The mode selected when neither a flag nor the environment names one. */
50
52
  defaultMajorMode: string;
@@ -1 +1 @@
1
- {"version":3,"file":"majorModes.d.cts","names":[],"sources":["../../src/adapters/majorModes.ts"],"mappings":";;KAIY,qBAAqB;;UAGhB;EACf;EACA;EACA,SAAS;;;UAIM;EACf;EACA;EACA,QAAQ;EACR;EACA;;UAGe;;;;;;;EAOf;;;;;;;EAOA,WAAW,eAAe;EAC1B;;;UAIe,gCAAgC;EAC/C;;EAEA;;UAGe;;EAEf;;EAEA;;UAGe;EACf,QAAQ,eAAe;;EAEvB;;EAEA,yBAAyB;;EAEzB,WAAW,eAAe;;EAE1B,mBAAmB,eAAe;;UAGnB;EACf,WAAW;EACX,eAAe;EACf,uBAAuB;;EAEvB,kBAAkB;;EAElB,0BAA0B;;EAE1B,aAAa,mBAAmB;;EAEhC,gBAAgB,mBAAmB;;;;;;;;iBAoBrB,wBAAwB;;;;;;;;;iBAwJxB,qBAAqB,kBAAkB,yBAAuC;;iBAwD9E,6BACd,QAAQ,kBACR,iBACA,sBACC;;iBAuBa,cAAc,QAAQ,kBAAkB;;iBAqBxC,aAAa,QAAQ,kBAAkB,cAAc,WAAW;;iBAmDhE,gBAAgB,QAAQ,kBAAkB;;;;;;;iBAc1C,yBACd,QAAQ,kBACR,2BACA"}
1
+ {"version":3,"file":"majorModes.d.cts","names":[],"sources":["../../src/adapters/majorModes.ts"],"mappings":";;KAIY,qBAAqB;;UAGhB;EACf;EACA;EACA,SAAS;;;UAIM;EACf;EACA;EACA,QAAQ;EACR;EACA;;UAGe;;;;;;;EAOf;;;;;;;EAOA,WAAW,eAAe;EAC1B;;;UAIe,gCAAgC;EAC/C;;EAEA;;UAGe;;EAEf;;EAEA;;UAGe;;EAEf,UAAU;EACV,QAAQ,eAAe;;EAEvB;;EAEA,yBAAyB;;EAEzB,WAAW,eAAe;;EAE1B,mBAAmB,eAAe;;UAGnB;EACf,WAAW;EACX,eAAe;EACf,uBAAuB;;EAEvB,kBAAkB;;EAElB,0BAA0B;;EAE1B,aAAa,mBAAmB;;EAEhC,gBAAgB,mBAAmB;;;;;;;;iBAsBrB,wBAAwB;;;;;;;;;iBAmKxB,qBAAqB,kBAAkB,yBAAuC;;iBAiE9E,6BACd,QAAQ,kBACR,iBACA,sBACC;;iBA4Ba,cAAc,QAAQ,kBAAkB;;iBAqBxC,aAAa,QAAQ,kBAAkB,cAAc,WAAW;;iBAmDhE,gBAAgB,QAAQ,kBAAkB;;;;;;;iBAc1C,yBACd,QAAQ,kBACR,2BACA"}
@@ -45,6 +45,8 @@ interface MajorModeDefinition {
45
45
  layers: string[];
46
46
  }
47
47
  interface MajorModesConfig {
48
+ /** Unconditional package bundle loaded before packages from selected named layers. */
49
+ default?: ResolvedLayerDefinition;
48
50
  layers: Record<string, ResolvedLayerDefinition>;
49
51
  /** The mode selected when neither a flag nor the environment names one. */
50
52
  defaultMajorMode: string;
@@ -1 +1 @@
1
- {"version":3,"file":"majorModes.d.mts","names":[],"sources":["../../src/adapters/majorModes.ts"],"mappings":";;KAIY,qBAAqB;;UAGhB;EACf;EACA;EACA,SAAS;;;UAIM;EACf;EACA;EACA,QAAQ;EACR;EACA;;UAGe;;;;;;;EAOf;;;;;;;EAOA,WAAW,eAAe;EAC1B;;;UAIe,gCAAgC;EAC/C;;EAEA;;UAGe;;EAEf;;EAEA;;UAGe;EACf,QAAQ,eAAe;;EAEvB;;EAEA,yBAAyB;;EAEzB,WAAW,eAAe;;EAE1B,mBAAmB,eAAe;;UAGnB;EACf,WAAW;EACX,eAAe;EACf,uBAAuB;;EAEvB,kBAAkB;;EAElB,0BAA0B;;EAE1B,aAAa,mBAAmB;;EAEhC,gBAAgB,mBAAmB;;;;;;;;iBAoBrB,wBAAwB;;;;;;;;;iBAwJxB,qBAAqB,kBAAkB,yBAAuC;;iBAwD9E,6BACd,QAAQ,kBACR,iBACA,sBACC;;iBAuBa,cAAc,QAAQ,kBAAkB;;iBAqBxC,aAAa,QAAQ,kBAAkB,cAAc,WAAW;;iBAmDhE,gBAAgB,QAAQ,kBAAkB;;;;;;;iBAc1C,yBACd,QAAQ,kBACR,2BACA"}
1
+ {"version":3,"file":"majorModes.d.mts","names":[],"sources":["../../src/adapters/majorModes.ts"],"mappings":";;KAIY,qBAAqB;;UAGhB;EACf;EACA;EACA,SAAS;;;UAIM;EACf;EACA;EACA,QAAQ;EACR;EACA;;UAGe;;;;;;;EAOf;;;;;;;EAOA,WAAW,eAAe;EAC1B;;;UAIe,gCAAgC;EAC/C;;EAEA;;UAGe;;EAEf;;EAEA;;UAGe;;EAEf,UAAU;EACV,QAAQ,eAAe;;EAEvB;;EAEA,yBAAyB;;EAEzB,WAAW,eAAe;;EAE1B,mBAAmB,eAAe;;UAGnB;EACf,WAAW;EACX,eAAe;EACf,uBAAuB;;EAEvB,kBAAkB;;EAElB,0BAA0B;;EAE1B,aAAa,mBAAmB;;EAEhC,gBAAgB,mBAAmB;;;;;;;;iBAsBrB,wBAAwB;;;;;;;;;iBAmKxB,qBAAqB,kBAAkB,yBAAuC;;iBAiE9E,6BACd,QAAQ,kBACR,iBACA,sBACC;;iBA4Ba,cAAc,QAAQ,kBAAkB;;iBAqBxC,aAAa,QAAQ,kBAAkB,cAAc,WAAW;;iBAmDhE,gBAAgB,QAAQ,kBAAkB;;;;;;;iBAc1C,yBACd,QAAQ,kBACR,2BACA"}
@@ -1,2 +1,2 @@
1
- import{DOOM_DIR as e,readDoomConfigSources as t}from"./layeredConfig.mjs";import n from"node:path";import r from"node:os";const i=`modes.yaml`,a=n.join(e,i),o=[`./`,`../`];function s(e){return n.isAbsolute(e)||o.some(t=>e.startsWith(t))}const c=[`name`,`optional`,`config`],l=[`description`,`layers`];function u(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function d(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 f(e,t,n){let r=`Layer "${e}" package[${n}] in ${a}`;if(typeof t==`string`){if(!t.trim())throw Error(`${r} must be a non-empty string or mapping`);return t.trim()}if(!u(t))throw Error(`${r} must be a non-empty string or mapping`);if(d(t,c,r),typeof t.name!=`string`||!t.name.trim())throw Error(`${r}.name must be a non-empty string`);if(t.optional!==void 0&&typeof t.optional!=`boolean`)throw Error(`${r}.optional must be a boolean`);if(t.config!==void 0&&!u(t.config))throw Error(`${r}.config must be a mapping`);return{name:t.name.trim(),...t.optional===!0?{optional:!0}:{},...t.config?{config:{...t.config}}:{}}}function p(e,t){if(t===void 0)return;let n=`Layer "${e}" extensions in ${a}`;if(!Array.isArray(t))throw Error(`${n} must be an array`);return t.map((e,t)=>{if(typeof e!=`string`||!e.trim())throw Error(`${n}[${t}] must be a non-empty string`);return e.trim()})}function m(e,t){if(t!==void 0){if(!Array.isArray(t))throw Error(`Layer "${e}" packages in ${a} must be an array`);return t.map((t,n)=>f(e,t,n))}}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){let n=`Major mode "${e}" layers in ${a}`;if(!Array.isArray(t))throw Error(`${n} must be an array`);return t.map((e,t)=>{if(typeof e!=`string`||!e.trim())throw Error(`${n}[${t}] must be a non-empty string`);return e.trim()})}function _(e){return e.length>0?`Uses layers: ${e.join(`, `)}.`:`Uses Doom Pi core extensions only.`}function v(e,t){if(Array.isArray(t)){let n=g(e,t);return{description:_(n),layers:n}}let n=`Major mode "${e}" in ${a}`;if(!u(t))throw Error(`${n} must be a mapping with description and layers`);if(d(t,l,n),typeof t.description!=`string`||!t.description.trim())throw Error(`${n}.description must be a non-empty string`);return{description:t.description.trim(),layers:g(e,t.layers)}}function y(e){if(e===void 0)return{};if(!u(e))throw Error(`majorMode in ${a} must be a mapping`);return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,t===null?null:v(e,t)]))}function b(e,t){if(!u(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`);if(`config`in t)throw Error(`Layer "${e}" in ${a} uses unsupported field "config"; move it under the package entry that owns it`);let n=p(e,t.extensions),r=m(e,t.packages);return{...t,...n===void 0?{}:{extensions:n},...r===void 0?{}:{packages:r}}}function x(e){if(e===void 0)return{};if(!u(e))throw Error(`layers in ${a} must be a mapping`);return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,t===null?null:b(e,t)]))}function S(e,n=r.homedir()){let o=t(i,e,n),s={},c={},l={},u=`copilot`,d,f=!1;for(let e of o){for(let[t,n]of Object.entries(x(e.document.layers))){if(n===null){delete s[t];continue}s[t]={...n,baseDirectory:e.baseDirectory,filePath:e.filePath}}Object.hasOwn(e.document,`defaultMajorMode`)&&(u=h(e.document.defaultMajorMode),d={filePath:e.filePath,baseDirectory:e.baseDirectory},f=!0);for(let[t,n]of Object.entries(y(e.document.majorMode))){if(n===null){delete c[t],delete l[t];continue}c[t]=n,l[t]={filePath:e.filePath,baseDirectory:e.baseDirectory}}}for(let[e,t]of Object.entries(c))for(let n of t.layers)if(!s[n])throw Error(`Unknown layer "${n}" in majorMode.${e} of ${a}`);if(f&&!Object.hasOwn(c,u))throw Error(`Unknown default major mode "${u}" in ${a}`);return{layers:s,defaultMajorMode:u,...d?{defaultMajorModeSource:d}:{},majorMode:c,majorModeSources:l}}function C(e,t,n){let r=n.trim();if(!r)throw Error(`Package name must be a non-empty string`);let i=[];for(let n of t){let t=e.layers[n];if(!t)throw Error(`Unknown layer: ${n}`);for(let e of t.packages??[])typeof e!=`string`&&e.config!==void 0&&(e.name!==r&&!e.name.startsWith(`${r}/`)||i.push({layer:n,specifier:e.name,config:{...e.config},baseDirectory:t.baseDirectory,...t.filePath?{filePath:t.filePath}:{}}))}return i}function w(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].layers]}function T(e,t,n){let r=e.localEntries?.(t,n);if(r!==void 0)return r;let i=e.localEntry(t,n);return i?[i]:void 0}function E(e,t,n){let r=e.layers[t];if(!r)throw Error(`Unknown layer: ${t}`);let i=[];for(let e of r.extensions??[]){if(!s(e)){i.push(n.ownEntry(e));continue}let a=T(n,e,r.baseDirectory);if(!a?.length)throw Error(`Layer "${t}" extension "${e}" does not resolve under ${r.baseDirectory}. Point it at a Pi extension file or directory.`);i.push(...a)}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=T(n,a,r.baseDirectory);if(e?.length)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 declares pi.extensions.`);continue}if(o){let e=n.optionalPackageEntries?.(a);if(e===void 0){let t=n.optionalPackageEntry(a);e=t?[t]:void 0}e&&i.push(...e);continue}let c=n.packageEntries?.(a)??[n.packageEntry(a)];if(c.length===0)throw Error(`Layer "${t}" package "${a}" declares no Pi extensions.`);i.push(...c)}return i}function D(e,t){let n=new Set;for(let r of t)for(let t of e.layers[r]?.hookGroups??[])n.add(t);return[...n]}function O(e,t,n){return n?[...t]:t.filter(t=>(e.layers[t]?.hookGroups?.length??0)===0)}export{O as filterHookDisabledLayers,s as isLocalPackageSpecifier,E as layerEntries,D as layerHookGroups,S as loadMajorModesConfig,w as resolveLayers,C as resolvePackageConfigurations};
1
+ import{DOOM_DIR as e,readDoomConfigSources as t}from"./layeredConfig.mjs";import n from"node:path";import r from"node:os";const i=`modes.yaml`,a=n.join(e,i),o=[`./`,`../`],s=[`packages`];function c(e){return n.isAbsolute(e)||o.some(t=>e.startsWith(t))}const l=[`name`,`optional`,`config`],u=[`description`,`layers`];function d(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function f(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 p(e,t,n){let r=`Layer "${e}" package[${n}] in ${a}`;if(typeof t==`string`){if(!t.trim())throw Error(`${r} must be a non-empty string or mapping`);return t.trim()}if(!d(t))throw Error(`${r} must be a non-empty string or mapping`);if(f(t,l,r),typeof t.name!=`string`||!t.name.trim())throw Error(`${r}.name must be a non-empty string`);if(t.optional!==void 0&&typeof t.optional!=`boolean`)throw Error(`${r}.optional must be a boolean`);if(t.config!==void 0&&!d(t.config))throw Error(`${r}.config must be a mapping`);return{name:t.name.trim(),...t.optional===!0?{optional:!0}:{},...t.config?{config:{...t.config}}:{}}}function m(e,t){if(t===void 0)return;let n=`Layer "${e}" extensions in ${a}`;if(!Array.isArray(t))throw Error(`${n} must be an array`);return t.map((e,t)=>{if(typeof e!=`string`||!e.trim())throw Error(`${n}[${t}] must be a non-empty string`);return e.trim()})}function h(e,t){if(t!==void 0){if(!Array.isArray(t))throw Error(`Layer "${e}" packages in ${a} must be an array`);return t.map((t,n)=>p(e,t,n))}}function g(e){if(typeof e!=`string`||!e.trim())throw Error(`defaultMajorMode in ${a} must be a non-empty string`);return e.trim()}function _(e,t){let n=`Major mode "${e}" layers in ${a}`;if(!Array.isArray(t))throw Error(`${n} must be an array`);return t.map((e,t)=>{if(typeof e!=`string`||!e.trim())throw Error(`${n}[${t}] must be a non-empty string`);return e.trim()})}function v(e){return e.length>0?`Uses layers: ${e.join(`, `)}.`:`Uses Doom Pi core extensions only.`}function y(e,t){if(Array.isArray(t)){let n=_(e,t);return{description:v(n),layers:n}}let n=`Major mode "${e}" in ${a}`;if(!d(t))throw Error(`${n} must be a mapping with description and layers`);if(f(t,u,n),typeof t.description!=`string`||!t.description.trim())throw Error(`${n}.description must be a non-empty string`);return{description:t.description.trim(),layers:_(e,t.layers)}}function b(e){if(e===void 0)return{};if(!d(e))throw Error(`majorMode in ${a} must be a mapping`);return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,t===null?null:y(e,t)]))}function x(e,t){if(!d(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`);if(`config`in t)throw Error(`Layer "${e}" in ${a} uses unsupported field "config"; move it under the package entry that owns it`);let n=m(e,t.extensions),r=h(e,t.packages);return{...t,...n===void 0?{}:{extensions:n},...r===void 0?{}:{packages:r}}}function S(e){if(e===void 0)return{};if(!d(e))throw Error(`layers in ${a} must be a mapping`);return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,t===null?null:x(e,t)]))}function C(e){let t=`default in ${a}`;if(!d(e))throw Error(`${t} must be a mapping with packages`);if(f(e,s,t),!Object.hasOwn(e,`packages`))throw Error(`Layer "default" packages in ${a} must be an array`);return{packages:h(`default`,e.packages)}}function w(e,n=r.homedir()){let o=t(i,e,n),s,c={},l={},u={},d=`copilot`,f,p=!1;for(let e of o){Object.hasOwn(e.document,`default`)&&(s={...C(e.document.default),baseDirectory:e.baseDirectory,filePath:e.filePath});for(let[t,n]of Object.entries(S(e.document.layers))){if(n===null){delete c[t];continue}c[t]={...n,baseDirectory:e.baseDirectory,filePath:e.filePath}}Object.hasOwn(e.document,`defaultMajorMode`)&&(d=g(e.document.defaultMajorMode),f={filePath:e.filePath,baseDirectory:e.baseDirectory},p=!0);for(let[t,n]of Object.entries(b(e.document.majorMode))){if(n===null){delete l[t],delete u[t];continue}l[t]=n,u[t]={filePath:e.filePath,baseDirectory:e.baseDirectory}}}for(let[e,t]of Object.entries(l))for(let n of t.layers)if(!c[n])throw Error(`Unknown layer "${n}" in majorMode.${e} of ${a}`);if(p&&!Object.hasOwn(l,d))throw Error(`Unknown default major mode "${d}" in ${a}`);return{...s?{default:s}:{},layers:c,defaultMajorMode:d,...f?{defaultMajorModeSource:f}:{},majorMode:l,majorModeSources:u}}function T(e,t,n){let r=n.trim();if(!r)throw Error(`Package name must be a non-empty string`);let i=[],a=[];e.default&&a.push({name:`default`,definition:e.default});for(let n of t){let t=e.layers[n];if(!t)throw Error(`Unknown layer: ${n}`);a.push({name:n,definition:t})}for(let{name:e,definition:t}of a)for(let n of t.packages??[])typeof n!=`string`&&n.config!==void 0&&(n.name!==r&&!n.name.startsWith(`${r}/`)||i.push({layer:e,specifier:n.name,config:{...n.config},baseDirectory:t.baseDirectory,...t.filePath?{filePath:t.filePath}:{}}));return i}function E(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].layers]}function D(e,t,n){let r=e.localEntries?.(t,n);if(r!==void 0)return r;let i=e.localEntry(t,n);return i?[i]:void 0}function O(e,t,n){let r=e.layers[t];if(!r)throw Error(`Unknown layer: ${t}`);let i=[];for(let e of r.extensions??[]){if(!c(e)){i.push(n.ownEntry(e));continue}let a=D(n,e,r.baseDirectory);if(!a?.length)throw Error(`Layer "${t}" extension "${e}" does not resolve under ${r.baseDirectory}. Point it at a Pi extension file or directory.`);i.push(...a)}for(let e of r.packages??[]){let a=typeof e==`string`?e:e.name,o=typeof e!=`string`&&e.optional===!0;if(c(a)){let e=D(n,a,r.baseDirectory);if(e?.length)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 declares pi.extensions.`);continue}if(o){let e=n.optionalPackageEntries?.(a);if(e===void 0){let t=n.optionalPackageEntry(a);e=t?[t]:void 0}e&&i.push(...e);continue}let s=n.packageEntries?.(a)??[n.packageEntry(a)];if(s.length===0)throw Error(`Layer "${t}" package "${a}" declares no Pi extensions.`);i.push(...s)}return i}function k(e,t){let n=new Set;for(let r of t)for(let t of e.layers[r]?.hookGroups??[])n.add(t);return[...n]}function A(e,t,n){return n?[...t]:t.filter(t=>(e.layers[t]?.hookGroups?.length??0)===0)}export{A as filterHookDisabledLayers,c as isLocalPackageSpecifier,O as layerEntries,k as layerHookGroups,w as loadMajorModesConfig,E as resolveLayers,T as resolvePackageConfigurations};
2
2
  //# sourceMappingURL=majorModes.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"majorModes.mjs","names":[],"sources":["../../src/adapters/majorModes.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\nimport { DOOM_DIR, type DoomConfigProvenance, readDoomConfigSources } from './layeredConfig.ts';\n\nexport type LayerPackageConfig = Record<string, unknown>;\n\n/** One package selected by a layer, plus configuration owned by that package. */\nexport interface LayerPackage {\n name: string;\n optional?: boolean;\n config?: LayerPackageConfig;\n}\n\n/** One configured package found while walking selected layers in order. */\nexport interface ResolvedPackageConfiguration {\n layer: string;\n specifier: string;\n config: LayerPackageConfig;\n baseDirectory: string;\n filePath?: string;\n}\n\nexport interface LayerDefinition {\n /**\n * DoomPi built-in names, or Pi-compatible local extension paths.\n * Bare names resolve from DoomPi's own extension entries. Paths beginning\n * with `./` or `../`, plus absolute paths, resolve against the config that\n * declares the layer and may name a script file or extension directory.\n */\n extensions?: string[];\n /**\n * npm specifiers, or local package paths retained for compatibility.\n * Relative paths resolve against the repository root for `.doom/modes.yaml`\n * and against the global `.doom` directory for\n * `~/.pi/.doom/modes.yaml`.\n */\n packages?: Array<string | LayerPackage>;\n hookGroups?: string[];\n}\n\n/** A layer plus the root its relative extension and package paths resolve against. */\nexport interface ResolvedLayerDefinition extends LayerDefinition {\n baseDirectory: string;\n /** Absolute path of the modes file that declared this layer. */\n filePath?: string;\n}\n\nexport interface MajorModeDefinition {\n /** Human-readable purpose used by pickers and agent-facing catalogs. */\n description: string;\n /** Ordered layer names activated by this mode. */\n layers: 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 /** Source that last declared the default, absent for the built-in fallback. */\n defaultMajorModeSource?: DoomConfigProvenance;\n /** Named major modes, including their purpose and ordered layers. */\n majorMode: Record<string, MajorModeDefinition>;\n /** Declaring source for each surviving named mode. */\n majorModeSources?: Record<string, DoomConfigProvenance>;\n}\n\nexport interface LayerResolvers {\n ownEntry: (name: string) => string;\n packageEntry: (name: string) => string;\n optionalPackageEntry: (name: string) => string | undefined;\n /** Resolves every standard package.json pi.extensions entry for a package root. */\n packageEntries?: (name: string) => string[];\n /** Optional counterpart to packageEntries. */\n optionalPackageEntries?: (name: string) => string[] | undefined;\n /** Resolves one path-style layer entry against the config that declared it. */\n localEntry: (specifier: string, baseDirectory: string) => string | undefined;\n /** Resolves every Pi extension entry contributed by a local file or directory. */\n localEntries?: (specifier: string, baseDirectory: string) => string[] | undefined;\n}\n\ninterface ModesDocument {\n layers?: unknown;\n defaultMajorMode?: unknown;\n majorMode?: unknown;\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 value names a path rather than a built-in or installed package.\n *\n * The same rule Node applies to import specifiers, so a bare name is always a\n * logical name 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_PACKAGE_KEYS = ['name', 'optional', 'config'] as const;\nconst MAJOR_MODE_KEYS = ['description', 'layers'] as const;\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 parseLayerPackage(layerName: string, value: unknown, index: number): string | LayerPackage {\n const location = `Layer \"${layerName}\" package[${index}] in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (typeof value === 'string') {\n if (!value.trim()) throw new Error(`${location} must be a non-empty string or mapping`);\n return value.trim();\n }\n if (!isRecord(value)) throw new Error(`${location} must be a non-empty string or mapping`);\n assertKnownKeys(value, LAYER_PACKAGE_KEYS, location);\n if (typeof value.name !== 'string' || !value.name.trim()) {\n throw new Error(`${location}.name must be a non-empty string`);\n }\n if (value.optional !== undefined && typeof value.optional !== 'boolean') {\n throw new Error(`${location}.optional must be a boolean`);\n }\n if (value.config !== undefined && !isRecord(value.config)) {\n throw new Error(`${location}.config must be a mapping`);\n }\n return {\n name: value.name.trim(),\n ...(value.optional === true ? { optional: true } : {}),\n ...(value.config ? { config: { ...value.config } } : {}),\n };\n}\n\nfunction parseLayerExtensions(layerName: string, value: unknown): string[] | undefined {\n if (value === undefined) return undefined;\n const location = `Layer \"${layerName}\" extensions in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!Array.isArray(value)) throw new Error(`${location} must be an array`);\n return value.map((entry, index) => {\n if (typeof entry !== 'string' || !entry.trim()) {\n throw new Error(`${location}[${index}] must be a non-empty string`);\n }\n return entry.trim();\n });\n}\n\nfunction parseLayerPackages(layerName: string, value: unknown): Array<string | LayerPackage> | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value)) {\n throw new Error(`Layer \"${layerName}\" packages in ${MAJOR_MODES_RELATIVE_PATH} must be an array`);\n }\n return value.map((entry, index) => parseLayerPackage(layerName, entry, index));\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 parseMajorModeLayers(modeName: string, value: unknown): string[] {\n const location = `Major mode \"${modeName}\" layers in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!Array.isArray(value)) throw new Error(`${location} must be an array`);\n return value.map((layerName, index) => {\n if (typeof layerName !== 'string' || !layerName.trim()) {\n throw new Error(`${location}[${index}] must be a non-empty string`);\n }\n return layerName.trim();\n });\n}\n\nfunction legacyMajorModeDescription(layers: readonly string[]): string {\n return layers.length > 0 ? `Uses layers: ${layers.join(', ')}.` : 'Uses Doom Pi core extensions only.';\n}\n\nfunction parseMajorMode(name: string, value: unknown): MajorModeDefinition {\n if (Array.isArray(value)) {\n const layers = parseMajorModeLayers(name, value);\n return { description: legacyMajorModeDescription(layers), layers };\n }\n\n const location = `Major mode \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!isRecord(value)) throw new Error(`${location} must be a mapping with description and layers`);\n assertKnownKeys(value, MAJOR_MODE_KEYS, location);\n if (typeof value.description !== 'string' || !value.description.trim()) {\n throw new Error(`${location}.description must be a non-empty string`);\n }\n return {\n description: value.description.trim(),\n layers: parseMajorModeLayers(name, value.layers),\n };\n}\n\nfunction parseMajorModes(value: unknown): Record<string, MajorModeDefinition | null> {\n if (value === undefined) return {};\n if (!isRecord(value)) throw new Error(`majorMode in ${MAJOR_MODES_RELATIVE_PATH} must be a mapping`);\n return Object.fromEntries(\n Object.entries(value).map(([name, definition]) => [\n name,\n definition === null ? null : parseMajorMode(name, definition),\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 if ('config' in definition) {\n throw new Error(\n `Layer \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH} uses unsupported field \"config\"; move it under the package entry that owns it`,\n );\n }\n const extensions = parseLayerExtensions(name, definition.extensions);\n const packages = parseLayerPackages(name, definition.packages);\n return {\n ...definition,\n ...(extensions === undefined ? {} : { extensions }),\n ...(packages === undefined ? {} : { packages }),\n } as LayerDefinition;\n}\n\nfunction parseLayers(value: unknown): Record<string, LayerDefinition | null> {\n if (value === undefined) return {};\n if (!isRecord(value)) throw new Error(`layers in ${MAJOR_MODES_RELATIVE_PATH} must be a mapping`);\n return Object.fromEntries(\n Object.entries(value).map(([name, definition]) => [\n name,\n definition === null ? null : parseLayer(name, definition),\n ]),\n );\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, MajorModeDefinition> = {};\n const majorModeSources: Record<string, DoomConfigProvenance> = {};\n let defaultMajorMode = FALLBACK_MAJOR_MODE;\n let defaultMajorModeSource: DoomConfigProvenance | undefined;\n let hasConfiguredDefault = false;\n for (const source of sources) {\n for (const [name, definition] of Object.entries(parseLayers(source.document.layers))) {\n if (definition === null) {\n delete layers[name];\n continue;\n }\n layers[name] = {\n ...definition,\n baseDirectory: source.baseDirectory,\n filePath: source.filePath,\n };\n }\n if (Object.hasOwn(source.document, 'defaultMajorMode')) {\n defaultMajorMode = parseDefaultMajorMode(source.document.defaultMajorMode);\n defaultMajorModeSource = { filePath: source.filePath, baseDirectory: source.baseDirectory };\n hasConfiguredDefault = true;\n }\n for (const [name, definition] of Object.entries(parseMajorModes(source.document.majorMode))) {\n if (definition === null) {\n delete majorMode[name];\n delete majorModeSources[name];\n continue;\n }\n majorMode[name] = definition;\n majorModeSources[name] = { filePath: source.filePath, baseDirectory: source.baseDirectory };\n }\n }\n\n for (const [modeName, definition] of Object.entries(majorMode)) {\n for (const name of definition.layers) {\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 {\n layers,\n defaultMajorMode,\n ...(defaultMajorModeSource ? { defaultMajorModeSource } : {}),\n majorMode,\n majorModeSources,\n };\n}\n\n/** Finds configuration owned by one package while preserving layer and package order. */\nexport function resolvePackageConfigurations(\n config: MajorModesConfig,\n names: string[],\n packageName: string,\n): ResolvedPackageConfiguration[] {\n const normalizedPackageName = packageName.trim();\n if (!normalizedPackageName) throw new Error('Package name must be a non-empty string');\n const configurations: ResolvedPackageConfiguration[] = [];\n for (const name of names) {\n const layer = config.layers[name];\n if (!layer) throw new Error(`Unknown layer: ${name}`);\n for (const entry of layer.packages ?? []) {\n if (typeof entry === 'string' || entry.config === undefined) continue;\n if (entry.name !== normalizedPackageName && !entry.name.startsWith(`${normalizedPackageName}/`)) continue;\n configurations.push({\n layer: name,\n specifier: entry.name,\n config: { ...entry.config },\n baseDirectory: layer.baseDirectory,\n ...(layer.filePath ? { filePath: layer.filePath } : {}),\n });\n }\n }\n return configurations;\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]!.layers];\n}\n\nfunction resolveLocalEntries(\n resolvers: LayerResolvers,\n specifier: string,\n baseDirectory: string,\n): string[] | undefined {\n const resolved = resolvers.localEntries?.(specifier, baseDirectory);\n if (resolved !== undefined) return resolved;\n const single = resolvers.localEntry(specifier, baseDirectory);\n return single ? [single] : undefined;\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 ?? []) {\n if (!isLocalPackageSpecifier(entry)) {\n entries.push(resolvers.ownEntry(entry));\n continue;\n }\n const resolved = resolveLocalEntries(resolvers, entry, layer.baseDirectory);\n if (!resolved?.length) {\n throw new Error(\n `Layer \"${name}\" extension \"${entry}\" does not resolve under ${layer.baseDirectory}. ` +\n 'Point it at a Pi extension file or directory.',\n );\n }\n entries.push(...resolved);\n }\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 = resolveLocalEntries(resolvers, specifier, layer.baseDirectory);\n if (resolved?.length) 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 declares pi.extensions.',\n );\n }\n continue;\n }\n if (optional) {\n let resolved = resolvers.optionalPackageEntries?.(specifier);\n if (resolved === undefined) {\n const single = resolvers.optionalPackageEntry(specifier);\n resolved = single ? [single] : undefined;\n }\n if (resolved) entries.push(...resolved);\n continue;\n }\n const resolved = resolvers.packageEntries?.(specifier) ?? [resolvers.packageEntry(specifier)];\n if (resolved.length === 0) {\n throw new Error(`Layer \"${name}\" package \"${specifier}\" declares no Pi extensions.`);\n }\n entries.push(...resolved);\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\n/**\n * Suppress layers that exist to contribute hook groups when hooks are disabled.\n *\n * Lives beside layerHookGroups because it is the inverse question about the same\n * field, and because every caller that resolves layers has to ask it.\n */\nexport function filterHookDisabledLayers(\n config: MajorModesConfig,\n layers: readonly string[],\n hooks: boolean,\n): string[] {\n if (hooks) return [...layers];\n return layers.filter((layer) => (config.layers[layer]?.hookGroups?.length ?? 0) === 0);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"majorModes.mjs","names":[],"sources":["../../src/adapters/majorModes.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\nimport { DOOM_DIR, type DoomConfigProvenance, readDoomConfigSources } from './layeredConfig.ts';\n\nexport type LayerPackageConfig = Record<string, unknown>;\n\n/** One package selected by a layer, plus configuration owned by that package. */\nexport interface LayerPackage {\n name: string;\n optional?: boolean;\n config?: LayerPackageConfig;\n}\n\n/** One configured package found while walking selected layers in order. */\nexport interface ResolvedPackageConfiguration {\n layer: string;\n specifier: string;\n config: LayerPackageConfig;\n baseDirectory: string;\n filePath?: string;\n}\n\nexport interface LayerDefinition {\n /**\n * DoomPi built-in names, or Pi-compatible local extension paths.\n * Bare names resolve from DoomPi's own extension entries. Paths beginning\n * with `./` or `../`, plus absolute paths, resolve against the config that\n * declares the layer and may name a script file or extension directory.\n */\n extensions?: string[];\n /**\n * npm specifiers, or local package paths retained for compatibility.\n * Relative paths resolve against the repository root for `.doom/modes.yaml`\n * and against the global `.doom` directory for\n * `~/.pi/.doom/modes.yaml`.\n */\n packages?: Array<string | LayerPackage>;\n hookGroups?: string[];\n}\n\n/** A layer plus the root its relative extension and package paths resolve against. */\nexport interface ResolvedLayerDefinition extends LayerDefinition {\n baseDirectory: string;\n /** Absolute path of the modes file that declared this layer. */\n filePath?: string;\n}\n\nexport interface MajorModeDefinition {\n /** Human-readable purpose used by pickers and agent-facing catalogs. */\n description: string;\n /** Ordered layer names activated by this mode. */\n layers: string[];\n}\n\nexport interface MajorModesConfig {\n /** Unconditional package bundle loaded before packages from selected named layers. */\n default?: ResolvedLayerDefinition;\n layers: Record<string, ResolvedLayerDefinition>;\n /** The mode selected when neither a flag nor the environment names one. */\n defaultMajorMode: string;\n /** Source that last declared the default, absent for the built-in fallback. */\n defaultMajorModeSource?: DoomConfigProvenance;\n /** Named major modes, including their purpose and ordered layers. */\n majorMode: Record<string, MajorModeDefinition>;\n /** Declaring source for each surviving named mode. */\n majorModeSources?: Record<string, DoomConfigProvenance>;\n}\n\nexport interface LayerResolvers {\n ownEntry: (name: string) => string;\n packageEntry: (name: string) => string;\n optionalPackageEntry: (name: string) => string | undefined;\n /** Resolves every standard package.json pi.extensions entry for a package root. */\n packageEntries?: (name: string) => string[];\n /** Optional counterpart to packageEntries. */\n optionalPackageEntries?: (name: string) => string[] | undefined;\n /** Resolves one path-style layer entry against the config that declared it. */\n localEntry: (specifier: string, baseDirectory: string) => string | undefined;\n /** Resolves every Pi extension entry contributed by a local file or directory. */\n localEntries?: (specifier: string, baseDirectory: string) => string[] | undefined;\n}\n\ninterface ModesDocument {\n default?: unknown;\n layers?: unknown;\n defaultMajorMode?: unknown;\n majorMode?: unknown;\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;\nconst DEFAULT_KEYS = ['packages'] as const;\n\n/**\n * Whether a layer value names a path rather than a built-in or installed package.\n *\n * The same rule Node applies to import specifiers, so a bare name is always a\n * logical name 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_PACKAGE_KEYS = ['name', 'optional', 'config'] as const;\nconst MAJOR_MODE_KEYS = ['description', 'layers'] as const;\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 parseLayerPackage(layerName: string, value: unknown, index: number): string | LayerPackage {\n const location = `Layer \"${layerName}\" package[${index}] in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (typeof value === 'string') {\n if (!value.trim()) throw new Error(`${location} must be a non-empty string or mapping`);\n return value.trim();\n }\n if (!isRecord(value)) throw new Error(`${location} must be a non-empty string or mapping`);\n assertKnownKeys(value, LAYER_PACKAGE_KEYS, location);\n if (typeof value.name !== 'string' || !value.name.trim()) {\n throw new Error(`${location}.name must be a non-empty string`);\n }\n if (value.optional !== undefined && typeof value.optional !== 'boolean') {\n throw new Error(`${location}.optional must be a boolean`);\n }\n if (value.config !== undefined && !isRecord(value.config)) {\n throw new Error(`${location}.config must be a mapping`);\n }\n return {\n name: value.name.trim(),\n ...(value.optional === true ? { optional: true } : {}),\n ...(value.config ? { config: { ...value.config } } : {}),\n };\n}\n\nfunction parseLayerExtensions(layerName: string, value: unknown): string[] | undefined {\n if (value === undefined) return undefined;\n const location = `Layer \"${layerName}\" extensions in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!Array.isArray(value)) throw new Error(`${location} must be an array`);\n return value.map((entry, index) => {\n if (typeof entry !== 'string' || !entry.trim()) {\n throw new Error(`${location}[${index}] must be a non-empty string`);\n }\n return entry.trim();\n });\n}\n\nfunction parseLayerPackages(layerName: string, value: unknown): Array<string | LayerPackage> | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value)) {\n throw new Error(`Layer \"${layerName}\" packages in ${MAJOR_MODES_RELATIVE_PATH} must be an array`);\n }\n return value.map((entry, index) => parseLayerPackage(layerName, entry, index));\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 parseMajorModeLayers(modeName: string, value: unknown): string[] {\n const location = `Major mode \"${modeName}\" layers in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!Array.isArray(value)) throw new Error(`${location} must be an array`);\n return value.map((layerName, index) => {\n if (typeof layerName !== 'string' || !layerName.trim()) {\n throw new Error(`${location}[${index}] must be a non-empty string`);\n }\n return layerName.trim();\n });\n}\n\nfunction legacyMajorModeDescription(layers: readonly string[]): string {\n return layers.length > 0 ? `Uses layers: ${layers.join(', ')}.` : 'Uses Doom Pi core extensions only.';\n}\n\nfunction parseMajorMode(name: string, value: unknown): MajorModeDefinition {\n if (Array.isArray(value)) {\n const layers = parseMajorModeLayers(name, value);\n return { description: legacyMajorModeDescription(layers), layers };\n }\n\n const location = `Major mode \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!isRecord(value)) throw new Error(`${location} must be a mapping with description and layers`);\n assertKnownKeys(value, MAJOR_MODE_KEYS, location);\n if (typeof value.description !== 'string' || !value.description.trim()) {\n throw new Error(`${location}.description must be a non-empty string`);\n }\n return {\n description: value.description.trim(),\n layers: parseMajorModeLayers(name, value.layers),\n };\n}\n\nfunction parseMajorModes(value: unknown): Record<string, MajorModeDefinition | null> {\n if (value === undefined) return {};\n if (!isRecord(value)) throw new Error(`majorMode in ${MAJOR_MODES_RELATIVE_PATH} must be a mapping`);\n return Object.fromEntries(\n Object.entries(value).map(([name, definition]) => [\n name,\n definition === null ? null : parseMajorMode(name, definition),\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 if ('config' in definition) {\n throw new Error(\n `Layer \"${name}\" in ${MAJOR_MODES_RELATIVE_PATH} uses unsupported field \"config\"; move it under the package entry that owns it`,\n );\n }\n const extensions = parseLayerExtensions(name, definition.extensions);\n const packages = parseLayerPackages(name, definition.packages);\n return {\n ...definition,\n ...(extensions === undefined ? {} : { extensions }),\n ...(packages === undefined ? {} : { packages }),\n } as LayerDefinition;\n}\n\nfunction parseLayers(value: unknown): Record<string, LayerDefinition | null> {\n if (value === undefined) return {};\n if (!isRecord(value)) throw new Error(`layers in ${MAJOR_MODES_RELATIVE_PATH} must be a mapping`);\n return Object.fromEntries(\n Object.entries(value).map(([name, definition]) => [\n name,\n definition === null ? null : parseLayer(name, definition),\n ]),\n );\n}\n\nfunction parseDefault(value: unknown): LayerDefinition {\n const location = `default in ${MAJOR_MODES_RELATIVE_PATH}`;\n if (!isRecord(value)) throw new Error(`${location} must be a mapping with packages`);\n assertKnownKeys(value, DEFAULT_KEYS, location);\n if (!Object.hasOwn(value, 'packages')) {\n throw new Error(`Layer \"default\" packages in ${MAJOR_MODES_RELATIVE_PATH} must be an array`);\n }\n const packages = parseLayerPackages('default', value.packages);\n return { packages };\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 let defaultDefinition: ResolvedLayerDefinition | undefined;\n const layers: Record<string, ResolvedLayerDefinition> = {};\n const majorMode: Record<string, MajorModeDefinition> = {};\n const majorModeSources: Record<string, DoomConfigProvenance> = {};\n let defaultMajorMode = FALLBACK_MAJOR_MODE;\n let defaultMajorModeSource: DoomConfigProvenance | undefined;\n let hasConfiguredDefault = false;\n for (const source of sources) {\n if (Object.hasOwn(source.document, 'default')) {\n defaultDefinition = {\n ...parseDefault(source.document.default),\n baseDirectory: source.baseDirectory,\n filePath: source.filePath,\n };\n }\n for (const [name, definition] of Object.entries(parseLayers(source.document.layers))) {\n if (definition === null) {\n delete layers[name];\n continue;\n }\n layers[name] = {\n ...definition,\n baseDirectory: source.baseDirectory,\n filePath: source.filePath,\n };\n }\n if (Object.hasOwn(source.document, 'defaultMajorMode')) {\n defaultMajorMode = parseDefaultMajorMode(source.document.defaultMajorMode);\n defaultMajorModeSource = { filePath: source.filePath, baseDirectory: source.baseDirectory };\n hasConfiguredDefault = true;\n }\n for (const [name, definition] of Object.entries(parseMajorModes(source.document.majorMode))) {\n if (definition === null) {\n delete majorMode[name];\n delete majorModeSources[name];\n continue;\n }\n majorMode[name] = definition;\n majorModeSources[name] = { filePath: source.filePath, baseDirectory: source.baseDirectory };\n }\n }\n\n for (const [modeName, definition] of Object.entries(majorMode)) {\n for (const name of definition.layers) {\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 {\n ...(defaultDefinition ? { default: defaultDefinition } : {}),\n layers,\n defaultMajorMode,\n ...(defaultMajorModeSource ? { defaultMajorModeSource } : {}),\n majorMode,\n majorModeSources,\n };\n}\n\n/** Finds configuration owned by one package while preserving layer and package order. */\nexport function resolvePackageConfigurations(\n config: MajorModesConfig,\n names: string[],\n packageName: string,\n): ResolvedPackageConfiguration[] {\n const normalizedPackageName = packageName.trim();\n if (!normalizedPackageName) throw new Error('Package name must be a non-empty string');\n const configurations: ResolvedPackageConfiguration[] = [];\n const definitions: Array<{ name: string; definition: ResolvedLayerDefinition }> = [];\n if (config.default) definitions.push({ name: 'default', definition: config.default });\n for (const name of names) {\n const definition = config.layers[name];\n if (!definition) throw new Error(`Unknown layer: ${name}`);\n definitions.push({ name, definition });\n }\n for (const { name, definition } of definitions) {\n for (const entry of definition.packages ?? []) {\n if (typeof entry === 'string' || entry.config === undefined) continue;\n if (entry.name !== normalizedPackageName && !entry.name.startsWith(`${normalizedPackageName}/`)) continue;\n configurations.push({\n layer: name,\n specifier: entry.name,\n config: { ...entry.config },\n baseDirectory: definition.baseDirectory,\n ...(definition.filePath ? { filePath: definition.filePath } : {}),\n });\n }\n }\n return configurations;\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]!.layers];\n}\n\nfunction resolveLocalEntries(\n resolvers: LayerResolvers,\n specifier: string,\n baseDirectory: string,\n): string[] | undefined {\n const resolved = resolvers.localEntries?.(specifier, baseDirectory);\n if (resolved !== undefined) return resolved;\n const single = resolvers.localEntry(specifier, baseDirectory);\n return single ? [single] : undefined;\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 ?? []) {\n if (!isLocalPackageSpecifier(entry)) {\n entries.push(resolvers.ownEntry(entry));\n continue;\n }\n const resolved = resolveLocalEntries(resolvers, entry, layer.baseDirectory);\n if (!resolved?.length) {\n throw new Error(\n `Layer \"${name}\" extension \"${entry}\" does not resolve under ${layer.baseDirectory}. ` +\n 'Point it at a Pi extension file or directory.',\n );\n }\n entries.push(...resolved);\n }\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 = resolveLocalEntries(resolvers, specifier, layer.baseDirectory);\n if (resolved?.length) 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 declares pi.extensions.',\n );\n }\n continue;\n }\n if (optional) {\n let resolved = resolvers.optionalPackageEntries?.(specifier);\n if (resolved === undefined) {\n const single = resolvers.optionalPackageEntry(specifier);\n resolved = single ? [single] : undefined;\n }\n if (resolved) entries.push(...resolved);\n continue;\n }\n const resolved = resolvers.packageEntries?.(specifier) ?? [resolvers.packageEntry(specifier)];\n if (resolved.length === 0) {\n throw new Error(`Layer \"${name}\" package \"${specifier}\" declares no Pi extensions.`);\n }\n entries.push(...resolved);\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\n/**\n * Suppress layers that exist to contribute hook groups when hooks are disabled.\n *\n * Lives beside layerHookGroups because it is the inverse question about the same\n * field, and because every caller that resolves layers has to ask it.\n */\nexport function filterHookDisabledLayers(\n config: MajorModesConfig,\n layers: readonly string[],\n hooks: boolean,\n): string[] {\n if (hooks) return [...layers];\n return layers.filter((layer) => (config.layers[layer]?.hookGroups?.length ?? 0) === 0);\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { GitPluginSource, PluginSource } from "../types/domains.cjs";
2
2
  //#region src/adapters/pluginCatalog.d.ts
3
- declare const MARKETPLACE_MANIFEST_RELATIVE_PATHS: readonly [".agents/plugins/marketplace.json", ".agents/plugins/api_marketplace.json", ".claude-plugin/marketplace.json", ".cursor-plugin/marketplace.json"];
3
+ declare const MARKETPLACE_MANIFEST_RELATIVE_PATHS: readonly ['.agents/plugins/marketplace.json', '.agents/plugins/api_marketplace.json', '.claude-plugin/marketplace.json', '.cursor-plugin/marketplace.json'];
4
4
  /** Matches Codex manifest precedence, including schema-gated root plugin.json. */
5
5
  declare function findPluginManifestPath(pluginRoot: string): string | undefined;
6
6
  declare function pluginDirectoryForSource(source: PluginSource, homeDoomDirectory: string): string;
@@ -1,6 +1,6 @@
1
1
  import { GitPluginSource, PluginSource } from "../types/domains.mjs";
2
2
  //#region src/adapters/pluginCatalog.d.ts
3
- declare const MARKETPLACE_MANIFEST_RELATIVE_PATHS: readonly [".agents/plugins/marketplace.json", ".agents/plugins/api_marketplace.json", ".claude-plugin/marketplace.json", ".cursor-plugin/marketplace.json"];
3
+ declare const MARKETPLACE_MANIFEST_RELATIVE_PATHS: readonly ['.agents/plugins/marketplace.json', '.agents/plugins/api_marketplace.json', '.claude-plugin/marketplace.json', '.cursor-plugin/marketplace.json'];
4
4
  /** Matches Codex manifest precedence, including schema-gated root plugin.json. */
5
5
  declare function findPluginManifestPath(pluginRoot: string): string | undefined;
6
6
  declare function pluginDirectoryForSource(source: PluginSource, homeDoomDirectory: string): string;
@@ -7,7 +7,7 @@ import { DoomConfig, PlanningModeConfig, ResolvedVoiceConfig, VoiceConfig } from
7
7
  * and a second hand-written list in the contributing package would drift. The
8
8
  * validating Sets below are built from these for the same reason.
9
9
  */
10
- declare const DOOM_PLANNING_THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
10
+ declare const DOOM_PLANNING_THINKING_LEVELS: readonly ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
11
11
  declare const DOOM_VOICE_ENGINES: readonly ["auto", "whisper-cpp", "openai-whisper", "mlx-whisper"];
12
12
  declare const DOOM_VOICE_TTS_ENGINES: readonly ["macos-say"];
13
13
  declare function parsePlanningModeConfig(value: unknown, filePath: string, location?: string): PlanningModeConfig;
@@ -7,7 +7,7 @@ import { DoomConfig, PlanningModeConfig, ResolvedVoiceConfig, VoiceConfig } from
7
7
  * and a second hand-written list in the contributing package would drift. The
8
8
  * validating Sets below are built from these for the same reason.
9
9
  */
10
- declare const DOOM_PLANNING_THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
10
+ declare const DOOM_PLANNING_THINKING_LEVELS: readonly ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
11
11
  declare const DOOM_VOICE_ENGINES: readonly ["auto", "whisper-cpp", "openai-whisper", "mlx-whisper"];
12
12
  declare const DOOM_VOICE_TTS_ENGINES: readonly ["macos-say"];
13
13
  declare function parsePlanningModeConfig(value: unknown, filePath: string, location?: string): PlanningModeConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-config",
3
- "version": "0.0.1-alpha.26",
3
+ "version": "0.0.1-alpha.28",
4
4
  "description": "Typed configuration loading, validation, and host adapters for composing DoomPi sessions.",
5
5
  "keywords": [
6
6
  "agent-configuration",
@@ -125,16 +125,16 @@
125
125
  "dependencies": {
126
126
  "@deepseek-ai/cordis": "4.0.1",
127
127
  "yaml": "2.9.0",
128
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.26",
129
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.26"
128
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.28",
129
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.28"
130
130
  },
131
131
  "devDependencies": {
132
132
  "@earendil-works/pi-coding-agent": "0.84.2",
133
- "@types/node": "26.1.1",
134
- "@vitest/coverage-v8": "4.1.10",
133
+ "@types/node": "26.2.0",
134
+ "@vitest/coverage-v8": "4.1.11",
135
135
  "tsdown": "0.22.14",
136
- "typescript": "6.0.3",
137
- "vitest": "4.1.10"
136
+ "typescript": "7.0.2",
137
+ "vitest": "4.1.11"
138
138
  },
139
139
  "peerDependencies": {
140
140
  "@earendil-works/pi-coding-agent": "0.84.2"
@@ -3,7 +3,7 @@
3
3
  ## Sources
4
4
 
5
5
  - `.doom/config.yaml` defines runtime settings such as project trust, editor integration, planning, and voice behavior.
6
- - `.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. Each major mode has a human-readable `description` and an ordered `layers` array, and a session runs under exactly one mode.
6
+ - `.doom/modes.yaml` defines default packages, named layers, and the major modes built from those layers. A layer may contribute extension entry names, package extension specifiers, and hook group IDs. Each major mode has a human-readable `description` and an ordered `layers` array, and a session runs under exactly one mode.
7
7
  - `.doom/domains.yaml` defines a named plugin catalog and 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.
8
8
  - `.doom/profiles.yaml` defines a named profile catalog. Roots discover persona folders, while explicit entries can attach environment defaults.
9
9
  - Each of these four documents 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.
@@ -15,6 +15,21 @@ Parsing and resolution belong to `@agimon-ai/doompi-config` (`config.ts`, `major
15
15
  `doompi init` emits rich major-mode mappings:
16
16
 
17
17
  ```yaml
18
+ default:
19
+ packages:
20
+ - '@agimon-ai/doompi-help'
21
+ - '@agimon-ai/doompi-hook'
22
+ - '@agimon-ai/doompi-goal'
23
+ - '@agimon-ai/doompi-voice'
24
+ - '@agimon-ai/doompi-runner'
25
+ - '@agimon-ai/doompi-file-edit'
26
+ - '@agimon-ai/doompi-autocompact'
27
+ - '@agimon-ai/doompi-loop'
28
+ - '@agimon-ai/doompi-plan'
29
+ - '@agimon-ai/doompi-workflow'
30
+ - '@agimon-ai/doompi-log'
31
+ - '@agimon-ai/doompi-mcp'
32
+
18
33
  layers:
19
34
  guardrails:
20
35
  extensions: [repositoryHooks, ./extensions/project-policy.ts]
@@ -31,9 +46,17 @@ Bare `extensions` values select DoomPi built-ins. Path-like values beginning wit
31
46
  `../`, plus absolute paths, load Pi-compatible local scripts or extension directories.
32
47
  They resolve from the repository root in project config and from `~/.pi/.doom` in global
33
48
  config. Keep npm package specifiers and package-owned configuration under `packages`.
34
- Path-like `packages` remain supported for compatibility with unpublished Pi packages.
49
+ Path-like `packages` remain supported for compatibility with unpublished Pi packages. The
50
+ top-level `default.packages` list accepts the same package entries and runs before packages from
51
+ the selected named layers. It replaces the former hardcoded distribution defaults. When
52
+ `default` is absent, DoomPi does not add a fallback package set. Use `default.packages: []` for
53
+ an explicit empty baseline. Fixed core packages remain active, and named layer definitions and
54
+ selection are unchanged.
35
55
 
36
56
  Home and repository `layers` and `majorMode` records merge by name. Distinct names from both files remain available; a same-named repository entry replaces the complete home entry. A repository may set an inherited named layer or major mode to `null` to remove it. Tombstones apply only to entries in these two named maps—`null` inside package arrays or package-owned `config` remains ordinary opaque data. Resolved layers, modes, and package configurations retain the declaring file path and base directory. Legacy array entries such as `copilot: [team]` are still accepted and normalized with a generated description, but new configuration should use the rich mapping emitted by `doompi init`.
57
+ When both files declare `default`, the repository block replaces the personal block as one whole
58
+ package list. Omitting `default` from the repository leaves an inherited personal definition in
59
+ place; no package fallback is synthesized when neither source declares one.
37
60
 
38
61
  ## Domain plugin catalog
39
62