@agimon-ai/doompi-config 0.0.1-alpha.2 → 0.0.1-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -14
- package/dist/adapters/atomicJson.cjs +2 -0
- package/dist/adapters/atomicJson.cjs.map +1 -0
- package/dist/adapters/atomicJson.d.cts +6 -0
- package/dist/adapters/atomicJson.d.cts.map +1 -0
- package/dist/adapters/atomicJson.d.mts +6 -0
- package/dist/adapters/atomicJson.d.mts.map +1 -0
- package/dist/adapters/atomicJson.mjs +2 -0
- package/dist/adapters/atomicJson.mjs.map +1 -0
- package/dist/adapters/config.cjs +2 -0
- package/dist/adapters/config.cjs.map +1 -0
- package/dist/adapters/config.d.cts +11 -0
- package/dist/adapters/config.d.cts.map +1 -0
- package/dist/adapters/config.d.mts +11 -0
- package/dist/adapters/config.d.mts.map +1 -0
- package/dist/adapters/config.mjs +2 -0
- package/dist/adapters/config.mjs.map +1 -0
- package/dist/adapters/configWriter.cjs +2 -0
- package/dist/adapters/configWriter.cjs.map +1 -0
- package/dist/adapters/configWriter.d.cts +52 -0
- package/dist/adapters/configWriter.d.cts.map +1 -0
- package/dist/adapters/configWriter.d.mts +52 -0
- package/dist/adapters/configWriter.d.mts.map +1 -0
- package/dist/adapters/configWriter.mjs +2 -0
- package/dist/adapters/configWriter.mjs.map +1 -0
- package/dist/adapters/domains.cjs +2 -0
- package/dist/adapters/domains.cjs.map +1 -0
- package/dist/adapters/domains.d.cts +25 -0
- package/dist/adapters/domains.d.cts.map +1 -0
- package/dist/adapters/domains.d.mts +25 -0
- package/dist/adapters/domains.d.mts.map +1 -0
- package/dist/adapters/domains.mjs +2 -0
- package/dist/adapters/domains.mjs.map +1 -0
- package/dist/adapters/harnessState.cjs +2 -0
- package/dist/adapters/harnessState.cjs.map +1 -0
- package/dist/adapters/harnessState.d.cts +50 -0
- package/dist/adapters/harnessState.d.cts.map +1 -0
- package/dist/adapters/harnessState.d.mts +50 -0
- package/dist/adapters/harnessState.d.mts.map +1 -0
- package/dist/adapters/harnessState.mjs +2 -0
- package/dist/adapters/harnessState.mjs.map +1 -0
- package/dist/adapters/harnessStore.cjs +2 -0
- package/dist/adapters/harnessStore.cjs.map +1 -0
- package/dist/adapters/harnessStore.d.cts +102 -0
- package/dist/adapters/harnessStore.d.cts.map +1 -0
- package/dist/adapters/harnessStore.d.mts +102 -0
- package/dist/adapters/harnessStore.d.mts.map +1 -0
- package/dist/adapters/harnessStore.mjs +2 -0
- package/dist/adapters/harnessStore.mjs.map +1 -0
- package/dist/adapters/init.cjs +178 -0
- package/dist/adapters/init.cjs.map +1 -0
- package/dist/adapters/init.d.cts +39 -0
- package/dist/adapters/init.d.cts.map +1 -0
- package/dist/adapters/init.d.mts +39 -0
- package/dist/adapters/init.d.mts.map +1 -0
- package/dist/adapters/init.mjs +178 -0
- package/dist/adapters/init.mjs.map +1 -0
- package/dist/adapters/layeredConfig.cjs +2 -0
- package/dist/adapters/layeredConfig.cjs.map +1 -0
- package/dist/adapters/layeredConfig.d.cts +49 -0
- package/dist/adapters/layeredConfig.d.cts.map +1 -0
- package/dist/adapters/layeredConfig.d.mts +49 -0
- package/dist/adapters/layeredConfig.d.mts.map +1 -0
- package/dist/adapters/layeredConfig.mjs +2 -0
- package/dist/adapters/layeredConfig.mjs.map +1 -0
- package/dist/adapters/majorModes.cjs +2 -0
- package/dist/adapters/majorModes.cjs.map +1 -0
- package/dist/adapters/majorModes.d.cts +88 -0
- package/dist/adapters/majorModes.d.cts.map +1 -0
- package/dist/adapters/majorModes.d.mts +88 -0
- package/dist/adapters/majorModes.d.mts.map +1 -0
- package/dist/adapters/majorModes.mjs +2 -0
- package/dist/adapters/majorModes.mjs.map +1 -0
- package/dist/adapters/pi/configExtension.cjs +2 -0
- package/dist/adapters/pi/configExtension.cjs.map +1 -0
- package/dist/adapters/pi/configExtension.d.cts +8 -0
- package/dist/adapters/pi/configExtension.d.cts.map +1 -0
- package/dist/adapters/pi/configExtension.d.mts +8 -0
- package/dist/adapters/pi/configExtension.d.mts.map +1 -0
- package/dist/adapters/pi/configExtension.mjs +2 -0
- package/dist/adapters/pi/configExtension.mjs.map +1 -0
- package/dist/adapters/pi/doomConfigExtension.cjs +2 -0
- package/dist/adapters/pi/doomConfigExtension.cjs.map +1 -0
- package/dist/adapters/pi/doomConfigExtension.d.cts +8 -0
- package/dist/adapters/pi/doomConfigExtension.d.cts.map +1 -0
- package/dist/adapters/pi/doomConfigExtension.d.mts +8 -0
- package/dist/adapters/pi/doomConfigExtension.d.mts.map +1 -0
- package/dist/adapters/pi/doomConfigExtension.mjs +2 -0
- package/dist/adapters/pi/doomConfigExtension.mjs.map +1 -0
- package/dist/adapters/pi/helpContribution.cjs +2 -0
- package/dist/adapters/pi/helpContribution.cjs.map +1 -0
- package/dist/adapters/pi/helpContribution.mjs +2 -0
- package/dist/adapters/pi/helpContribution.mjs.map +1 -0
- package/dist/adapters/pi/piContext.cjs +2 -0
- package/dist/adapters/pi/piContext.cjs.map +1 -0
- package/dist/adapters/pi/piContext.d.cts +20 -0
- package/dist/adapters/pi/piContext.d.cts.map +1 -0
- package/dist/adapters/pi/piContext.d.mts +20 -0
- package/dist/adapters/pi/piContext.d.mts.map +1 -0
- package/dist/adapters/pi/piContext.mjs +2 -0
- package/dist/adapters/pi/piContext.mjs.map +1 -0
- package/dist/adapters/piConfig.cjs +2 -0
- package/dist/adapters/piConfig.cjs.map +1 -0
- package/dist/adapters/piConfig.d.cts +8 -0
- package/dist/adapters/piConfig.d.cts.map +1 -0
- package/dist/adapters/piConfig.d.mts +8 -0
- package/dist/adapters/piConfig.d.mts.map +1 -0
- package/dist/adapters/piConfig.mjs +2 -0
- package/dist/adapters/piConfig.mjs.map +1 -0
- package/dist/adapters/pluginCatalog.cjs +2 -0
- package/dist/adapters/pluginCatalog.cjs.map +1 -0
- package/dist/adapters/pluginCatalog.d.cts +12 -0
- package/dist/adapters/pluginCatalog.d.cts.map +1 -0
- package/dist/adapters/pluginCatalog.d.mts +12 -0
- package/dist/adapters/pluginCatalog.d.mts.map +1 -0
- package/dist/adapters/pluginCatalog.mjs +2 -0
- package/dist/adapters/pluginCatalog.mjs.map +1 -0
- package/dist/adapters/profiles.cjs +4 -0
- package/dist/adapters/profiles.cjs.map +1 -0
- package/dist/adapters/profiles.d.cts +36 -0
- package/dist/adapters/profiles.d.cts.map +1 -0
- package/dist/adapters/profiles.d.mts +36 -0
- package/dist/adapters/profiles.d.mts.map +1 -0
- package/dist/adapters/profiles.mjs +4 -0
- package/dist/adapters/profiles.mjs.map +1 -0
- package/dist/atomicJson.cjs +1 -2
- package/dist/atomicJson.d.cts +2 -6
- package/dist/atomicJson.d.mts +2 -6
- package/dist/atomicJson.mjs +1 -2
- package/dist/config/piConfig.cjs +1 -2
- package/dist/config/piConfig.d.cts +2 -7
- package/dist/config/piConfig.d.mts +2 -7
- package/dist/config/piConfig.mjs +1 -2
- package/dist/config/schema.d.cts +2 -33
- package/dist/config/schema.d.mts +2 -33
- package/dist/config.cjs +1 -2
- package/dist/config.d.cts +3 -24
- package/dist/config.d.mts +3 -24
- package/dist/config.mjs +1 -2
- package/dist/configWriter.cjs +1 -2
- package/dist/configWriter.d.cts +2 -52
- package/dist/configWriter.d.mts +2 -52
- package/dist/configWriter.mjs +1 -2
- package/dist/container/doomConfigContainer.cjs +2 -0
- package/dist/container/doomConfigContainer.cjs.map +1 -0
- package/dist/container/doomConfigContainer.d.cts +12 -0
- package/dist/container/doomConfigContainer.d.cts.map +1 -0
- package/dist/container/doomConfigContainer.d.mts +12 -0
- package/dist/container/doomConfigContainer.d.mts.map +1 -0
- package/dist/container/doomConfigContainer.mjs +2 -0
- package/dist/container/doomConfigContainer.mjs.map +1 -0
- package/dist/container.cjs +1 -2
- package/dist/container.d.cts +2 -12
- package/dist/container.d.mts +2 -12
- package/dist/container.mjs +1 -2
- package/dist/domains.cjs +1 -2
- package/dist/domains.d.cts +6 -77
- package/dist/domains.d.mts +6 -77
- package/dist/domains.mjs +1 -2
- package/dist/extensions/doom.cjs +1 -2
- package/dist/extensions/doom.d.cts +2 -8
- package/dist/extensions/doom.d.mts +2 -8
- package/dist/extensions/doom.mjs +1 -2
- package/dist/extensions/pi.cjs +1 -2
- package/dist/extensions/pi.d.cts +2 -8
- package/dist/extensions/pi.d.mts +2 -8
- package/dist/extensions/pi.mjs +1 -2
- package/dist/harnessState.cjs +1 -2
- package/dist/harnessState.d.cts +2 -50
- package/dist/harnessState.d.mts +2 -50
- package/dist/harnessState.mjs +1 -2
- package/dist/harnessStore.cjs +1 -2
- package/dist/harnessStore.d.cts +2 -102
- package/dist/harnessStore.d.mts +2 -102
- package/dist/harnessStore.mjs +1 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +20 -16
- package/dist/index.d.mts +20 -16
- package/dist/index.mjs +1 -1
- package/dist/init.cjs +1 -76
- package/dist/init.d.cts +2 -33
- package/dist/init.d.mts +2 -33
- package/dist/init.mjs +1 -76
- package/dist/layeredConfig.cjs +1 -2
- package/dist/layeredConfig.d.cts +2 -49
- package/dist/layeredConfig.d.mts +2 -49
- package/dist/layeredConfig.mjs +1 -2
- package/dist/majorModes.cjs +1 -2
- package/dist/majorModes.d.cts +2 -75
- package/dist/majorModes.d.mts +2 -75
- package/dist/majorModes.mjs +1 -2
- package/dist/pi.cjs +1 -1
- package/dist/pi.d.cts +1 -1
- package/dist/pi.d.mts +1 -1
- package/dist/pi.mjs +1 -1
- package/dist/piContext.cjs +1 -2
- package/dist/piContext.d.cts +2 -19
- package/dist/piContext.d.mts +2 -19
- package/dist/piContext.mjs +1 -2
- package/dist/profiles.cjs +1 -4
- package/dist/profiles.d.cts +2 -35
- package/dist/profiles.d.mts +2 -35
- package/dist/profiles.mjs +1 -4
- package/dist/schemas/config/schema.d.cts +33 -0
- package/dist/schemas/config/schema.d.cts.map +1 -0
- package/dist/schemas/config/schema.d.mts +33 -0
- package/dist/schemas/config/schema.d.mts.map +1 -0
- package/dist/services/configPolicy.cjs +2 -0
- package/dist/services/configPolicy.cjs.map +1 -0
- package/dist/services/configPolicy.d.cts +19 -0
- package/dist/services/configPolicy.d.cts.map +1 -0
- package/dist/services/configPolicy.d.mts +19 -0
- package/dist/services/configPolicy.d.mts.map +1 -0
- package/dist/services/configPolicy.mjs +2 -0
- package/dist/services/configPolicy.mjs.map +1 -0
- package/dist/services/domains.cjs +2 -0
- package/dist/services/domains.cjs.map +1 -0
- package/dist/services/domains.d.cts +12 -0
- package/dist/services/domains.d.cts.map +1 -0
- package/dist/services/domains.d.mts +12 -0
- package/dist/services/domains.d.mts.map +1 -0
- package/dist/services/domains.mjs +2 -0
- package/dist/services/domains.mjs.map +1 -0
- package/dist/types/config.cjs +2 -0
- package/dist/types/config.cjs.map +1 -0
- package/dist/types/config.d.cts +136 -0
- package/dist/types/config.d.cts.map +1 -0
- package/dist/types/config.d.mts +136 -0
- package/dist/types/config.d.mts.map +1 -0
- package/dist/types/config.mjs +2 -0
- package/dist/types/config.mjs.map +1 -0
- package/dist/types/domains.d.cts +89 -0
- package/dist/types/domains.d.cts.map +1 -0
- package/dist/types/domains.d.mts +89 -0
- package/dist/types/domains.d.mts.map +1 -0
- package/dist/types.cjs +1 -2
- package/dist/types.d.cts +2 -132
- package/dist/types.d.mts +2 -132
- package/dist/types.mjs +1 -2
- package/llms.txt +9 -0
- package/package.json +18 -6
- package/skills/doom-pi-config/SKILL.md +6 -3
- package/skills/doom-pi-config/references/config-contract.md +123 -8
- package/dist/atomicJson.cjs.map +0 -1
- package/dist/atomicJson.d.cts.map +0 -1
- package/dist/atomicJson.d.mts.map +0 -1
- package/dist/atomicJson.mjs.map +0 -1
- package/dist/config/piConfig.cjs.map +0 -1
- package/dist/config/piConfig.d.cts.map +0 -1
- package/dist/config/piConfig.d.mts.map +0 -1
- package/dist/config/piConfig.mjs.map +0 -1
- package/dist/config/schema.d.cts.map +0 -1
- package/dist/config/schema.d.mts.map +0 -1
- package/dist/config.cjs.map +0 -1
- package/dist/config.d.cts.map +0 -1
- package/dist/config.d.mts.map +0 -1
- package/dist/config.mjs.map +0 -1
- package/dist/configWriter.cjs.map +0 -1
- package/dist/configWriter.d.cts.map +0 -1
- package/dist/configWriter.d.mts.map +0 -1
- package/dist/configWriter.mjs.map +0 -1
- package/dist/container.cjs.map +0 -1
- package/dist/container.d.cts.map +0 -1
- package/dist/container.d.mts.map +0 -1
- package/dist/container.mjs.map +0 -1
- package/dist/domains.cjs.map +0 -1
- package/dist/domains.d.cts.map +0 -1
- package/dist/domains.d.mts.map +0 -1
- package/dist/domains.mjs.map +0 -1
- package/dist/extensions/doom.cjs.map +0 -1
- package/dist/extensions/doom.d.cts.map +0 -1
- package/dist/extensions/doom.d.mts.map +0 -1
- package/dist/extensions/doom.mjs.map +0 -1
- package/dist/extensions/pi.cjs.map +0 -1
- package/dist/extensions/pi.d.cts.map +0 -1
- package/dist/extensions/pi.d.mts.map +0 -1
- package/dist/extensions/pi.mjs.map +0 -1
- package/dist/harnessState.cjs.map +0 -1
- package/dist/harnessState.d.cts.map +0 -1
- package/dist/harnessState.d.mts.map +0 -1
- package/dist/harnessState.mjs.map +0 -1
- package/dist/harnessStore.cjs.map +0 -1
- package/dist/harnessStore.d.cts.map +0 -1
- package/dist/harnessStore.d.mts.map +0 -1
- package/dist/harnessStore.mjs.map +0 -1
- package/dist/init.cjs.map +0 -1
- package/dist/init.d.cts.map +0 -1
- package/dist/init.d.mts.map +0 -1
- package/dist/init.mjs.map +0 -1
- package/dist/layeredConfig.cjs.map +0 -1
- package/dist/layeredConfig.d.cts.map +0 -1
- package/dist/layeredConfig.d.mts.map +0 -1
- package/dist/layeredConfig.mjs.map +0 -1
- package/dist/majorModes.cjs.map +0 -1
- package/dist/majorModes.d.cts.map +0 -1
- package/dist/majorModes.d.mts.map +0 -1
- package/dist/majorModes.mjs.map +0 -1
- package/dist/piContext.cjs.map +0 -1
- package/dist/piContext.d.cts.map +0 -1
- package/dist/piContext.d.mts.map +0 -1
- package/dist/piContext.mjs.map +0 -1
- package/dist/profiles.cjs.map +0 -1
- package/dist/profiles.d.cts.map +0 -1
- package/dist/profiles.d.mts.map +0 -1
- package/dist/profiles.mjs.map +0 -1
- package/dist/types.cjs.map +0 -1
- package/dist/types.d.cts.map +0 -1
- package/dist/types.d.mts.map +0 -1
- package/dist/types.mjs.map +0 -1
package/dist/config.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.cjs","names":["parseYaml","fs","os","path"],"sources":["../src/config.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport type {\n DoomConfig,\n DoomSelectionConfig,\n EditorConfig,\n PlanningAgentConfig,\n PlanningModeConfig,\n PlanningThinkingLevel,\n ProjectTrust,\n ResolvedVoiceConfig,\n VoiceAdapterConfig,\n VoiceAutoCaptureConfig,\n VoiceConfig,\n VoiceEngine,\n VoiceModelConfig,\n VoiceTtsConfig,\n VoiceTtsEngine,\n} from './types.ts';\n\nconst DOOM_DIR = '.doom';\nconst CONFIG_FILE = 'config.yaml';\nconst GLOBAL_CONFIG_DIR = '.pi';\nconst PLANS_DIR = 'plans';\nconst DEFAULT_PROJECT_TRUST: ProjectTrust = 'ask';\nconst DEFAULT_VOICE_ENGINE: VoiceEngine = 'auto';\nconst DEFAULT_VOICE_LANGUAGE = 'auto';\nconst DEFAULT_RECORDER_DEVICE = 'none:default';\nconst ROOT_KEYS = ['modes', 'projectTrust', 'editor', 'voice', 'selection'] as const;\nconst SELECTION_KEYS = ['majorMode', 'domains', 'profile'] as const;\nconst MODE_KEYS = ['planning'] as const;\nconst PLANNING_KEYS = ['main', 'subagents', 'plansDirectory'] as const;\nconst AGENT_KEYS = ['model', 'thinking'] as const;\nconst EDITOR_KEYS = ['command'] as const;\nconst VOICE_KEYS = ['engine', 'language', 'recorder', 'adapters', 'autoCapture'] as const;\nconst RECORDER_KEYS = ['binary', 'device'] as const;\nconst WHISPER_CPP_ENGINE = 'whisper-cpp' as const;\nconst OPENAI_WHISPER_ENGINE = 'openai-whisper' as const;\nconst MLX_WHISPER_ENGINE = 'mlx-whisper' as const;\nconst MACOS_SAY_TTS_ENGINE = 'macos-say' as const;\nconst ADAPTERS_KEYS = [WHISPER_CPP_ENGINE, OPENAI_WHISPER_ENGINE, MLX_WHISPER_ENGINE] as const;\nconst ADAPTER_KEYS = ['binary', 'model'] as const;\nconst MODEL_KEYS = ['path', 'id'] as const;\nconst AUTO_CAPTURE_KEYS = ['model', 'startPhrases', 'stopPhrases', 'tts'] as const;\nconst TTS_KEYS = ['engine', 'voice', 'rate'] as const;\nconst MAX_CONTROL_PHRASES = 16;\nconst MAX_CONTROL_PHRASE_LENGTH = 64;\nconst MAX_TTS_VOICE_LENGTH = 64;\nconst MIN_TTS_RATE = 80;\nconst MAX_TTS_RATE = 500;\nconst MODEL_REFERENCE_PATTERN = /^[^/\\s]+\\/[^/\\s]+(?:\\/[^/\\s]+)*$/u;\nconst PHRASE_PUNCTUATION_PATTERN = /[\\p{P}\\p{S}]+/gu;\n/**\n * The accepted values, in the order a picker should offer them.\n *\n * Exported because the config UI has to offer exactly what the parser accepts,\n * and a second hand-written list in the contributing package would drift. The\n * validating Sets below are built from these for the same reason.\n */\nexport const DOOM_PLANNING_THINKING_LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const;\nexport const DOOM_VOICE_ENGINES = [\n DEFAULT_VOICE_ENGINE,\n WHISPER_CPP_ENGINE,\n OPENAI_WHISPER_ENGINE,\n MLX_WHISPER_ENGINE,\n] as const;\nexport const DOOM_VOICE_TTS_ENGINES = [MACOS_SAY_TTS_ENGINE] as const;\n\nconst THINKING_VALUES = new Set<PlanningThinkingLevel>(DOOM_PLANNING_THINKING_LEVELS);\nconst TRUST_VALUES = new Set<ProjectTrust>(['ask', 'always', 'never']);\nconst ENGINE_VALUES = new Set<VoiceEngine>(DOOM_VOICE_ENGINES);\nconst TTS_ENGINE_VALUES = new Set<VoiceTtsEngine>(DOOM_VOICE_TTS_ENGINES);\ntype ConfigObject = Record<string, unknown>;\n\nfunction isObject(value: unknown): value is ConfigObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\nfunction assertKeys(value: ConfigObject, allowed: readonly string[], location: string, filePath: string): void {\n const unknown = Object.keys(value).filter((key) => !allowed.includes(key));\n if (unknown.length > 0)\n throw new Error(`Doom config at ${filePath} has unsupported ${location} field(s): ${unknown.join(', ')}`);\n}\nfunction optionalString(value: unknown, location: string, filePath: string): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'string' || value.trim().length === 0)\n throw new Error(`Doom config at ${filePath} requires ${location} to be a non-empty string`);\n return value.trim();\n}\nfunction hasControlCharacters(value: string): boolean {\n for (const character of value) {\n const codePoint = character.codePointAt(0);\n if (codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f)) return true;\n }\n return false;\n}\nfunction boundedOptionalString(\n value: unknown,\n location: string,\n filePath: string,\n maximumLength: number,\n): string | undefined {\n const text = optionalString(value, location, filePath);\n if (!text) return undefined;\n if (text.length > maximumLength)\n throw new Error(`Doom config at ${filePath} requires ${location} to be at most ${maximumLength} characters`);\n if (hasControlCharacters(text))\n throw new Error(`Doom config at ${filePath} does not allow control characters in ${location}`);\n return text;\n}\nfunction parseModelReference(value: unknown, filePath: string): string {\n const location = 'voice.autoCapture.model';\n const model = optionalString(value, location, filePath);\n if (!model || !MODEL_REFERENCE_PATTERN.test(model))\n throw new Error(`Doom config at ${filePath} requires ${location} in provider/model-id form`);\n return model;\n}\nfunction normalizeControlPhrase(value: string): string {\n return value\n .normalize('NFKC')\n .toLocaleLowerCase('en-US')\n .replace(PHRASE_PUNCTUATION_PATTERN, ' ')\n .replace(/\\s+/gu, ' ')\n .trim();\n}\nfunction parseControlPhrases(value: unknown, location: string, filePath: string): string[] | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value) || value.some((phrase) => typeof phrase !== 'string'))\n throw new Error(`Doom config at ${filePath} requires ${location} to be an array of strings`);\n if (value.length > MAX_CONTROL_PHRASES)\n throw new Error(\n `Doom config at ${filePath} requires ${location} to contain at most ${MAX_CONTROL_PHRASES} phrases`,\n );\n const phrases = value.map((phrase) => phrase.trim());\n const normalized = new Set<string>();\n for (const phrase of phrases) {\n if (phrase.length === 0)\n throw new Error(`Doom config at ${filePath} requires ${location} to contain non-empty strings`);\n if (phrase.length > MAX_CONTROL_PHRASE_LENGTH)\n throw new Error(\n `Doom config at ${filePath} requires ${location} phrases to be at most ${MAX_CONTROL_PHRASE_LENGTH} characters`,\n );\n if (hasControlCharacters(phrase))\n throw new Error(`Doom config at ${filePath} does not allow control characters in ${location}`);\n const key = normalizeControlPhrase(phrase);\n if (!key) throw new Error(`Doom config at ${filePath} requires ${location} to contain words`);\n if (normalized.has(key))\n throw new Error(`Doom config at ${filePath} does not allow normalized duplicates in ${location}`);\n normalized.add(key);\n }\n return phrases;\n}\nfunction parseTtsConfig(value: unknown, filePath: string): VoiceTtsConfig {\n const location = 'voice.autoCapture.tts';\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, TTS_KEYS, location, filePath);\n if (typeof value.engine !== 'string' || !TTS_ENGINE_VALUES.has(value.engine as VoiceTtsEngine))\n throw new Error(\n `Doom config at ${filePath} requires ${location}.engine to be one of: ${[...TTS_ENGINE_VALUES].join(', ')}`,\n );\n const voice = boundedOptionalString(value.voice, `${location}.voice`, filePath, MAX_TTS_VOICE_LENGTH);\n const rate = value.rate;\n if (\n rate !== undefined &&\n (typeof rate !== 'number' || !Number.isInteger(rate) || rate < MIN_TTS_RATE || rate > MAX_TTS_RATE)\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.rate to be an integer from ${MIN_TTS_RATE} to ${MAX_TTS_RATE}`,\n );\n }\n return {\n engine: value.engine as VoiceTtsEngine,\n ...(voice ? { voice } : {}),\n ...(rate === undefined ? {} : { rate }),\n };\n}\nfunction parseAutoCapture(value: unknown, filePath: string): VoiceAutoCaptureConfig | undefined {\n if (value === undefined) return undefined;\n const location = 'voice.autoCapture';\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AUTO_CAPTURE_KEYS, location, filePath);\n const startPhrases = parseControlPhrases(value.startPhrases, `${location}.startPhrases`, filePath);\n const stopPhrases = parseControlPhrases(value.stopPhrases, `${location}.stopPhrases`, filePath);\n const startKeys = new Set((startPhrases ?? []).map(normalizeControlPhrase));\n if ((stopPhrases ?? []).some((phrase) => startKeys.has(normalizeControlPhrase(phrase))))\n throw new Error(`Doom config at ${filePath} does not allow a phrase in both startPhrases and stopPhrases`);\n return {\n model: parseModelReference(value.model, filePath),\n ...(startPhrases ? { startPhrases } : {}),\n ...(stopPhrases ? { stopPhrases } : {}),\n tts: parseTtsConfig(value.tts, filePath),\n };\n}\nfunction parseAgent(value: unknown, location: string, filePath: string): PlanningAgentConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AGENT_KEYS, location, filePath);\n const thinking = value.thinking;\n if (\n thinking !== undefined &&\n (typeof thinking !== 'string' || !THINKING_VALUES.has(thinking as PlanningThinkingLevel))\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.thinking to be one of: ${[...THINKING_VALUES].join(', ')}`,\n );\n }\n const model = optionalString(value.model, `${location}.model`, filePath);\n return { ...(model ? { model } : {}), ...(thinking ? { thinking: thinking as PlanningThinkingLevel } : {}) };\n}\nexport function parsePlanningModeConfig(\n value: unknown,\n filePath: string,\n location = 'modes.planning',\n): PlanningModeConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, PLANNING_KEYS, location, filePath);\n return {\n main: parseAgent(value.main, `${location}.main`, filePath),\n subagents: parseAgent(value.subagents, `${location}.subagents`, filePath),\n plansDirectory: optionalString(value.plansDirectory, `${location}.plansDirectory`, filePath),\n };\n}\nfunction parseModel(value: unknown, location: string, filePath: string, allowId: boolean): VoiceModelConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, MODEL_KEYS, location, filePath);\n const modelPath = optionalString(value.path, `${location}.path`, filePath);\n const id = optionalString(value.id, `${location}.id`, filePath);\n if (Boolean(modelPath) === Boolean(id))\n throw new Error(`Doom config at ${filePath} requires exactly one of ${location}.path or ${location}.id`);\n if (id && !allowId) throw new Error(`Doom config at ${filePath} does not support ${location}.id for whisper-cpp`);\n return { ...(modelPath ? { path: modelPath } : {}), ...(id ? { id } : {}) };\n}\nfunction parseAdapter(\n value: unknown,\n location: string,\n filePath: string,\n allowId: boolean,\n): VoiceAdapterConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, ADAPTER_KEYS, location, filePath);\n const binary = optionalString(value.binary, `${location}.binary`, filePath);\n return { ...(binary ? { binary } : {}), model: parseModel(value.model, `${location}.model`, filePath, allowId) };\n}\nfunction parseVoice(value: unknown, filePath: string): VoiceConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires voice to be an object`);\n assertKeys(value, VOICE_KEYS, 'voice', filePath);\n const result: VoiceConfig = {};\n if (value.engine !== undefined) {\n if (typeof value.engine !== 'string' || !ENGINE_VALUES.has(value.engine as VoiceEngine))\n throw new Error(\n `Doom config at ${filePath} requires voice.engine to be one of: ${[...ENGINE_VALUES].join(', ')}`,\n );\n result.engine = value.engine as VoiceEngine;\n }\n const language = optionalString(value.language, 'voice.language', filePath);\n if (language) result.language = language;\n if (value.recorder !== undefined) {\n if (!isObject(value.recorder))\n throw new Error(`Doom config at ${filePath} requires voice.recorder to be an object`);\n assertKeys(value.recorder, RECORDER_KEYS, 'voice.recorder', filePath);\n result.recorder = {};\n const binary = optionalString(value.recorder.binary, 'voice.recorder.binary', filePath);\n const device = optionalString(value.recorder.device, 'voice.recorder.device', filePath);\n if (binary) result.recorder.binary = binary;\n if (device) result.recorder.device = device;\n }\n if (value.adapters !== undefined) {\n if (!isObject(value.adapters))\n throw new Error(`Doom config at ${filePath} requires voice.adapters to be an object`);\n assertKeys(value.adapters, ADAPTERS_KEYS, 'voice.adapters', filePath);\n result.adapters = {\n [WHISPER_CPP_ENGINE]: parseAdapter(\n value.adapters[WHISPER_CPP_ENGINE],\n `voice.adapters.${WHISPER_CPP_ENGINE}`,\n filePath,\n false,\n ),\n [OPENAI_WHISPER_ENGINE]: parseAdapter(\n value.adapters[OPENAI_WHISPER_ENGINE],\n `voice.adapters.${OPENAI_WHISPER_ENGINE}`,\n filePath,\n true,\n ),\n [MLX_WHISPER_ENGINE]: parseAdapter(\n value.adapters[MLX_WHISPER_ENGINE],\n `voice.adapters.${MLX_WHISPER_ENGINE}`,\n filePath,\n true,\n ),\n };\n }\n const autoCapture = parseAutoCapture(value.autoCapture, filePath);\n if (autoCapture) result.autoCapture = autoCapture;\n return result;\n}\nfunction parseSelection(value: unknown, filePath: string): DoomSelectionConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires selection to be an object`);\n assertKeys(value, SELECTION_KEYS, 'selection', filePath);\n const domains = value.domains;\n if (domains !== undefined && (!Array.isArray(domains) || domains.some((name) => typeof name !== 'string'))) {\n throw new Error(`Doom config at ${filePath} requires selection.domains to be an array of strings`);\n }\n return {\n majorMode: optionalString(value.majorMode, 'selection.majorMode', filePath),\n // An empty list is meaningful: it selects no content domains at all.\n domains: domains as string[] | undefined,\n profile: optionalString(value.profile, 'selection.profile', filePath),\n };\n}\nfunction parseEditor(value: unknown, filePath: string): EditorConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires editor to be an object`);\n assertKeys(value, EDITOR_KEYS, 'editor', filePath);\n return { command: optionalString(value.command, 'editor.command', filePath) };\n}\nexport function parseDoomConfig(content: string, filePath: string): DoomConfig {\n let parsed: unknown;\n try {\n parsed = parseYaml(content) ?? {};\n } catch (error) {\n throw new Error(`Could not parse Doom config at ${filePath}`, { cause: error });\n }\n if (!isObject(parsed)) throw new Error(`Doom config at ${filePath} must be a YAML object`);\n assertKeys(parsed, ROOT_KEYS, 'root', filePath);\n let modes: DoomConfig['modes'];\n if (parsed.modes !== undefined) {\n if (!isObject(parsed.modes)) throw new Error(`Doom config at ${filePath} requires modes to be an object`);\n assertKeys(parsed.modes, MODE_KEYS, 'modes', filePath);\n modes =\n parsed.modes.planning === undefined ? {} : { planning: parsePlanningModeConfig(parsed.modes.planning, filePath) };\n }\n const trust = parsed.projectTrust === undefined ? DEFAULT_PROJECT_TRUST : parsed.projectTrust;\n if (typeof trust !== 'string' || !TRUST_VALUES.has(trust as ProjectTrust))\n throw new Error(`Doom config at ${filePath} projectTrust must be ask, always, or never`);\n return {\n modes,\n projectTrust: trust as ProjectTrust,\n editor: parseEditor(parsed.editor, filePath),\n voice: parseVoice(parsed.voice, filePath),\n selection: parseSelection(parsed.selection, filePath),\n };\n}\nfunction readConfig(filePath: string): DoomConfig {\n return fs.existsSync(filePath)\n ? parseDoomConfig(fs.readFileSync(filePath, 'utf8'), filePath)\n : { projectTrust: DEFAULT_PROJECT_TRUST };\n}\nfunction mergeAgent(\n globalValue?: PlanningAgentConfig,\n repositoryValue?: PlanningAgentConfig,\n): PlanningAgentConfig | undefined {\n return globalValue || repositoryValue ? { ...globalValue, ...repositoryValue } : undefined;\n}\nexport function mergeDoomConfigs(globalConfig: DoomConfig, repositoryConfig: DoomConfig): DoomConfig {\n if (repositoryConfig.voice?.autoCapture)\n throw new Error('Repository Doom config voice.autoCapture is global-only; configure it in ~/.pi/.doom/config.yaml');\n const globalPlanning = globalConfig.modes?.planning;\n const repositoryPlanning = repositoryConfig.modes?.planning;\n const planning =\n globalPlanning || repositoryPlanning\n ? {\n main: mergeAgent(globalPlanning?.main, repositoryPlanning?.main),\n subagents: mergeAgent(globalPlanning?.subagents, repositoryPlanning?.subagents),\n plansDirectory: repositoryPlanning?.plansDirectory ?? globalPlanning?.plansDirectory,\n }\n : undefined;\n const voice = mergeVoice(globalConfig.voice, repositoryConfig.voice);\n return {\n modes: planning ? { planning } : undefined,\n projectTrust: repositoryConfig.projectTrust,\n editor: globalConfig.editor,\n voice,\n // Per key, so a repository can pin one axis and inherit the rest.\n selection:\n globalConfig.selection || repositoryConfig.selection\n ? { ...globalConfig.selection, ...repositoryConfig.selection }\n : undefined,\n };\n}\nfunction mergeAdapter(\n globalValue?: VoiceAdapterConfig,\n repositoryValue?: VoiceAdapterConfig,\n): VoiceAdapterConfig | undefined {\n if (!globalValue && !repositoryValue) return undefined;\n return { ...globalValue, ...repositoryValue, model: repositoryValue?.model ?? globalValue!.model };\n}\nfunction mergeVoice(globalValue?: VoiceConfig, repositoryValue?: VoiceConfig): VoiceConfig | undefined {\n if (!globalValue && !repositoryValue) return undefined;\n return {\n ...globalValue,\n ...repositoryValue,\n recorder:\n globalValue?.recorder || repositoryValue?.recorder\n ? { ...globalValue?.recorder, ...repositoryValue?.recorder }\n : undefined,\n adapters:\n globalValue?.adapters || repositoryValue?.adapters\n ? {\n [WHISPER_CPP_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[WHISPER_CPP_ENGINE],\n repositoryValue?.adapters?.[WHISPER_CPP_ENGINE],\n ),\n [OPENAI_WHISPER_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[OPENAI_WHISPER_ENGINE],\n repositoryValue?.adapters?.[OPENAI_WHISPER_ENGINE],\n ),\n [MLX_WHISPER_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[MLX_WHISPER_ENGINE],\n repositoryValue?.adapters?.[MLX_WHISPER_ENGINE],\n ),\n }\n : undefined,\n ...(globalValue?.autoCapture ? { autoCapture: globalValue.autoCapture } : {}),\n };\n}\nexport function resolveVoiceConfig(config: VoiceConfig): ResolvedVoiceConfig {\n return {\n engine: config.engine ?? DEFAULT_VOICE_ENGINE,\n language: config.language ?? DEFAULT_VOICE_LANGUAGE,\n recorder: { ...config.recorder, device: config.recorder?.device ?? DEFAULT_RECORDER_DEVICE },\n adapters: config.adapters ?? {},\n ...(config.autoCapture\n ? {\n autoCapture: {\n ...config.autoCapture,\n startPhrases: [...(config.autoCapture.startPhrases ?? [])],\n stopPhrases: [...(config.autoCapture.stopPhrases ?? [])],\n tts: { ...config.autoCapture.tts },\n },\n }\n : {}),\n };\n}\nexport function globalDoomConfigDirectory(homeDirectory = os.homedir()): string {\n return path.join(homeDirectory, GLOBAL_CONFIG_DIR, DOOM_DIR);\n}\nexport function globalDoomConfigPath(homeDirectory = os.homedir()): string {\n return path.join(globalDoomConfigDirectory(homeDirectory), CONFIG_FILE);\n}\nexport function resolvePlanningPlansDirectory(\n configured: string | undefined,\n repoRoot: string,\n homeDirectory = os.homedir(),\n): string {\n if (!configured) return path.join(homeDirectory, GLOBAL_CONFIG_DIR, PLANS_DIR);\n if (configured === '~') return homeDirectory;\n if (configured.startsWith('~/')) return path.resolve(homeDirectory, configured.slice(2));\n if (configured.startsWith('~')) {\n throw new Error(\"Doom planning plansDirectory supports only '~' or '~/' home aliases\");\n }\n return path.isAbsolute(configured) ? path.normalize(configured) : path.resolve(repoRoot, configured);\n}\nexport function repositoryDoomConfigPath(repoRoot: string): string {\n return path.join(repoRoot, DOOM_DIR, CONFIG_FILE);\n}\nexport function loadDoomConfig(repoRoot: string, homeDirectory = os.homedir()): DoomConfig {\n return mergeDoomConfigs(\n readConfig(globalDoomConfigPath(homeDirectory)),\n readConfig(repositoryDoomConfigPath(repoRoot)),\n );\n}\n"],"mappings":"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"}
|
package/dist/config.d.cts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.cts","names":[],"sources":["../src/config.ts"],"mappings":";;;;;;;;;cA6Da;cACA;cAMA;iBA8IG,wBACd,gBACA,kBACA,oBACC;iBAyGa,gBAAgB,iBAAiB,mBAAmB;iBAsCpD,iBAAiB,cAAc,YAAY,kBAAkB,aAAa;iBA8D1E,mBAAmB,QAAQ,cAAc;iBAkBzC,0BAA0B;iBAG1B,qBAAqB;iBAGrB,8BACd,gCACA,kBACA;iBAUc,yBAAyB;iBAGzB,eAAe,kBAAkB,yBAA+B"}
|
package/dist/config.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.mts","names":[],"sources":["../src/config.ts"],"mappings":";;;;;;;;;cA6Da;cACA;cAMA;iBA8IG,wBACd,gBACA,kBACA,oBACC;iBAyGa,gBAAgB,iBAAiB,mBAAmB;iBAsCpD,iBAAiB,cAAc,YAAY,kBAAkB,aAAa;iBA8D1E,mBAAmB,QAAQ,cAAc;iBAkBzC,0BAA0B;iBAG1B,qBAAqB;iBAGrB,8BACd,gCACA,kBACA;iBAUc,yBAAyB;iBAGzB,eAAe,kBAAkB,yBAA+B"}
|
package/dist/config.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","names":["parseYaml"],"sources":["../src/config.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport type {\n DoomConfig,\n DoomSelectionConfig,\n EditorConfig,\n PlanningAgentConfig,\n PlanningModeConfig,\n PlanningThinkingLevel,\n ProjectTrust,\n ResolvedVoiceConfig,\n VoiceAdapterConfig,\n VoiceAutoCaptureConfig,\n VoiceConfig,\n VoiceEngine,\n VoiceModelConfig,\n VoiceTtsConfig,\n VoiceTtsEngine,\n} from './types.ts';\n\nconst DOOM_DIR = '.doom';\nconst CONFIG_FILE = 'config.yaml';\nconst GLOBAL_CONFIG_DIR = '.pi';\nconst PLANS_DIR = 'plans';\nconst DEFAULT_PROJECT_TRUST: ProjectTrust = 'ask';\nconst DEFAULT_VOICE_ENGINE: VoiceEngine = 'auto';\nconst DEFAULT_VOICE_LANGUAGE = 'auto';\nconst DEFAULT_RECORDER_DEVICE = 'none:default';\nconst ROOT_KEYS = ['modes', 'projectTrust', 'editor', 'voice', 'selection'] as const;\nconst SELECTION_KEYS = ['majorMode', 'domains', 'profile'] as const;\nconst MODE_KEYS = ['planning'] as const;\nconst PLANNING_KEYS = ['main', 'subagents', 'plansDirectory'] as const;\nconst AGENT_KEYS = ['model', 'thinking'] as const;\nconst EDITOR_KEYS = ['command'] as const;\nconst VOICE_KEYS = ['engine', 'language', 'recorder', 'adapters', 'autoCapture'] as const;\nconst RECORDER_KEYS = ['binary', 'device'] as const;\nconst WHISPER_CPP_ENGINE = 'whisper-cpp' as const;\nconst OPENAI_WHISPER_ENGINE = 'openai-whisper' as const;\nconst MLX_WHISPER_ENGINE = 'mlx-whisper' as const;\nconst MACOS_SAY_TTS_ENGINE = 'macos-say' as const;\nconst ADAPTERS_KEYS = [WHISPER_CPP_ENGINE, OPENAI_WHISPER_ENGINE, MLX_WHISPER_ENGINE] as const;\nconst ADAPTER_KEYS = ['binary', 'model'] as const;\nconst MODEL_KEYS = ['path', 'id'] as const;\nconst AUTO_CAPTURE_KEYS = ['model', 'startPhrases', 'stopPhrases', 'tts'] as const;\nconst TTS_KEYS = ['engine', 'voice', 'rate'] as const;\nconst MAX_CONTROL_PHRASES = 16;\nconst MAX_CONTROL_PHRASE_LENGTH = 64;\nconst MAX_TTS_VOICE_LENGTH = 64;\nconst MIN_TTS_RATE = 80;\nconst MAX_TTS_RATE = 500;\nconst MODEL_REFERENCE_PATTERN = /^[^/\\s]+\\/[^/\\s]+(?:\\/[^/\\s]+)*$/u;\nconst PHRASE_PUNCTUATION_PATTERN = /[\\p{P}\\p{S}]+/gu;\n/**\n * The accepted values, in the order a picker should offer them.\n *\n * Exported because the config UI has to offer exactly what the parser accepts,\n * and a second hand-written list in the contributing package would drift. The\n * validating Sets below are built from these for the same reason.\n */\nexport const DOOM_PLANNING_THINKING_LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const;\nexport const DOOM_VOICE_ENGINES = [\n DEFAULT_VOICE_ENGINE,\n WHISPER_CPP_ENGINE,\n OPENAI_WHISPER_ENGINE,\n MLX_WHISPER_ENGINE,\n] as const;\nexport const DOOM_VOICE_TTS_ENGINES = [MACOS_SAY_TTS_ENGINE] as const;\n\nconst THINKING_VALUES = new Set<PlanningThinkingLevel>(DOOM_PLANNING_THINKING_LEVELS);\nconst TRUST_VALUES = new Set<ProjectTrust>(['ask', 'always', 'never']);\nconst ENGINE_VALUES = new Set<VoiceEngine>(DOOM_VOICE_ENGINES);\nconst TTS_ENGINE_VALUES = new Set<VoiceTtsEngine>(DOOM_VOICE_TTS_ENGINES);\ntype ConfigObject = Record<string, unknown>;\n\nfunction isObject(value: unknown): value is ConfigObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\nfunction assertKeys(value: ConfigObject, allowed: readonly string[], location: string, filePath: string): void {\n const unknown = Object.keys(value).filter((key) => !allowed.includes(key));\n if (unknown.length > 0)\n throw new Error(`Doom config at ${filePath} has unsupported ${location} field(s): ${unknown.join(', ')}`);\n}\nfunction optionalString(value: unknown, location: string, filePath: string): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== 'string' || value.trim().length === 0)\n throw new Error(`Doom config at ${filePath} requires ${location} to be a non-empty string`);\n return value.trim();\n}\nfunction hasControlCharacters(value: string): boolean {\n for (const character of value) {\n const codePoint = character.codePointAt(0);\n if (codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f)) return true;\n }\n return false;\n}\nfunction boundedOptionalString(\n value: unknown,\n location: string,\n filePath: string,\n maximumLength: number,\n): string | undefined {\n const text = optionalString(value, location, filePath);\n if (!text) return undefined;\n if (text.length > maximumLength)\n throw new Error(`Doom config at ${filePath} requires ${location} to be at most ${maximumLength} characters`);\n if (hasControlCharacters(text))\n throw new Error(`Doom config at ${filePath} does not allow control characters in ${location}`);\n return text;\n}\nfunction parseModelReference(value: unknown, filePath: string): string {\n const location = 'voice.autoCapture.model';\n const model = optionalString(value, location, filePath);\n if (!model || !MODEL_REFERENCE_PATTERN.test(model))\n throw new Error(`Doom config at ${filePath} requires ${location} in provider/model-id form`);\n return model;\n}\nfunction normalizeControlPhrase(value: string): string {\n return value\n .normalize('NFKC')\n .toLocaleLowerCase('en-US')\n .replace(PHRASE_PUNCTUATION_PATTERN, ' ')\n .replace(/\\s+/gu, ' ')\n .trim();\n}\nfunction parseControlPhrases(value: unknown, location: string, filePath: string): string[] | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value) || value.some((phrase) => typeof phrase !== 'string'))\n throw new Error(`Doom config at ${filePath} requires ${location} to be an array of strings`);\n if (value.length > MAX_CONTROL_PHRASES)\n throw new Error(\n `Doom config at ${filePath} requires ${location} to contain at most ${MAX_CONTROL_PHRASES} phrases`,\n );\n const phrases = value.map((phrase) => phrase.trim());\n const normalized = new Set<string>();\n for (const phrase of phrases) {\n if (phrase.length === 0)\n throw new Error(`Doom config at ${filePath} requires ${location} to contain non-empty strings`);\n if (phrase.length > MAX_CONTROL_PHRASE_LENGTH)\n throw new Error(\n `Doom config at ${filePath} requires ${location} phrases to be at most ${MAX_CONTROL_PHRASE_LENGTH} characters`,\n );\n if (hasControlCharacters(phrase))\n throw new Error(`Doom config at ${filePath} does not allow control characters in ${location}`);\n const key = normalizeControlPhrase(phrase);\n if (!key) throw new Error(`Doom config at ${filePath} requires ${location} to contain words`);\n if (normalized.has(key))\n throw new Error(`Doom config at ${filePath} does not allow normalized duplicates in ${location}`);\n normalized.add(key);\n }\n return phrases;\n}\nfunction parseTtsConfig(value: unknown, filePath: string): VoiceTtsConfig {\n const location = 'voice.autoCapture.tts';\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, TTS_KEYS, location, filePath);\n if (typeof value.engine !== 'string' || !TTS_ENGINE_VALUES.has(value.engine as VoiceTtsEngine))\n throw new Error(\n `Doom config at ${filePath} requires ${location}.engine to be one of: ${[...TTS_ENGINE_VALUES].join(', ')}`,\n );\n const voice = boundedOptionalString(value.voice, `${location}.voice`, filePath, MAX_TTS_VOICE_LENGTH);\n const rate = value.rate;\n if (\n rate !== undefined &&\n (typeof rate !== 'number' || !Number.isInteger(rate) || rate < MIN_TTS_RATE || rate > MAX_TTS_RATE)\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.rate to be an integer from ${MIN_TTS_RATE} to ${MAX_TTS_RATE}`,\n );\n }\n return {\n engine: value.engine as VoiceTtsEngine,\n ...(voice ? { voice } : {}),\n ...(rate === undefined ? {} : { rate }),\n };\n}\nfunction parseAutoCapture(value: unknown, filePath: string): VoiceAutoCaptureConfig | undefined {\n if (value === undefined) return undefined;\n const location = 'voice.autoCapture';\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AUTO_CAPTURE_KEYS, location, filePath);\n const startPhrases = parseControlPhrases(value.startPhrases, `${location}.startPhrases`, filePath);\n const stopPhrases = parseControlPhrases(value.stopPhrases, `${location}.stopPhrases`, filePath);\n const startKeys = new Set((startPhrases ?? []).map(normalizeControlPhrase));\n if ((stopPhrases ?? []).some((phrase) => startKeys.has(normalizeControlPhrase(phrase))))\n throw new Error(`Doom config at ${filePath} does not allow a phrase in both startPhrases and stopPhrases`);\n return {\n model: parseModelReference(value.model, filePath),\n ...(startPhrases ? { startPhrases } : {}),\n ...(stopPhrases ? { stopPhrases } : {}),\n tts: parseTtsConfig(value.tts, filePath),\n };\n}\nfunction parseAgent(value: unknown, location: string, filePath: string): PlanningAgentConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, AGENT_KEYS, location, filePath);\n const thinking = value.thinking;\n if (\n thinking !== undefined &&\n (typeof thinking !== 'string' || !THINKING_VALUES.has(thinking as PlanningThinkingLevel))\n ) {\n throw new Error(\n `Doom config at ${filePath} requires ${location}.thinking to be one of: ${[...THINKING_VALUES].join(', ')}`,\n );\n }\n const model = optionalString(value.model, `${location}.model`, filePath);\n return { ...(model ? { model } : {}), ...(thinking ? { thinking: thinking as PlanningThinkingLevel } : {}) };\n}\nexport function parsePlanningModeConfig(\n value: unknown,\n filePath: string,\n location = 'modes.planning',\n): PlanningModeConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, PLANNING_KEYS, location, filePath);\n return {\n main: parseAgent(value.main, `${location}.main`, filePath),\n subagents: parseAgent(value.subagents, `${location}.subagents`, filePath),\n plansDirectory: optionalString(value.plansDirectory, `${location}.plansDirectory`, filePath),\n };\n}\nfunction parseModel(value: unknown, location: string, filePath: string, allowId: boolean): VoiceModelConfig {\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, MODEL_KEYS, location, filePath);\n const modelPath = optionalString(value.path, `${location}.path`, filePath);\n const id = optionalString(value.id, `${location}.id`, filePath);\n if (Boolean(modelPath) === Boolean(id))\n throw new Error(`Doom config at ${filePath} requires exactly one of ${location}.path or ${location}.id`);\n if (id && !allowId) throw new Error(`Doom config at ${filePath} does not support ${location}.id for whisper-cpp`);\n return { ...(modelPath ? { path: modelPath } : {}), ...(id ? { id } : {}) };\n}\nfunction parseAdapter(\n value: unknown,\n location: string,\n filePath: string,\n allowId: boolean,\n): VoiceAdapterConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires ${location} to be an object`);\n assertKeys(value, ADAPTER_KEYS, location, filePath);\n const binary = optionalString(value.binary, `${location}.binary`, filePath);\n return { ...(binary ? { binary } : {}), model: parseModel(value.model, `${location}.model`, filePath, allowId) };\n}\nfunction parseVoice(value: unknown, filePath: string): VoiceConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires voice to be an object`);\n assertKeys(value, VOICE_KEYS, 'voice', filePath);\n const result: VoiceConfig = {};\n if (value.engine !== undefined) {\n if (typeof value.engine !== 'string' || !ENGINE_VALUES.has(value.engine as VoiceEngine))\n throw new Error(\n `Doom config at ${filePath} requires voice.engine to be one of: ${[...ENGINE_VALUES].join(', ')}`,\n );\n result.engine = value.engine as VoiceEngine;\n }\n const language = optionalString(value.language, 'voice.language', filePath);\n if (language) result.language = language;\n if (value.recorder !== undefined) {\n if (!isObject(value.recorder))\n throw new Error(`Doom config at ${filePath} requires voice.recorder to be an object`);\n assertKeys(value.recorder, RECORDER_KEYS, 'voice.recorder', filePath);\n result.recorder = {};\n const binary = optionalString(value.recorder.binary, 'voice.recorder.binary', filePath);\n const device = optionalString(value.recorder.device, 'voice.recorder.device', filePath);\n if (binary) result.recorder.binary = binary;\n if (device) result.recorder.device = device;\n }\n if (value.adapters !== undefined) {\n if (!isObject(value.adapters))\n throw new Error(`Doom config at ${filePath} requires voice.adapters to be an object`);\n assertKeys(value.adapters, ADAPTERS_KEYS, 'voice.adapters', filePath);\n result.adapters = {\n [WHISPER_CPP_ENGINE]: parseAdapter(\n value.adapters[WHISPER_CPP_ENGINE],\n `voice.adapters.${WHISPER_CPP_ENGINE}`,\n filePath,\n false,\n ),\n [OPENAI_WHISPER_ENGINE]: parseAdapter(\n value.adapters[OPENAI_WHISPER_ENGINE],\n `voice.adapters.${OPENAI_WHISPER_ENGINE}`,\n filePath,\n true,\n ),\n [MLX_WHISPER_ENGINE]: parseAdapter(\n value.adapters[MLX_WHISPER_ENGINE],\n `voice.adapters.${MLX_WHISPER_ENGINE}`,\n filePath,\n true,\n ),\n };\n }\n const autoCapture = parseAutoCapture(value.autoCapture, filePath);\n if (autoCapture) result.autoCapture = autoCapture;\n return result;\n}\nfunction parseSelection(value: unknown, filePath: string): DoomSelectionConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires selection to be an object`);\n assertKeys(value, SELECTION_KEYS, 'selection', filePath);\n const domains = value.domains;\n if (domains !== undefined && (!Array.isArray(domains) || domains.some((name) => typeof name !== 'string'))) {\n throw new Error(`Doom config at ${filePath} requires selection.domains to be an array of strings`);\n }\n return {\n majorMode: optionalString(value.majorMode, 'selection.majorMode', filePath),\n // An empty list is meaningful: it selects no content domains at all.\n domains: domains as string[] | undefined,\n profile: optionalString(value.profile, 'selection.profile', filePath),\n };\n}\nfunction parseEditor(value: unknown, filePath: string): EditorConfig | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`Doom config at ${filePath} requires editor to be an object`);\n assertKeys(value, EDITOR_KEYS, 'editor', filePath);\n return { command: optionalString(value.command, 'editor.command', filePath) };\n}\nexport function parseDoomConfig(content: string, filePath: string): DoomConfig {\n let parsed: unknown;\n try {\n parsed = parseYaml(content) ?? {};\n } catch (error) {\n throw new Error(`Could not parse Doom config at ${filePath}`, { cause: error });\n }\n if (!isObject(parsed)) throw new Error(`Doom config at ${filePath} must be a YAML object`);\n assertKeys(parsed, ROOT_KEYS, 'root', filePath);\n let modes: DoomConfig['modes'];\n if (parsed.modes !== undefined) {\n if (!isObject(parsed.modes)) throw new Error(`Doom config at ${filePath} requires modes to be an object`);\n assertKeys(parsed.modes, MODE_KEYS, 'modes', filePath);\n modes =\n parsed.modes.planning === undefined ? {} : { planning: parsePlanningModeConfig(parsed.modes.planning, filePath) };\n }\n const trust = parsed.projectTrust === undefined ? DEFAULT_PROJECT_TRUST : parsed.projectTrust;\n if (typeof trust !== 'string' || !TRUST_VALUES.has(trust as ProjectTrust))\n throw new Error(`Doom config at ${filePath} projectTrust must be ask, always, or never`);\n return {\n modes,\n projectTrust: trust as ProjectTrust,\n editor: parseEditor(parsed.editor, filePath),\n voice: parseVoice(parsed.voice, filePath),\n selection: parseSelection(parsed.selection, filePath),\n };\n}\nfunction readConfig(filePath: string): DoomConfig {\n return fs.existsSync(filePath)\n ? parseDoomConfig(fs.readFileSync(filePath, 'utf8'), filePath)\n : { projectTrust: DEFAULT_PROJECT_TRUST };\n}\nfunction mergeAgent(\n globalValue?: PlanningAgentConfig,\n repositoryValue?: PlanningAgentConfig,\n): PlanningAgentConfig | undefined {\n return globalValue || repositoryValue ? { ...globalValue, ...repositoryValue } : undefined;\n}\nexport function mergeDoomConfigs(globalConfig: DoomConfig, repositoryConfig: DoomConfig): DoomConfig {\n if (repositoryConfig.voice?.autoCapture)\n throw new Error('Repository Doom config voice.autoCapture is global-only; configure it in ~/.pi/.doom/config.yaml');\n const globalPlanning = globalConfig.modes?.planning;\n const repositoryPlanning = repositoryConfig.modes?.planning;\n const planning =\n globalPlanning || repositoryPlanning\n ? {\n main: mergeAgent(globalPlanning?.main, repositoryPlanning?.main),\n subagents: mergeAgent(globalPlanning?.subagents, repositoryPlanning?.subagents),\n plansDirectory: repositoryPlanning?.plansDirectory ?? globalPlanning?.plansDirectory,\n }\n : undefined;\n const voice = mergeVoice(globalConfig.voice, repositoryConfig.voice);\n return {\n modes: planning ? { planning } : undefined,\n projectTrust: repositoryConfig.projectTrust,\n editor: globalConfig.editor,\n voice,\n // Per key, so a repository can pin one axis and inherit the rest.\n selection:\n globalConfig.selection || repositoryConfig.selection\n ? { ...globalConfig.selection, ...repositoryConfig.selection }\n : undefined,\n };\n}\nfunction mergeAdapter(\n globalValue?: VoiceAdapterConfig,\n repositoryValue?: VoiceAdapterConfig,\n): VoiceAdapterConfig | undefined {\n if (!globalValue && !repositoryValue) return undefined;\n return { ...globalValue, ...repositoryValue, model: repositoryValue?.model ?? globalValue!.model };\n}\nfunction mergeVoice(globalValue?: VoiceConfig, repositoryValue?: VoiceConfig): VoiceConfig | undefined {\n if (!globalValue && !repositoryValue) return undefined;\n return {\n ...globalValue,\n ...repositoryValue,\n recorder:\n globalValue?.recorder || repositoryValue?.recorder\n ? { ...globalValue?.recorder, ...repositoryValue?.recorder }\n : undefined,\n adapters:\n globalValue?.adapters || repositoryValue?.adapters\n ? {\n [WHISPER_CPP_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[WHISPER_CPP_ENGINE],\n repositoryValue?.adapters?.[WHISPER_CPP_ENGINE],\n ),\n [OPENAI_WHISPER_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[OPENAI_WHISPER_ENGINE],\n repositoryValue?.adapters?.[OPENAI_WHISPER_ENGINE],\n ),\n [MLX_WHISPER_ENGINE]: mergeAdapter(\n globalValue?.adapters?.[MLX_WHISPER_ENGINE],\n repositoryValue?.adapters?.[MLX_WHISPER_ENGINE],\n ),\n }\n : undefined,\n ...(globalValue?.autoCapture ? { autoCapture: globalValue.autoCapture } : {}),\n };\n}\nexport function resolveVoiceConfig(config: VoiceConfig): ResolvedVoiceConfig {\n return {\n engine: config.engine ?? DEFAULT_VOICE_ENGINE,\n language: config.language ?? DEFAULT_VOICE_LANGUAGE,\n recorder: { ...config.recorder, device: config.recorder?.device ?? DEFAULT_RECORDER_DEVICE },\n adapters: config.adapters ?? {},\n ...(config.autoCapture\n ? {\n autoCapture: {\n ...config.autoCapture,\n startPhrases: [...(config.autoCapture.startPhrases ?? [])],\n stopPhrases: [...(config.autoCapture.stopPhrases ?? [])],\n tts: { ...config.autoCapture.tts },\n },\n }\n : {}),\n };\n}\nexport function globalDoomConfigDirectory(homeDirectory = os.homedir()): string {\n return path.join(homeDirectory, GLOBAL_CONFIG_DIR, DOOM_DIR);\n}\nexport function globalDoomConfigPath(homeDirectory = os.homedir()): string {\n return path.join(globalDoomConfigDirectory(homeDirectory), CONFIG_FILE);\n}\nexport function resolvePlanningPlansDirectory(\n configured: string | undefined,\n repoRoot: string,\n homeDirectory = os.homedir(),\n): string {\n if (!configured) return path.join(homeDirectory, GLOBAL_CONFIG_DIR, PLANS_DIR);\n if (configured === '~') return homeDirectory;\n if (configured.startsWith('~/')) return path.resolve(homeDirectory, configured.slice(2));\n if (configured.startsWith('~')) {\n throw new Error(\"Doom planning plansDirectory supports only '~' or '~/' home aliases\");\n }\n return path.isAbsolute(configured) ? path.normalize(configured) : path.resolve(repoRoot, configured);\n}\nexport function repositoryDoomConfigPath(repoRoot: string): string {\n return path.join(repoRoot, DOOM_DIR, CONFIG_FILE);\n}\nexport function loadDoomConfig(repoRoot: string, homeDirectory = os.homedir()): DoomConfig {\n return mergeDoomConfigs(\n readConfig(globalDoomConfigPath(homeDirectory)),\n readConfig(repositoryDoomConfigPath(repoRoot)),\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"configWriter.cjs","names":["fs","parseDocument","path","randomUUID"],"sources":["../src/configWriter.ts"],"sourcesContent":["/**\n * Writes into the global Doom config file, one key path at a time.\n *\n * Extracted from doom-file-edit, which owned the only writer in the repo and had\n * it hard-coded to `editor.command`. Several extensions now edit their own\n * settings from the config panel, and every one of them needs the same four\n * properties: keep the user's comments, never publish a file the parser would\n * reject, never leave a half-written file behind, and apply a set of related\n * edits together.\n *\n * That last one is not a convenience. `voice.adapters.<engine>.model` accepts a\n * path or an id but not both, so switching between them is a set and an unset\n * that must land in the same write, or the file is briefly invalid.\n *\n * Atomicity is the same rename trick as `atomicJson.ts` next door, and carries\n * the same caveat: it prevents torn reads, not lost updates. Two sessions with\n * the panel open still clobber each other, because the rename only publishes\n * what the caller already computed.\n *\n * AVOID:\n * - Treating this as a lock\n * - Writing an empty string; the parser rejects blank values, so clearing a key\n * means removing it\n */\n\nimport { randomUUID } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { parseDocument } from 'yaml';\nimport { parseDoomConfig } from './config.ts';\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\n\nexport interface DoomConfigEdit {\n /** Key path into the document, e.g. `['voice', 'adapters', 'mlx-whisper', 'model', 'id']`. */\n readonly keyPath: readonly string[];\n /** The value to write. `undefined` removes the key. */\n readonly value?: string;\n}\n\nexport interface WriteDoomConfigOptions {\n /**\n * Remove ancestors left holding nothing. Defaults to true, and is not\n * cosmetic: an adapter left as `{}` fails validation, because the parser\n * requires a model once the adapter exists at all.\n */\n readonly prune?: boolean;\n}\n\nasync function readIfPresent(filePath: string): Promise<string> {\n try {\n return await fs.readFile(filePath, 'utf8');\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return '';\n throw error;\n }\n}\n\nfunction isEmptyCollection(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const items = (value as { items?: unknown }).items;\n return Array.isArray(items) && items.length === 0;\n}\n\n/** Drops ancestors of a removed key that are now empty, deepest first. */\nfunction pruneEmptyAncestors(document: ReturnType<typeof parseDocument>, keyPath: readonly string[]): void {\n for (let depth = keyPath.length - 1; depth > 0; depth -= 1) {\n const ancestor = keyPath.slice(0, depth);\n if (!document.hasIn(ancestor)) continue;\n if (!isEmptyCollection(document.getIn(ancestor, true))) return;\n document.deleteIn(ancestor);\n }\n}\n\n/**\n * Applies every edit as one document mutation, or throws having written nothing.\n *\n * Validation runs against the serialized result rather than each edit, so a pair\n * of edits only valid together is accepted, and a single edit that breaks an\n * invariant elsewhere is still caught.\n */\nexport async function writeDoomConfigValues(\n filePath: string,\n edits: readonly DoomConfigEdit[],\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n if (edits.length === 0) return;\n const content = await readIfPresent(filePath);\n // Not `content || '{}\\n'`: seeding an absent file with a flow map makes yaml\n // keep that style, so the first write produces `{ editor: { command: ... } }`\n // and every later one grows that one line. An empty source starts a block map.\n const document = parseDocument(content, { keepSourceTokens: true });\n if (document.errors.length > 0) throw new Error(`Could not parse Doom config at ${filePath}`);\n\n for (const edit of edits) {\n const keyPath = [...edit.keyPath];\n if (edit.value !== undefined) {\n document.setIn(keyPath, edit.value);\n continue;\n }\n // deleteIn throws when an ancestor is missing rather than treating the key as\n // already absent, and clearing an unset key is worth supporting.\n if (!document.hasIn(keyPath)) continue;\n document.deleteIn(keyPath);\n if (options.prune !== false) pruneEmptyAncestors(document, keyPath);\n }\n\n const output = document.toString();\n parseDoomConfig(output, filePath);\n\n await fs.mkdir(path.dirname(filePath), { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await fs.writeFile(temporaryPath, output, { encoding: 'utf8', mode: PRIVATE_FILE_MODE });\n await fs.rename(temporaryPath, filePath);\n } catch (error) {\n await fs.rm(temporaryPath, { force: true });\n throw error;\n }\n}\n\nexport async function setDoomConfigValue(filePath: string, keyPath: readonly string[], value: string): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath, value }]);\n}\n\nexport async function unsetDoomConfigValue(filePath: string, keyPath: readonly string[]): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath }]);\n}\n"],"mappings":"2RAkDA,eAAe,EAAc,EAAmC,CAC9D,GAAI,CACF,OAAO,MAAMA,EAAAA,QAAG,SAAS,EAAU,MAAM,CAC3C,OAAS,EAAO,CACd,GAAI,aAAiB,OAAS,SAAU,GAAS,EAAM,OAAS,SAAU,MAAO,GACjF,MAAM,CACR,CACF,CAEA,SAAS,EAAkB,EAAyB,CAClD,GAAsB,OAAO,GAAU,WAAnC,EAA6C,MAAO,GACxD,IAAM,EAAS,EAA8B,MAC7C,OAAO,MAAM,QAAQ,CAAK,GAAK,EAAM,SAAW,CAClD,CAGA,SAAS,EAAoB,EAA4C,EAAkC,CACzG,IAAK,IAAI,EAAQ,EAAQ,OAAS,EAAG,EAAQ,EAAG,IAAY,CAC1D,IAAM,EAAW,EAAQ,MAAM,EAAG,CAAK,EAClC,KAAS,MAAM,CAAQ,EAC5B,IAAI,CAAC,EAAkB,EAAS,MAAM,EAAU,EAAI,CAAC,EAAG,OACxD,EAAS,SAAS,CAAQ,CAD8B,CAE1D,CACF,CASA,eAAsB,EACpB,EACA,EACA,EAAkC,CAAC,EACpB,CACf,GAAI,EAAM,SAAW,EAAG,OACxB,IAAM,EAAU,MAAM,EAAc,CAAQ,EAItC,GAAA,EAAWC,EAAAA,cAAAA,CAAc,EAAS,CAAE,iBAAkB,EAAK,CAAC,EAClE,GAAI,EAAS,OAAO,OAAS,EAAG,MAAU,MAAM,kCAAkC,GAAU,EAE5F,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAU,CAAC,GAAG,EAAK,OAAO,EAChC,GAAI,EAAK,QAAU,IAAA,GAAW,CAC5B,EAAS,MAAM,EAAS,EAAK,KAAK,EAClC,QACF,CAGK,EAAS,MAAM,CAAO,IAC3B,EAAS,SAAS,CAAO,EACrB,EAAQ,QAAU,IAAO,EAAoB,EAAU,CAAO,EACpE,CAEA,IAAM,EAAS,EAAS,SAAS,EACjC,EAAA,gBAAgB,EAAQ,CAAQ,EAEhC,MAAMD,EAAAA,QAAG,MAAME,EAAAA,QAAK,QAAQ,CAAQ,EAAG,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EACxF,IAAM,EAAgB,GAAG,EAAS,GAAG,QAAQ,IAAI,IAAA,EAAGC,EAAAA,WAAAA,CAAW,EAAE,MACjE,GAAI,CACF,MAAMH,EAAAA,QAAG,UAAU,EAAe,EAAQ,CAAE,SAAU,OAAQ,KAAM,GAAkB,CAAC,EACvF,MAAMA,EAAAA,QAAG,OAAO,EAAe,CAAQ,CACzC,OAAS,EAAO,CAEd,MADA,MAAMA,EAAAA,QAAG,GAAG,EAAe,CAAE,MAAO,EAAK,CAAC,EACpC,CACR,CACF,CAEA,eAAsB,EAAmB,EAAkB,EAA4B,EAA8B,CACnH,MAAM,EAAsB,EAAU,CAAC,CAAE,UAAS,OAAM,CAAC,CAAC,CAC5D,CAEA,eAAsB,EAAqB,EAAkB,EAA2C,CACtG,MAAM,EAAsB,EAAU,CAAC,CAAE,SAAQ,CAAC,CAAC,CACrD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"configWriter.d.cts","names":[],"sources":["../src/configWriter.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;UAkCiB;;WAEN;;WAEA;;UAGM;;;;;;WAMN;;;;;;;;;iBAmCW,sBACpB,kBACA,gBAAgB,kBAChB,UAAS,yBACR;iBAoCmB,mBAAmB,kBAAkB,4BAA4B,gBAAgB;iBAIjF,qBAAqB,kBAAkB,6BAA6B"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"configWriter.d.mts","names":[],"sources":["../src/configWriter.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;UAkCiB;;WAEN;;WAEA;;UAGM;;;;;;WAMN;;;;;;;;;iBAmCW,sBACpB,kBACA,gBAAgB,kBAChB,UAAS,yBACR;iBAoCmB,mBAAmB,kBAAkB,4BAA4B,gBAAgB;iBAIjF,qBAAqB,kBAAkB,6BAA6B"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"configWriter.mjs","names":[],"sources":["../src/configWriter.ts"],"sourcesContent":["/**\n * Writes into the global Doom config file, one key path at a time.\n *\n * Extracted from doom-file-edit, which owned the only writer in the repo and had\n * it hard-coded to `editor.command`. Several extensions now edit their own\n * settings from the config panel, and every one of them needs the same four\n * properties: keep the user's comments, never publish a file the parser would\n * reject, never leave a half-written file behind, and apply a set of related\n * edits together.\n *\n * That last one is not a convenience. `voice.adapters.<engine>.model` accepts a\n * path or an id but not both, so switching between them is a set and an unset\n * that must land in the same write, or the file is briefly invalid.\n *\n * Atomicity is the same rename trick as `atomicJson.ts` next door, and carries\n * the same caveat: it prevents torn reads, not lost updates. Two sessions with\n * the panel open still clobber each other, because the rename only publishes\n * what the caller already computed.\n *\n * AVOID:\n * - Treating this as a lock\n * - Writing an empty string; the parser rejects blank values, so clearing a key\n * means removing it\n */\n\nimport { randomUUID } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { parseDocument } from 'yaml';\nimport { parseDoomConfig } from './config.ts';\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\n\nexport interface DoomConfigEdit {\n /** Key path into the document, e.g. `['voice', 'adapters', 'mlx-whisper', 'model', 'id']`. */\n readonly keyPath: readonly string[];\n /** The value to write. `undefined` removes the key. */\n readonly value?: string;\n}\n\nexport interface WriteDoomConfigOptions {\n /**\n * Remove ancestors left holding nothing. Defaults to true, and is not\n * cosmetic: an adapter left as `{}` fails validation, because the parser\n * requires a model once the adapter exists at all.\n */\n readonly prune?: boolean;\n}\n\nasync function readIfPresent(filePath: string): Promise<string> {\n try {\n return await fs.readFile(filePath, 'utf8');\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return '';\n throw error;\n }\n}\n\nfunction isEmptyCollection(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const items = (value as { items?: unknown }).items;\n return Array.isArray(items) && items.length === 0;\n}\n\n/** Drops ancestors of a removed key that are now empty, deepest first. */\nfunction pruneEmptyAncestors(document: ReturnType<typeof parseDocument>, keyPath: readonly string[]): void {\n for (let depth = keyPath.length - 1; depth > 0; depth -= 1) {\n const ancestor = keyPath.slice(0, depth);\n if (!document.hasIn(ancestor)) continue;\n if (!isEmptyCollection(document.getIn(ancestor, true))) return;\n document.deleteIn(ancestor);\n }\n}\n\n/**\n * Applies every edit as one document mutation, or throws having written nothing.\n *\n * Validation runs against the serialized result rather than each edit, so a pair\n * of edits only valid together is accepted, and a single edit that breaks an\n * invariant elsewhere is still caught.\n */\nexport async function writeDoomConfigValues(\n filePath: string,\n edits: readonly DoomConfigEdit[],\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n if (edits.length === 0) return;\n const content = await readIfPresent(filePath);\n // Not `content || '{}\\n'`: seeding an absent file with a flow map makes yaml\n // keep that style, so the first write produces `{ editor: { command: ... } }`\n // and every later one grows that one line. An empty source starts a block map.\n const document = parseDocument(content, { keepSourceTokens: true });\n if (document.errors.length > 0) throw new Error(`Could not parse Doom config at ${filePath}`);\n\n for (const edit of edits) {\n const keyPath = [...edit.keyPath];\n if (edit.value !== undefined) {\n document.setIn(keyPath, edit.value);\n continue;\n }\n // deleteIn throws when an ancestor is missing rather than treating the key as\n // already absent, and clearing an unset key is worth supporting.\n if (!document.hasIn(keyPath)) continue;\n document.deleteIn(keyPath);\n if (options.prune !== false) pruneEmptyAncestors(document, keyPath);\n }\n\n const output = document.toString();\n parseDoomConfig(output, filePath);\n\n await fs.mkdir(path.dirname(filePath), { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await fs.writeFile(temporaryPath, output, { encoding: 'utf8', mode: PRIVATE_FILE_MODE });\n await fs.rename(temporaryPath, filePath);\n } catch (error) {\n await fs.rm(temporaryPath, { force: true });\n throw error;\n }\n}\n\nexport async function setDoomConfigValue(filePath: string, keyPath: readonly string[], value: string): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath, value }]);\n}\n\nexport async function unsetDoomConfigValue(filePath: string, keyPath: readonly string[]): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath }]);\n}\n"],"mappings":"sLAkDA,eAAe,EAAc,EAAmC,CAC9D,GAAI,CACF,OAAO,MAAM,EAAG,SAAS,EAAU,MAAM,CAC3C,OAAS,EAAO,CACd,GAAI,aAAiB,OAAS,SAAU,GAAS,EAAM,OAAS,SAAU,MAAO,GACjF,MAAM,CACR,CACF,CAEA,SAAS,EAAkB,EAAyB,CAClD,GAAsB,OAAO,GAAU,WAAnC,EAA6C,MAAO,GACxD,IAAM,EAAS,EAA8B,MAC7C,OAAO,MAAM,QAAQ,CAAK,GAAK,EAAM,SAAW,CAClD,CAGA,SAAS,EAAoB,EAA4C,EAAkC,CACzG,IAAK,IAAI,EAAQ,EAAQ,OAAS,EAAG,EAAQ,EAAG,IAAY,CAC1D,IAAM,EAAW,EAAQ,MAAM,EAAG,CAAK,EAClC,KAAS,MAAM,CAAQ,EAC5B,IAAI,CAAC,EAAkB,EAAS,MAAM,EAAU,EAAI,CAAC,EAAG,OACxD,EAAS,SAAS,CAAQ,CAD8B,CAE1D,CACF,CASA,eAAsB,EACpB,EACA,EACA,EAAkC,CAAC,EACpB,CACf,GAAI,EAAM,SAAW,EAAG,OACxB,IAAM,EAAU,MAAM,EAAc,CAAQ,EAItC,EAAW,EAAc,EAAS,CAAE,iBAAkB,EAAK,CAAC,EAClE,GAAI,EAAS,OAAO,OAAS,EAAG,MAAU,MAAM,kCAAkC,GAAU,EAE5F,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAU,CAAC,GAAG,EAAK,OAAO,EAChC,GAAI,EAAK,QAAU,IAAA,GAAW,CAC5B,EAAS,MAAM,EAAS,EAAK,KAAK,EAClC,QACF,CAGK,EAAS,MAAM,CAAO,IAC3B,EAAS,SAAS,CAAO,EACrB,EAAQ,QAAU,IAAO,EAAoB,EAAU,CAAO,EACpE,CAEA,IAAM,EAAS,EAAS,SAAS,EACjC,EAAgB,EAAQ,CAAQ,EAEhC,MAAM,EAAG,MAAM,EAAK,QAAQ,CAAQ,EAAG,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EACxF,IAAM,EAAgB,GAAG,EAAS,GAAG,QAAQ,IAAI,GAAG,EAAW,EAAE,MACjE,GAAI,CACF,MAAM,EAAG,UAAU,EAAe,EAAQ,CAAE,SAAU,OAAQ,KAAM,GAAkB,CAAC,EACvF,MAAM,EAAG,OAAO,EAAe,CAAQ,CACzC,OAAS,EAAO,CAEd,MADA,MAAM,EAAG,GAAG,EAAe,CAAE,MAAO,EAAK,CAAC,EACpC,CACR,CACF,CAEA,eAAsB,EAAmB,EAAkB,EAA4B,EAA8B,CACnH,MAAM,EAAsB,EAAU,CAAC,CAAE,UAAS,OAAM,CAAC,CAAC,CAC5D,CAEA,eAAsB,EAAqB,EAAkB,EAA2C,CACtG,MAAM,EAAsB,EAAU,CAAC,CAAE,SAAQ,CAAC,CAAC,CACrD"}
|
package/dist/container.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"container.cjs","names":["loadDoomConfig","injectable","ContainerModule","DOOM_CONFIG_TYPES","Container"],"sources":["../src/container.ts"],"sourcesContent":["import 'reflect-metadata';\nimport { Container, ContainerModule, injectable } from 'inversify';\nimport { loadDoomConfig } from './config.ts';\nimport { DOOM_CONFIG_TYPES, type DoomConfig, type IDoomConfigService } from './types.ts';\n\n@injectable()\nexport class DoomConfigService implements IDoomConfigService {\n load(repoRoot: string, homeDirectory?: string): DoomConfig {\n return loadDoomConfig(repoRoot, homeDirectory);\n }\n}\nexport function createDoomConfigModule(): ContainerModule {\n return new ContainerModule(({ bind }) => {\n bind<IDoomConfigService>(DOOM_CONFIG_TYPES.ConfigService).to(DoomConfigService).inSingletonScope();\n });\n}\nexport function createDoomConfigContainer(): Container {\n const container = new Container();\n container.bind<IDoomConfigService>(DOOM_CONFIG_TYPES.ConfigService).to(DoomConfigService).inSingletonScope();\n return container;\n}\n"],"mappings":"kQAMa,EAAN,KAAsD,CAC3D,KAAK,EAAkB,EAAoC,CACzD,OAAOA,EAAAA,eAAe,EAAU,CAAa,CAC/C,CACF,EALCC,EAAAA,EAAAA,QAAAA,EAAAA,EAAAA,EAAAA,WAAAA,CAAW,CAAA,EAAA,CAAA,EAMZ,SAAgB,GAA0C,CACxD,OAAO,IAAIC,EAAAA,iBAAiB,CAAE,UAAW,CACvC,EAAyBC,EAAAA,kBAAkB,aAAa,CAAC,CAAC,GAAG,CAAiB,CAAC,CAAC,iBAAiB,CACnG,CAAC,CACH,CACA,SAAgB,GAAuC,CACrD,IAAM,EAAY,IAAIC,EAAAA,UAEtB,OADA,EAAU,KAAyBD,EAAAA,kBAAkB,aAAa,CAAC,CAAC,GAAG,CAAiB,CAAC,CAAC,iBAAiB,EACpG,CACT"}
|
package/dist/container.d.cts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"container.d.cts","names":[],"sources":["../src/container.ts"],"mappings":";;;;cAMa,6BAA6B;EACxC,KAAK,kBAAkB,yBAAyB;;iBAIlC,0BAA0B;iBAK1B,6BAA6B"}
|
package/dist/container.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"container.d.mts","names":[],"sources":["../src/container.ts"],"mappings":";;;;cAMa,6BAA6B;EACxC,KAAK,kBAAkB,yBAAyB;;iBAIlC,0BAA0B;iBAK1B,6BAA6B"}
|
package/dist/container.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"container.mjs","names":[],"sources":["../src/container.ts"],"sourcesContent":["import 'reflect-metadata';\nimport { Container, ContainerModule, injectable } from 'inversify';\nimport { loadDoomConfig } from './config.ts';\nimport { DOOM_CONFIG_TYPES, type DoomConfig, type IDoomConfigService } from './types.ts';\n\n@injectable()\nexport class DoomConfigService implements IDoomConfigService {\n load(repoRoot: string, homeDirectory?: string): DoomConfig {\n return loadDoomConfig(repoRoot, homeDirectory);\n }\n}\nexport function createDoomConfigModule(): ContainerModule {\n return new ContainerModule(({ bind }) => {\n bind<IDoomConfigService>(DOOM_CONFIG_TYPES.ConfigService).to(DoomConfigService).inSingletonScope();\n });\n}\nexport function createDoomConfigContainer(): Container {\n const container = new Container();\n container.bind<IDoomConfigService>(DOOM_CONFIG_TYPES.ConfigService).to(DoomConfigService).inSingletonScope();\n return container;\n}\n"],"mappings":"mRAMO,IAAM,EAAN,KAAsD,CAC3D,KAAK,EAAkB,EAAoC,CACzD,OAAO,EAAe,EAAU,CAAa,CAC/C,CACF,EALC,EAAA,EAAA,CAAA,EAAW,CAAA,EAAA,CAAA,EAMZ,SAAgB,GAA0C,CACxD,OAAO,IAAI,GAAiB,CAAE,UAAW,CACvC,EAAyB,EAAkB,aAAa,CAAC,CAAC,GAAG,CAAiB,CAAC,CAAC,iBAAiB,CACnG,CAAC,CACH,CACA,SAAgB,GAAuC,CACrD,IAAM,EAAY,IAAI,EAEtB,OADA,EAAU,KAAyB,EAAkB,aAAa,CAAC,CAAC,GAAG,CAAiB,CAAC,CAAC,iBAAiB,EACpG,CACT"}
|
package/dist/domains.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"domains.cjs","names":["os","readDoomConfigSources","mergeNamedSources","path","fs"],"sources":["../src/domains.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { mergeNamedSources, readDoomConfigSources } from './layeredConfig.ts';\n\nexport { DOOM_DIR } from './layeredConfig.ts';\n\n/** A plugin entry is a whole plugin dir, or a subset of one. */\nexport type DomainPlugin =\n | string\n | { dir: string; skills?: string[]; agents?: string[]; hooks?: boolean; mcp?: boolean };\n\nexport interface DomainMcpAllowlist {\n /** Server names kept from the repo-level .mcp.json. */\n servers?: string[];\n /** Upstream server names kept in the agiflow-proxy config. */\n proxy?: string[];\n}\n\nexport interface DomainDefinition {\n /**\n * Plugin directories, each one relative to the config that declares it: the\n * repository root for `.doom/domains.yaml`, the global `.doom` directory for\n * `~/.pi/.doom/domains.yaml`. The path is written out in full, so a plugin\n * does not have to live under `plugins/`.\n */\n plugins?: DomainPlugin[];\n mcp?: DomainMcpAllowlist;\n /** Prepend .claude/skills. Defaults to true. */\n sharedSkills?: boolean;\n}\n\n/** A domain plus the root its plugin paths resolve against. */\nexport interface ResolvedDomain extends DomainDefinition {\n baseDirectory: string;\n}\n\nexport interface DomainManifest {\n domains: Record<string, ResolvedDomain>;\n aliases: Record<string, string[]>;\n /** Domains selected when neither flags nor the environment name any. */\n defaultDomains?: string[];\n}\n\ninterface DomainDocument {\n domains?: Record<string, DomainDefinition>;\n aliases?: Record<string, string[]>;\n defaultDomains?: unknown;\n}\n\nconst DOMAINS_FILE = 'domains.yaml';\nconst UNKNOWN_DOMAIN_ERROR = 'Unknown domain';\nconst MISSING_DOMAIN_PLUGIN_ERROR = 'Domain plugin does not exist';\nconst MISSING_PLUGIN_DIRECTORY_ERROR = 'Plugin directory does not exist';\nconst NAME_DELIMITER = ',';\nconst DEFAULT_DOMAINS_PATH = '.doom/domains.yaml';\n\nfunction parseDefaultDomains(value: unknown): string[] {\n if (!Array.isArray(value) || value.some((name) => typeof name !== 'string' || !name.trim())) {\n throw new Error(`defaultDomains in ${DEFAULT_DOMAINS_PATH} must be an array of non-empty strings`);\n }\n return [...new Set(value.map((name: string) => name.trim()))];\n}\n\n/**\n * Reads domains from the global and repository configs, repository last.\n *\n * Either file alone is a complete manifest. A repository domain replaces the\n * global domain of the same name outright, and a repository defaultDomains\n * list replaces the global list when present.\n */\nexport function loadDomains(repoRoot: string, homeDirectory: string = os.homedir()): DomainManifest {\n const sources = readDoomConfigSources<DomainDocument>(DOMAINS_FILE, repoRoot, homeDirectory);\n const aliases: Record<string, string[]> = {};\n let defaultDomains: string[] | undefined;\n for (const source of sources) {\n Object.assign(aliases, source.document.aliases ?? {});\n if (Object.hasOwn(source.document, 'defaultDomains')) {\n defaultDomains = parseDefaultDomains(source.document.defaultDomains);\n }\n }\n const domains = mergeNamedSources(sources, (document) => document.domains);\n if (defaultDomains) {\n for (const name of expandDomainNames({ domains, aliases }, defaultDomains)) {\n if (!Object.hasOwn(domains, name)) {\n throw new Error(`Unknown default domain \"${name}\" in ${DEFAULT_DOMAINS_PATH}`);\n }\n }\n }\n return { domains, aliases, ...(defaultDomains ? { defaultDomains } : {}) };\n}\n\nexport function listDomainNames(repoRoot: string, homeDirectory?: string): string[] {\n const manifest = loadDomains(repoRoot, homeDirectory);\n return [...new Set([...Object.keys(manifest.domains), ...Object.keys(manifest.aliases)])].sort();\n}\n\n/** Splits comma-joined selections and expands aliases into concrete domain names. */\nexport function expandDomainNames(manifest: DomainManifest, domainNames: readonly string[]): string[] {\n return domainNames\n .flatMap((raw) =>\n raw\n .split(NAME_DELIMITER)\n .map((name) => name.trim())\n .filter(Boolean),\n )\n .flatMap((name) => manifest.aliases[name] ?? [name]);\n}\n\nexport function domainCompletionPrefix(textBeforeCursor: string): string | undefined {\n const match = textBeforeCursor.match(/^\\/domains\\s+(?:[^\\s,]+,)*([^\\s,]*)$/);\n return match?.[1];\n}\n\nexport function domainCompletionItems(domainNames: string[], query: string): Array<{ value: string; label: string }> {\n const normalizedQuery = query.toLowerCase();\n return domainNames\n .filter((name) => name.toLowerCase().startsWith(normalizedQuery))\n .map((name) => ({ value: name, label: name }));\n}\n\n/** A resolved plugin directory plus any subset filter the domain applied. */\nexport interface PluginEntry {\n directory: string;\n /** Skill names to keep. Omit to keep every skill in the plugin. */\n skills?: string[];\n /** Agent names to keep. Omit to keep every agent in the plugin. */\n agents?: string[];\n hooks?: boolean;\n mcp?: boolean;\n}\n\n/**\n * Whether to prepend .claude/skills.\n *\n * Shared skills are included unless every selected domain opts out, so mixing a\n * trimmed domain with an untrimmed one cannot strip the untrimmed one's skills.\n */\nexport function resolveSharedSkills(repoRoot: string, domainNames: string[], homeDirectory?: string): boolean {\n const manifest = loadDomains(repoRoot, homeDirectory);\n const expanded = expandDomainNames(manifest, domainNames);\n if (expanded.length === 0) return true;\n return expanded.some((name) => manifest.domains[name]?.sharedSkills !== false);\n}\n\n/** Resolves domains to plugin entries, preserving each plugin's subset filter. */\nexport function resolvePluginEntries(\n repoRoot: string,\n domainNames: string[],\n explicitDirectories: string[],\n homeDirectory?: string,\n): PluginEntry[] {\n const manifest = loadDomains(repoRoot, homeDirectory);\n const entries: PluginEntry[] = [];\n const seen = new Set<string>();\n\n for (const name of expandDomainNames(manifest, domainNames)) {\n const domain = manifest.domains[name];\n if (!domain) throw new Error(`${UNKNOWN_DOMAIN_ERROR}: ${name}`);\n for (const plugin of domain.plugins ?? []) {\n const isSubset = typeof plugin !== 'string';\n const directory = path.resolve(domain.baseDirectory, isSubset ? plugin.dir : plugin);\n if (!fs.existsSync(directory)) throw new Error(`${MISSING_DOMAIN_PLUGIN_ERROR}: ${directory}`);\n if (seen.has(directory)) continue;\n seen.add(directory);\n entries.push(\n isSubset\n ? { directory, skills: plugin.skills, agents: plugin.agents, hooks: plugin.hooks, mcp: plugin.mcp }\n : { directory },\n );\n }\n }\n\n for (const directory of explicitDirectories) {\n if (!fs.existsSync(directory)) throw new Error(`${MISSING_PLUGIN_DIRECTORY_ERROR}: ${directory}`);\n const resolved = path.resolve(directory);\n if (seen.has(resolved)) continue;\n seen.add(resolved);\n entries.push({ directory: resolved });\n }\n return entries;\n}\n\nexport function resolvePluginDirectories(\n repoRoot: string,\n domainNames: string[],\n explicitDirectories: string[],\n homeDirectory?: string,\n): string[] {\n return resolvePluginEntries(repoRoot, domainNames, explicitDirectories, homeDirectory).map(\n (entry) => entry.directory,\n );\n}\n"],"mappings":"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"}
|
package/dist/domains.d.cts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"domains.d.cts","names":[],"sources":["../src/domains.ts"],"mappings":";;;KAQY;EAEN;EAAa;EAAmB;EAAmB;EAAiB;;UAEzD;;EAEf;;EAEA;;UAGe;;;;;;;EAOf,UAAU;EACV,MAAM;;EAEN;;;UAIe,uBAAuB;EACtC;;UAGe;EACf,SAAS,eAAe;EACxB,SAAS;;EAET;;;;;;;;;iBA8Bc,YAAY,kBAAkB,yBAAuC;iBAqBrE,gBAAgB,kBAAkB;;iBAMlC,kBAAkB,UAAU,gBAAgB;iBAW5C,uBAAuB;iBAKvB,sBAAsB,uBAAuB,gBAAgB;EAAQ;EAAe;;;UAQnF;EACf;;EAEA;;EAEA;EACA;EACA;;;;;;;;iBASc,oBAAoB,kBAAkB,uBAAuB;;iBAQ7D,qBACd,kBACA,uBACA,+BACA,yBACC;iBAgCa,yBACd,kBACA,uBACA,+BACA"}
|
package/dist/domains.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"domains.d.mts","names":[],"sources":["../src/domains.ts"],"mappings":";;;KAQY;EAEN;EAAa;EAAmB;EAAmB;EAAiB;;UAEzD;;EAEf;;EAEA;;UAGe;;;;;;;EAOf,UAAU;EACV,MAAM;;EAEN;;;UAIe,uBAAuB;EACtC;;UAGe;EACf,SAAS,eAAe;EACxB,SAAS;;EAET;;;;;;;;;iBA8Bc,YAAY,kBAAkB,yBAAuC;iBAqBrE,gBAAgB,kBAAkB;;iBAMlC,kBAAkB,UAAU,gBAAgB;iBAW5C,uBAAuB;iBAKvB,sBAAsB,uBAAuB,gBAAgB;EAAQ;EAAe;;;UAQnF;EACf;;EAEA;;EAEA;EACA;EACA;;;;;;;;iBASc,oBAAoB,kBAAkB,uBAAuB;;iBAQ7D,qBACd,kBACA,uBACA,+BACA,yBACC;iBAgCa,yBACd,kBACA,uBACA,+BACA"}
|
package/dist/domains.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"domains.mjs","names":[],"sources":["../src/domains.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { mergeNamedSources, readDoomConfigSources } from './layeredConfig.ts';\n\nexport { DOOM_DIR } from './layeredConfig.ts';\n\n/** A plugin entry is a whole plugin dir, or a subset of one. */\nexport type DomainPlugin =\n | string\n | { dir: string; skills?: string[]; agents?: string[]; hooks?: boolean; mcp?: boolean };\n\nexport interface DomainMcpAllowlist {\n /** Server names kept from the repo-level .mcp.json. */\n servers?: string[];\n /** Upstream server names kept in the agiflow-proxy config. */\n proxy?: string[];\n}\n\nexport interface DomainDefinition {\n /**\n * Plugin directories, each one relative to the config that declares it: the\n * repository root for `.doom/domains.yaml`, the global `.doom` directory for\n * `~/.pi/.doom/domains.yaml`. The path is written out in full, so a plugin\n * does not have to live under `plugins/`.\n */\n plugins?: DomainPlugin[];\n mcp?: DomainMcpAllowlist;\n /** Prepend .claude/skills. Defaults to true. */\n sharedSkills?: boolean;\n}\n\n/** A domain plus the root its plugin paths resolve against. */\nexport interface ResolvedDomain extends DomainDefinition {\n baseDirectory: string;\n}\n\nexport interface DomainManifest {\n domains: Record<string, ResolvedDomain>;\n aliases: Record<string, string[]>;\n /** Domains selected when neither flags nor the environment name any. */\n defaultDomains?: string[];\n}\n\ninterface DomainDocument {\n domains?: Record<string, DomainDefinition>;\n aliases?: Record<string, string[]>;\n defaultDomains?: unknown;\n}\n\nconst DOMAINS_FILE = 'domains.yaml';\nconst UNKNOWN_DOMAIN_ERROR = 'Unknown domain';\nconst MISSING_DOMAIN_PLUGIN_ERROR = 'Domain plugin does not exist';\nconst MISSING_PLUGIN_DIRECTORY_ERROR = 'Plugin directory does not exist';\nconst NAME_DELIMITER = ',';\nconst DEFAULT_DOMAINS_PATH = '.doom/domains.yaml';\n\nfunction parseDefaultDomains(value: unknown): string[] {\n if (!Array.isArray(value) || value.some((name) => typeof name !== 'string' || !name.trim())) {\n throw new Error(`defaultDomains in ${DEFAULT_DOMAINS_PATH} must be an array of non-empty strings`);\n }\n return [...new Set(value.map((name: string) => name.trim()))];\n}\n\n/**\n * Reads domains from the global and repository configs, repository last.\n *\n * Either file alone is a complete manifest. A repository domain replaces the\n * global domain of the same name outright, and a repository defaultDomains\n * list replaces the global list when present.\n */\nexport function loadDomains(repoRoot: string, homeDirectory: string = os.homedir()): DomainManifest {\n const sources = readDoomConfigSources<DomainDocument>(DOMAINS_FILE, repoRoot, homeDirectory);\n const aliases: Record<string, string[]> = {};\n let defaultDomains: string[] | undefined;\n for (const source of sources) {\n Object.assign(aliases, source.document.aliases ?? {});\n if (Object.hasOwn(source.document, 'defaultDomains')) {\n defaultDomains = parseDefaultDomains(source.document.defaultDomains);\n }\n }\n const domains = mergeNamedSources(sources, (document) => document.domains);\n if (defaultDomains) {\n for (const name of expandDomainNames({ domains, aliases }, defaultDomains)) {\n if (!Object.hasOwn(domains, name)) {\n throw new Error(`Unknown default domain \"${name}\" in ${DEFAULT_DOMAINS_PATH}`);\n }\n }\n }\n return { domains, aliases, ...(defaultDomains ? { defaultDomains } : {}) };\n}\n\nexport function listDomainNames(repoRoot: string, homeDirectory?: string): string[] {\n const manifest = loadDomains(repoRoot, homeDirectory);\n return [...new Set([...Object.keys(manifest.domains), ...Object.keys(manifest.aliases)])].sort();\n}\n\n/** Splits comma-joined selections and expands aliases into concrete domain names. */\nexport function expandDomainNames(manifest: DomainManifest, domainNames: readonly string[]): string[] {\n return domainNames\n .flatMap((raw) =>\n raw\n .split(NAME_DELIMITER)\n .map((name) => name.trim())\n .filter(Boolean),\n )\n .flatMap((name) => manifest.aliases[name] ?? [name]);\n}\n\nexport function domainCompletionPrefix(textBeforeCursor: string): string | undefined {\n const match = textBeforeCursor.match(/^\\/domains\\s+(?:[^\\s,]+,)*([^\\s,]*)$/);\n return match?.[1];\n}\n\nexport function domainCompletionItems(domainNames: string[], query: string): Array<{ value: string; label: string }> {\n const normalizedQuery = query.toLowerCase();\n return domainNames\n .filter((name) => name.toLowerCase().startsWith(normalizedQuery))\n .map((name) => ({ value: name, label: name }));\n}\n\n/** A resolved plugin directory plus any subset filter the domain applied. */\nexport interface PluginEntry {\n directory: string;\n /** Skill names to keep. Omit to keep every skill in the plugin. */\n skills?: string[];\n /** Agent names to keep. Omit to keep every agent in the plugin. */\n agents?: string[];\n hooks?: boolean;\n mcp?: boolean;\n}\n\n/**\n * Whether to prepend .claude/skills.\n *\n * Shared skills are included unless every selected domain opts out, so mixing a\n * trimmed domain with an untrimmed one cannot strip the untrimmed one's skills.\n */\nexport function resolveSharedSkills(repoRoot: string, domainNames: string[], homeDirectory?: string): boolean {\n const manifest = loadDomains(repoRoot, homeDirectory);\n const expanded = expandDomainNames(manifest, domainNames);\n if (expanded.length === 0) return true;\n return expanded.some((name) => manifest.domains[name]?.sharedSkills !== false);\n}\n\n/** Resolves domains to plugin entries, preserving each plugin's subset filter. */\nexport function resolvePluginEntries(\n repoRoot: string,\n domainNames: string[],\n explicitDirectories: string[],\n homeDirectory?: string,\n): PluginEntry[] {\n const manifest = loadDomains(repoRoot, homeDirectory);\n const entries: PluginEntry[] = [];\n const seen = new Set<string>();\n\n for (const name of expandDomainNames(manifest, domainNames)) {\n const domain = manifest.domains[name];\n if (!domain) throw new Error(`${UNKNOWN_DOMAIN_ERROR}: ${name}`);\n for (const plugin of domain.plugins ?? []) {\n const isSubset = typeof plugin !== 'string';\n const directory = path.resolve(domain.baseDirectory, isSubset ? plugin.dir : plugin);\n if (!fs.existsSync(directory)) throw new Error(`${MISSING_DOMAIN_PLUGIN_ERROR}: ${directory}`);\n if (seen.has(directory)) continue;\n seen.add(directory);\n entries.push(\n isSubset\n ? { directory, skills: plugin.skills, agents: plugin.agents, hooks: plugin.hooks, mcp: plugin.mcp }\n : { directory },\n );\n }\n }\n\n for (const directory of explicitDirectories) {\n if (!fs.existsSync(directory)) throw new Error(`${MISSING_PLUGIN_DIRECTORY_ERROR}: ${directory}`);\n const resolved = path.resolve(directory);\n if (seen.has(resolved)) continue;\n seen.add(resolved);\n entries.push({ directory: resolved });\n }\n return entries;\n}\n\nexport function resolvePluginDirectories(\n repoRoot: string,\n domainNames: string[],\n explicitDirectories: string[],\n homeDirectory?: string,\n): string[] {\n return resolvePluginEntries(repoRoot, domainNames, explicitDirectories, homeDirectory).map(\n (entry) => entry.directory,\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"doom.cjs","names":["loadPiConfig","loadDoomConfig"],"sources":["../../src/extensions/doom.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { loadDoomConfig } from '../config.ts';\nimport { loadPiConfig } from '../config/piConfig.ts';\nimport type { ConfigAdapterRegistrationOptions, PiConfig } from '../config/schema.ts';\n\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\n\nconst registeredHosts = new WeakSet<ExtensionAPI>();\nconst resolvedConfigs = new WeakMap<ExtensionContext, PiConfig>();\n\nexport function readDoomConfigContext(context: ExtensionContext): PiConfig | undefined {\n return resolvedConfigs.get(context);\n}\n\nexport default function registerDoomConfigAdapter(\n pi: ExtensionAPI,\n options: ConfigAdapterRegistrationOptions = {},\n): void {\n if (registeredHosts.has(pi)) throw new Error('Doom config adapter is already registered for this host.');\n registeredHosts.add(pi);\n pi.on(SESSION_START_EVENT, (_event, context) => {\n resolvedConfigs.set(\n context,\n loadPiConfig({\n repoRoot: context.cwd,\n homeDirectory: options.homeDirectory,\n defaults: options.defaults,\n overlay: { ...loadDoomConfig(context.cwd, options.homeDirectory) },\n programmatic: options.programmatic,\n environment: options.environment,\n cli: options.cli,\n isProjectTrusted: () => context.isProjectTrusted(),\n }),\n );\n });\n pi.on(SESSION_SHUTDOWN_EVENT, (_event, context) => {\n resolvedConfigs.delete(context);\n });\n}\n"],"mappings":"oKAQM,EAAkB,IAAI,QACtB,EAAkB,IAAI,QAE5B,SAAgB,EAAsB,EAAiD,CACrF,OAAO,EAAgB,IAAI,CAAO,CACpC,CAEA,SAAwB,EACtB,EACA,EAA4C,CAAC,EACvC,CACN,GAAI,EAAgB,IAAI,CAAE,EAAG,MAAU,MAAM,0DAA0D,EACvG,EAAgB,IAAI,CAAE,EACtB,EAAG,GAAG,iBAAsB,EAAQ,IAAY,CAC9C,EAAgB,IACd,EACAA,EAAAA,aAAa,CACX,SAAU,EAAQ,IAClB,cAAe,EAAQ,cACvB,SAAU,EAAQ,SAClB,QAAS,CAAE,GAAGC,EAAAA,eAAe,EAAQ,IAAK,EAAQ,aAAa,CAAE,EACjE,aAAc,EAAQ,aACtB,YAAa,EAAQ,YACrB,IAAK,EAAQ,IACb,qBAAwB,EAAQ,iBAAiB,CACnD,CAAC,CACH,CACF,CAAC,EACD,EAAG,GAAG,oBAAyB,EAAQ,IAAY,CACjD,EAAgB,OAAO,CAAO,CAChC,CAAC,CACH"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"doom.d.cts","names":[],"sources":["../../src/extensions/doom.ts"],"mappings":";;;iBAWgB,sBAAsB,SAAS,mBAAmB;iBAI1C,0BACtB,IAAI,cACJ,UAAS"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"doom.d.mts","names":[],"sources":["../../src/extensions/doom.ts"],"mappings":";;;iBAWgB,sBAAsB,SAAS,mBAAmB;iBAI1C,0BACtB,IAAI,cACJ,UAAS"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"doom.mjs","names":[],"sources":["../../src/extensions/doom.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { loadDoomConfig } from '../config.ts';\nimport { loadPiConfig } from '../config/piConfig.ts';\nimport type { ConfigAdapterRegistrationOptions, PiConfig } from '../config/schema.ts';\n\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\n\nconst registeredHosts = new WeakSet<ExtensionAPI>();\nconst resolvedConfigs = new WeakMap<ExtensionContext, PiConfig>();\n\nexport function readDoomConfigContext(context: ExtensionContext): PiConfig | undefined {\n return resolvedConfigs.get(context);\n}\n\nexport default function registerDoomConfigAdapter(\n pi: ExtensionAPI,\n options: ConfigAdapterRegistrationOptions = {},\n): void {\n if (registeredHosts.has(pi)) throw new Error('Doom config adapter is already registered for this host.');\n registeredHosts.add(pi);\n pi.on(SESSION_START_EVENT, (_event, context) => {\n resolvedConfigs.set(\n context,\n loadPiConfig({\n repoRoot: context.cwd,\n homeDirectory: options.homeDirectory,\n defaults: options.defaults,\n overlay: { ...loadDoomConfig(context.cwd, options.homeDirectory) },\n programmatic: options.programmatic,\n environment: options.environment,\n cli: options.cli,\n isProjectTrusted: () => context.isProjectTrusted(),\n }),\n );\n });\n pi.on(SESSION_SHUTDOWN_EVENT, (_event, context) => {\n resolvedConfigs.delete(context);\n });\n}\n"],"mappings":"qGAKA,MAGM,EAAkB,IAAI,QACtB,EAAkB,IAAI,QAE5B,SAAgB,EAAsB,EAAiD,CACrF,OAAO,EAAgB,IAAI,CAAO,CACpC,CAEA,SAAwB,EACtB,EACA,EAA4C,CAAC,EACvC,CACN,GAAI,EAAgB,IAAI,CAAE,EAAG,MAAU,MAAM,0DAA0D,EACvG,EAAgB,IAAI,CAAE,EACtB,EAAG,GAAG,iBAAsB,EAAQ,IAAY,CAC9C,EAAgB,IACd,EACA,EAAa,CACX,SAAU,EAAQ,IAClB,cAAe,EAAQ,cACvB,SAAU,EAAQ,SAClB,QAAS,CAAE,GAAG,EAAe,EAAQ,IAAK,EAAQ,aAAa,CAAE,EACjE,aAAc,EAAQ,aACtB,YAAa,EAAQ,YACrB,IAAK,EAAQ,IACb,qBAAwB,EAAQ,iBAAiB,CACnD,CAAC,CACH,CACF,CAAC,EACD,EAAG,GAAG,oBAAyB,EAAQ,IAAY,CACjD,EAAgB,OAAO,CAAO,CAChC,CAAC,CACH"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pi.cjs","names":["loadPiConfig"],"sources":["../../src/extensions/pi.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { loadPiConfig } from '../config/piConfig.ts';\nimport type { ConfigAdapterRegistrationOptions, PiConfig } from '../config/schema.ts';\n\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\n\nconst registeredHosts = new WeakSet<ExtensionAPI>();\nconst resolvedConfigs = new WeakMap<ExtensionContext, PiConfig>();\n\nexport function readPiConfigContext(context: ExtensionContext): PiConfig | undefined {\n return resolvedConfigs.get(context);\n}\n\nexport default function registerPiConfigAdapter(\n pi: ExtensionAPI,\n options: ConfigAdapterRegistrationOptions = {},\n): void {\n if (registeredHosts.has(pi)) throw new Error('Pi config adapter is already registered for this host.');\n registeredHosts.add(pi);\n pi.on(SESSION_START_EVENT, (_event, context) => {\n resolvedConfigs.set(\n context,\n loadPiConfig({\n repoRoot: context.cwd,\n homeDirectory: options.homeDirectory,\n defaults: options.defaults,\n programmatic: options.programmatic,\n environment: options.environment,\n cli: options.cli,\n isProjectTrusted: () => context.isProjectTrusted(),\n }),\n );\n });\n pi.on(SESSION_SHUTDOWN_EVENT, (_event, context) => {\n resolvedConfigs.delete(context);\n });\n}\n"],"mappings":"yIAOM,EAAkB,IAAI,QACtB,EAAkB,IAAI,QAE5B,SAAgB,EAAoB,EAAiD,CACnF,OAAO,EAAgB,IAAI,CAAO,CACpC,CAEA,SAAwB,EACtB,EACA,EAA4C,CAAC,EACvC,CACN,GAAI,EAAgB,IAAI,CAAE,EAAG,MAAU,MAAM,wDAAwD,EACrG,EAAgB,IAAI,CAAE,EACtB,EAAG,GAAG,iBAAsB,EAAQ,IAAY,CAC9C,EAAgB,IACd,EACAA,EAAAA,aAAa,CACX,SAAU,EAAQ,IAClB,cAAe,EAAQ,cACvB,SAAU,EAAQ,SAClB,aAAc,EAAQ,aACtB,YAAa,EAAQ,YACrB,IAAK,EAAQ,IACb,qBAAwB,EAAQ,iBAAiB,CACnD,CAAC,CACH,CACF,CAAC,EACD,EAAG,GAAG,oBAAyB,EAAQ,IAAY,CACjD,EAAgB,OAAO,CAAO,CAChC,CAAC,CACH"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pi.d.cts","names":[],"sources":["../../src/extensions/pi.ts"],"mappings":";;;iBAUgB,oBAAoB,SAAS,mBAAmB;iBAIxC,wBACtB,IAAI,cACJ,UAAS"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pi.d.mts","names":[],"sources":["../../src/extensions/pi.ts"],"mappings":";;;iBAUgB,oBAAoB,SAAS,mBAAmB;iBAIxC,wBACtB,IAAI,cACJ,UAAS"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pi.mjs","names":[],"sources":["../../src/extensions/pi.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { loadPiConfig } from '../config/piConfig.ts';\nimport type { ConfigAdapterRegistrationOptions, PiConfig } from '../config/schema.ts';\n\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\n\nconst registeredHosts = new WeakSet<ExtensionAPI>();\nconst resolvedConfigs = new WeakMap<ExtensionContext, PiConfig>();\n\nexport function readPiConfigContext(context: ExtensionContext): PiConfig | undefined {\n return resolvedConfigs.get(context);\n}\n\nexport default function registerPiConfigAdapter(\n pi: ExtensionAPI,\n options: ConfigAdapterRegistrationOptions = {},\n): void {\n if (registeredHosts.has(pi)) throw new Error('Pi config adapter is already registered for this host.');\n registeredHosts.add(pi);\n pi.on(SESSION_START_EVENT, (_event, context) => {\n resolvedConfigs.set(\n context,\n loadPiConfig({\n repoRoot: context.cwd,\n homeDirectory: options.homeDirectory,\n defaults: options.defaults,\n programmatic: options.programmatic,\n environment: options.environment,\n cli: options.cli,\n isProjectTrusted: () => context.isProjectTrusted(),\n }),\n );\n });\n pi.on(SESSION_SHUTDOWN_EVENT, (_event, context) => {\n resolvedConfigs.delete(context);\n });\n}\n"],"mappings":"sDAIA,MAGM,EAAkB,IAAI,QACtB,EAAkB,IAAI,QAE5B,SAAgB,EAAoB,EAAiD,CACnF,OAAO,EAAgB,IAAI,CAAO,CACpC,CAEA,SAAwB,EACtB,EACA,EAA4C,CAAC,EACvC,CACN,GAAI,EAAgB,IAAI,CAAE,EAAG,MAAU,MAAM,wDAAwD,EACrG,EAAgB,IAAI,CAAE,EACtB,EAAG,GAAG,iBAAsB,EAAQ,IAAY,CAC9C,EAAgB,IACd,EACA,EAAa,CACX,SAAU,EAAQ,IAClB,cAAe,EAAQ,cACvB,SAAU,EAAQ,SAClB,aAAc,EAAQ,aACtB,YAAa,EAAQ,YACrB,IAAK,EAAQ,IACb,qBAAwB,EAAQ,iBAAiB,CACnD,CAAC,CACH,CACF,CAAC,EACD,EAAG,GAAG,oBAAyB,EAAQ,IAAY,CACjD,EAAgB,OAAO,CAAO,CAChC,CAAC,CACH"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"harnessState.cjs","names":["path"],"sources":["../src/harnessState.ts"],"sourcesContent":["import path from 'node:path';\nimport type { HarnessState, PluginHookSource } from './types.ts';\n\n/**\n * The codec between harness state and the environment.\n *\n * Decoding exists for processes that were started without a state file: a\n * nested run, a third-party spawn, or anything older than the store. Encoding\n * exists because bash hooks, the shell launchers, and packages that do not\n * depend on this one can only read an environment. Ownership, persistence and\n * mutation belong to harnessStore.ts; nothing here touches disk or holds state.\n */\n\nconst DEFAULT_MAJOR_MODE = 'copilot';\nconst DISABLED_FLAG = '0';\n\n/**\n * State that lives in the file only.\n *\n * Both are JSON documents that every spawned process and every hook would\n * otherwise copy in its environment, and neither has a reader outside this\n * package: the plugin hook list is consumed by the Pi hooks extension and the\n * profile environment by the profile switcher, both of which read the store.\n */\nexport const FILE_ONLY_STATE_FIELDS: ReadonlySet<keyof HarnessState> = new Set(['profileEnvironment', 'pluginHooks']);\n\nexport type HarnessStateParseReporter = (key: string, error: unknown) => void;\n\nexport const HARNESS_STATE_KEYS = {\n root: 'DOOMPI_ROOT',\n majorMode: 'DOOMPI_MAJOR_MODE',\n temporaryDirectory: 'DOOMPI_TEMP_DIR',\n domains: 'DOOMPI_DOMAINS',\n layers: 'DOOMPI_LAYERS',\n profile: 'DOOMPI_PROFILE',\n profileEnvironment: 'DOOMPI_PROFILE_ENV',\n hookGroups: 'DOOMPI_HOOK_GROUPS',\n skillDirectories: 'DOOMPI_SKILL_DIRS',\n agentDirectories: 'DOOMPI_AGENT_DIRS',\n additionalDirectories: 'DOOMPI_ADDITIONAL_DIRS',\n childExtensions: 'DOOMPI_CHILD_EXTENSIONS',\n pluginDirectories: 'DOOMPI_PLUGIN_DIRS',\n pluginHooks: 'DOOMPI_PLUGIN_HOOKS',\n mcpConfigPath: 'DOOMPI_MCP_CONFIG',\n personaFile: 'DOOMPI_PERSONA_FILE',\n workflowExtension: 'DOOMPI_WORKFLOW_EXTENSION',\n hooks: 'DOOMPI_HOOKS_ENABLED',\n agents: 'DOOMPI_AGENTS_ENABLED',\n mcp: 'DOOMPI_MCP_ENABLED',\n allowProtectedWrites: 'DOOMPI_ALLOW_PROTECTED_WRITES',\n} as const;\n\nfunction splitCsv(value: string | undefined): string[] {\n return (\n value\n ?.split(',')\n .map((item) => item.trim())\n .filter(Boolean) ?? []\n );\n}\n\nfunction splitPaths(value: string | undefined): string[] {\n return (\n value\n ?.split(path.delimiter)\n .map((item) => item.trim())\n .filter(Boolean) ?? []\n );\n}\n\nfunction parseFlag(value: string | undefined): boolean {\n return value !== DISABLED_FLAG;\n}\n\nfunction parseStringRecord(\n value: string | undefined,\n key: string,\n report?: HarnessStateParseReporter,\n): Record<string, string> {\n if (!value) return {};\n try {\n const parsed = JSON.parse(value) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return {};\n return Object.fromEntries(\n Object.entries(parsed).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),\n );\n } catch (error) {\n report?.(key, error);\n return {};\n }\n}\n\nfunction parseStringList(value: string | undefined, key: string, report?: HarnessStateParseReporter): string[] {\n if (!value) return [];\n try {\n const parsed = JSON.parse(value) as unknown;\n return Array.isArray(parsed) ? parsed.filter((item): item is string => typeof item === 'string') : [];\n } catch (error) {\n report?.(key, error);\n return [];\n }\n}\n\nfunction parsePluginHooks(\n value: string | undefined,\n key: string,\n report?: HarnessStateParseReporter,\n): PluginHookSource[] {\n if (!value) return [];\n try {\n const parsed = JSON.parse(value) as unknown;\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(\n (item): item is PluginHookSource =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as PluginHookSource).pluginRoot === 'string' &&\n typeof (item as PluginHookSource).configPath === 'string',\n );\n } catch (error) {\n report?.(key, error);\n return [];\n }\n}\n\nexport function readHarnessState(\n environment: NodeJS.ProcessEnv = process.env,\n report?: HarnessStateParseReporter,\n): HarnessState {\n const keys = HARNESS_STATE_KEYS;\n const hookGroups = environment[keys.hookGroups];\n return {\n root: environment[keys.root] || undefined,\n majorMode: environment[keys.majorMode] || DEFAULT_MAJOR_MODE,\n temporaryDirectory: environment[keys.temporaryDirectory] || undefined,\n domains: splitCsv(environment[keys.domains]),\n layers: splitCsv(environment[keys.layers]),\n profile: environment[keys.profile] || undefined,\n profileEnvironment: parseStringRecord(environment[keys.profileEnvironment], keys.profileEnvironment, report),\n hookGroups: hookGroups === undefined ? undefined : splitCsv(hookGroups),\n skillDirectories: splitPaths(environment[keys.skillDirectories]),\n agentDirectories: splitPaths(environment[keys.agentDirectories]),\n additionalDirectories: splitPaths(environment[keys.additionalDirectories]),\n childExtensions: parseStringList(environment[keys.childExtensions], keys.childExtensions, report),\n pluginDirectories: splitPaths(environment[keys.pluginDirectories]),\n pluginHooks: parsePluginHooks(environment[keys.pluginHooks], keys.pluginHooks, report),\n mcpConfigPath: environment[keys.mcpConfigPath] || undefined,\n personaFile: environment[keys.personaFile] || undefined,\n workflowExtension: environment[keys.workflowExtension] || undefined,\n hooks: parseFlag(environment[keys.hooks]),\n agents: parseFlag(environment[keys.agents]),\n mcp: parseFlag(environment[keys.mcp]),\n allowProtectedWrites: parseFlag(environment[keys.allowProtectedWrites]),\n };\n}\n\n/**\n * Publishes a state patch into an environment.\n *\n * Takes the environment rather than assuming `process.env`, because the\n * launcher builds a child environment and Doom Team builds a spawn snapshot;\n * neither should mutate its own process to describe another one.\n *\n * Fields in FILE_ONLY_FIELDS are skipped: the file carries them, and a reader\n * that needs them reads the store.\n */\nexport function projectHarnessEnvironment(\n patch: Partial<HarnessState>,\n environment: NodeJS.ProcessEnv = process.env,\n): NodeJS.ProcessEnv {\n const assign = (key: string, value: string | undefined): void => {\n if (value === undefined) delete environment[key];\n else environment[key] = value;\n };\n const assignFlag = (key: string, value: boolean): void => assign(key, value ? '1' : DISABLED_FLAG);\n const keys = HARNESS_STATE_KEYS;\n\n if ('root' in patch) assign(keys.root, patch.root);\n if ('majorMode' in patch) assign(keys.majorMode, patch.majorMode);\n if ('temporaryDirectory' in patch) assign(keys.temporaryDirectory, patch.temporaryDirectory);\n if ('domains' in patch) assign(keys.domains, patch.domains?.join(','));\n if ('layers' in patch) assign(keys.layers, patch.layers?.join(','));\n if ('profile' in patch) assign(keys.profile, patch.profile);\n if ('hookGroups' in patch) assign(keys.hookGroups, patch.hookGroups?.join(','));\n if ('skillDirectories' in patch) assign(keys.skillDirectories, patch.skillDirectories?.join(path.delimiter));\n if ('agentDirectories' in patch) assign(keys.agentDirectories, patch.agentDirectories?.join(path.delimiter));\n if ('additionalDirectories' in patch) {\n assign(keys.additionalDirectories, patch.additionalDirectories?.join(path.delimiter));\n }\n if ('childExtensions' in patch) assign(keys.childExtensions, JSON.stringify(patch.childExtensions ?? []));\n if ('pluginDirectories' in patch) assign(keys.pluginDirectories, patch.pluginDirectories?.join(path.delimiter));\n if ('mcpConfigPath' in patch) assign(keys.mcpConfigPath, patch.mcpConfigPath);\n if ('personaFile' in patch) assign(keys.personaFile, patch.personaFile);\n if ('workflowExtension' in patch) assign(keys.workflowExtension, patch.workflowExtension);\n if (patch.hooks !== undefined) assignFlag(keys.hooks, patch.hooks);\n if (patch.agents !== undefined) assignFlag(keys.agents, patch.agents);\n if (patch.mcp !== undefined) assignFlag(keys.mcp, patch.mcp);\n if (patch.allowProtectedWrites !== undefined) assignFlag(keys.allowProtectedWrites, patch.allowProtectedWrites);\n return environment;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"harnessState.d.cts","names":[],"sources":["../src/harnessState.ts"],"mappings":";;;;;;;;;;cAwBa,wBAAwB,kBAAkB;KAE3C,6BAA6B,aAAa;cAEzC;;;;;;;;;;;;;;;;;;;;;;;iBAiGG,iBACd,cAAa,OAAO,YACpB,SAAS,4BACR;;;;;;;;;;;iBAsCa,0BACd,OAAO,QAAQ,eACf,cAAa,OAAO,aACnB,OAAO"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"harnessState.d.mts","names":[],"sources":["../src/harnessState.ts"],"mappings":";;;;;;;;;;cAwBa,wBAAwB,kBAAkB;KAE3C,6BAA6B,aAAa;cAEzC;;;;;;;;;;;;;;;;;;;;;;;iBAiGG,iBACd,cAAa,OAAO,YACpB,SAAS,4BACR;;;;;;;;;;;iBAsCa,0BACd,OAAO,QAAQ,eACf,cAAa,OAAO,aACnB,OAAO"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"harnessState.mjs","names":[],"sources":["../src/harnessState.ts"],"sourcesContent":["import path from 'node:path';\nimport type { HarnessState, PluginHookSource } from './types.ts';\n\n/**\n * The codec between harness state and the environment.\n *\n * Decoding exists for processes that were started without a state file: a\n * nested run, a third-party spawn, or anything older than the store. Encoding\n * exists because bash hooks, the shell launchers, and packages that do not\n * depend on this one can only read an environment. Ownership, persistence and\n * mutation belong to harnessStore.ts; nothing here touches disk or holds state.\n */\n\nconst DEFAULT_MAJOR_MODE = 'copilot';\nconst DISABLED_FLAG = '0';\n\n/**\n * State that lives in the file only.\n *\n * Both are JSON documents that every spawned process and every hook would\n * otherwise copy in its environment, and neither has a reader outside this\n * package: the plugin hook list is consumed by the Pi hooks extension and the\n * profile environment by the profile switcher, both of which read the store.\n */\nexport const FILE_ONLY_STATE_FIELDS: ReadonlySet<keyof HarnessState> = new Set(['profileEnvironment', 'pluginHooks']);\n\nexport type HarnessStateParseReporter = (key: string, error: unknown) => void;\n\nexport const HARNESS_STATE_KEYS = {\n root: 'DOOMPI_ROOT',\n majorMode: 'DOOMPI_MAJOR_MODE',\n temporaryDirectory: 'DOOMPI_TEMP_DIR',\n domains: 'DOOMPI_DOMAINS',\n layers: 'DOOMPI_LAYERS',\n profile: 'DOOMPI_PROFILE',\n profileEnvironment: 'DOOMPI_PROFILE_ENV',\n hookGroups: 'DOOMPI_HOOK_GROUPS',\n skillDirectories: 'DOOMPI_SKILL_DIRS',\n agentDirectories: 'DOOMPI_AGENT_DIRS',\n additionalDirectories: 'DOOMPI_ADDITIONAL_DIRS',\n childExtensions: 'DOOMPI_CHILD_EXTENSIONS',\n pluginDirectories: 'DOOMPI_PLUGIN_DIRS',\n pluginHooks: 'DOOMPI_PLUGIN_HOOKS',\n mcpConfigPath: 'DOOMPI_MCP_CONFIG',\n personaFile: 'DOOMPI_PERSONA_FILE',\n workflowExtension: 'DOOMPI_WORKFLOW_EXTENSION',\n hooks: 'DOOMPI_HOOKS_ENABLED',\n agents: 'DOOMPI_AGENTS_ENABLED',\n mcp: 'DOOMPI_MCP_ENABLED',\n allowProtectedWrites: 'DOOMPI_ALLOW_PROTECTED_WRITES',\n} as const;\n\nfunction splitCsv(value: string | undefined): string[] {\n return (\n value\n ?.split(',')\n .map((item) => item.trim())\n .filter(Boolean) ?? []\n );\n}\n\nfunction splitPaths(value: string | undefined): string[] {\n return (\n value\n ?.split(path.delimiter)\n .map((item) => item.trim())\n .filter(Boolean) ?? []\n );\n}\n\nfunction parseFlag(value: string | undefined): boolean {\n return value !== DISABLED_FLAG;\n}\n\nfunction parseStringRecord(\n value: string | undefined,\n key: string,\n report?: HarnessStateParseReporter,\n): Record<string, string> {\n if (!value) return {};\n try {\n const parsed = JSON.parse(value) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return {};\n return Object.fromEntries(\n Object.entries(parsed).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),\n );\n } catch (error) {\n report?.(key, error);\n return {};\n }\n}\n\nfunction parseStringList(value: string | undefined, key: string, report?: HarnessStateParseReporter): string[] {\n if (!value) return [];\n try {\n const parsed = JSON.parse(value) as unknown;\n return Array.isArray(parsed) ? parsed.filter((item): item is string => typeof item === 'string') : [];\n } catch (error) {\n report?.(key, error);\n return [];\n }\n}\n\nfunction parsePluginHooks(\n value: string | undefined,\n key: string,\n report?: HarnessStateParseReporter,\n): PluginHookSource[] {\n if (!value) return [];\n try {\n const parsed = JSON.parse(value) as unknown;\n if (!Array.isArray(parsed)) return [];\n return parsed.filter(\n (item): item is PluginHookSource =>\n typeof item === 'object' &&\n item !== null &&\n typeof (item as PluginHookSource).pluginRoot === 'string' &&\n typeof (item as PluginHookSource).configPath === 'string',\n );\n } catch (error) {\n report?.(key, error);\n return [];\n }\n}\n\nexport function readHarnessState(\n environment: NodeJS.ProcessEnv = process.env,\n report?: HarnessStateParseReporter,\n): HarnessState {\n const keys = HARNESS_STATE_KEYS;\n const hookGroups = environment[keys.hookGroups];\n return {\n root: environment[keys.root] || undefined,\n majorMode: environment[keys.majorMode] || DEFAULT_MAJOR_MODE,\n temporaryDirectory: environment[keys.temporaryDirectory] || undefined,\n domains: splitCsv(environment[keys.domains]),\n layers: splitCsv(environment[keys.layers]),\n profile: environment[keys.profile] || undefined,\n profileEnvironment: parseStringRecord(environment[keys.profileEnvironment], keys.profileEnvironment, report),\n hookGroups: hookGroups === undefined ? undefined : splitCsv(hookGroups),\n skillDirectories: splitPaths(environment[keys.skillDirectories]),\n agentDirectories: splitPaths(environment[keys.agentDirectories]),\n additionalDirectories: splitPaths(environment[keys.additionalDirectories]),\n childExtensions: parseStringList(environment[keys.childExtensions], keys.childExtensions, report),\n pluginDirectories: splitPaths(environment[keys.pluginDirectories]),\n pluginHooks: parsePluginHooks(environment[keys.pluginHooks], keys.pluginHooks, report),\n mcpConfigPath: environment[keys.mcpConfigPath] || undefined,\n personaFile: environment[keys.personaFile] || undefined,\n workflowExtension: environment[keys.workflowExtension] || undefined,\n hooks: parseFlag(environment[keys.hooks]),\n agents: parseFlag(environment[keys.agents]),\n mcp: parseFlag(environment[keys.mcp]),\n allowProtectedWrites: parseFlag(environment[keys.allowProtectedWrites]),\n };\n}\n\n/**\n * Publishes a state patch into an environment.\n *\n * Takes the environment rather than assuming `process.env`, because the\n * launcher builds a child environment and Doom Team builds a spawn snapshot;\n * neither should mutate its own process to describe another one.\n *\n * Fields in FILE_ONLY_FIELDS are skipped: the file carries them, and a reader\n * that needs them reads the store.\n */\nexport function projectHarnessEnvironment(\n patch: Partial<HarnessState>,\n environment: NodeJS.ProcessEnv = process.env,\n): NodeJS.ProcessEnv {\n const assign = (key: string, value: string | undefined): void => {\n if (value === undefined) delete environment[key];\n else environment[key] = value;\n };\n const assignFlag = (key: string, value: boolean): void => assign(key, value ? '1' : DISABLED_FLAG);\n const keys = HARNESS_STATE_KEYS;\n\n if ('root' in patch) assign(keys.root, patch.root);\n if ('majorMode' in patch) assign(keys.majorMode, patch.majorMode);\n if ('temporaryDirectory' in patch) assign(keys.temporaryDirectory, patch.temporaryDirectory);\n if ('domains' in patch) assign(keys.domains, patch.domains?.join(','));\n if ('layers' in patch) assign(keys.layers, patch.layers?.join(','));\n if ('profile' in patch) assign(keys.profile, patch.profile);\n if ('hookGroups' in patch) assign(keys.hookGroups, patch.hookGroups?.join(','));\n if ('skillDirectories' in patch) assign(keys.skillDirectories, patch.skillDirectories?.join(path.delimiter));\n if ('agentDirectories' in patch) assign(keys.agentDirectories, patch.agentDirectories?.join(path.delimiter));\n if ('additionalDirectories' in patch) {\n assign(keys.additionalDirectories, patch.additionalDirectories?.join(path.delimiter));\n }\n if ('childExtensions' in patch) assign(keys.childExtensions, JSON.stringify(patch.childExtensions ?? []));\n if ('pluginDirectories' in patch) assign(keys.pluginDirectories, patch.pluginDirectories?.join(path.delimiter));\n if ('mcpConfigPath' in patch) assign(keys.mcpConfigPath, patch.mcpConfigPath);\n if ('personaFile' in patch) assign(keys.personaFile, patch.personaFile);\n if ('workflowExtension' in patch) assign(keys.workflowExtension, patch.workflowExtension);\n if (patch.hooks !== undefined) assignFlag(keys.hooks, patch.hooks);\n if (patch.agents !== undefined) assignFlag(keys.agents, patch.agents);\n if (patch.mcp !== undefined) assignFlag(keys.mcp, patch.mcp);\n if (patch.allowProtectedWrites !== undefined) assignFlag(keys.allowProtectedWrites, patch.allowProtectedWrites);\n return environment;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"harnessStore.cjs","names":["fs","readHarnessState","path","os","HARNESS_STATE_KEYS"],"sources":["../src/harnessStore.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { writePrivateAtomicJson } from './atomicJson.ts';\nimport {\n HARNESS_STATE_KEYS,\n projectHarnessEnvironment,\n readHarnessState,\n type HarnessStateParseReporter,\n} from './harnessState.ts';\nimport type { HarnessState } from './types.ts';\n\n/**\n * Who owns the harness state, and where it lives.\n *\n * The state used to be the environment: twenty variables that every reader\n * re-parsed and every writer mutated globally. The file is the authority now,\n * and the environment is a projection published for the readers that can only\n * see an environment, which are bash hooks, the shell launchers, packages that\n * do not depend on this one, and any process spawned by any of them.\n *\n * OWNERSHIP IS PER PROCESS, AND THAT IS THE WHOLE DESIGN:\n * Doom Team subagents run detached, in their own process group, from an\n * environment snapshot taken at spawn. A child can outlive its parent. If it\n * shared the parent's file it would corrupt a session it does not own, and if\n * that file sat in the parent's run directory the parent's shutdown sweep would\n * delete it from under a live run. So every process writes a file it owns:\n * `owner.pid` records who, and a process that finds someone else's file copies\n * it before its first write.\n *\n * AVOID:\n * - Treating the file as shared mutable state between processes. Atomic writes\n * prevent torn reads, not lost updates\n * - Reading the environment directly for anything this exposes\n */\n\n/** Points at the state file. The one variable that is not derived. */\nexport const HARNESS_STATE_POINTER = 'DOOMPI_STATE';\n\nconst STATE_FILE_VERSION = 1;\nconst STATE_FILE_NAME = 'harness-state.json';\nconst ADOPTED_DIRECTORY = 'doom-harness';\n/**\n * Written for a process that does not exist yet, so the first one to read it\n * owns it. A spawner cannot know its child's pid, and a child that inherited\n * its parent's file would lose it the moment the parent cleaned up.\n */\nconst UNCLAIMED_OWNER = 0;\n\nexport interface HarnessStateFile {\n version: number;\n owner: { pid: number; startedAt: string };\n state: HarnessState;\n}\n\nexport interface LoadedHarnessState {\n /** Undefined when this process is running off the environment fallback. */\n filePath?: string;\n owned: boolean;\n state: HarnessState;\n}\n\nlet loaded: LoadedHarnessState | undefined;\n\n/** Drops the cached state. For tests, and for a process changing identity. */\nexport function resetHarnessStore(): void {\n loaded = undefined;\n}\n\nfunction parseStateFile(source: string, filePath: string): HarnessStateFile {\n const parsed = JSON.parse(source) as Partial<HarnessStateFile>;\n if (parsed.version !== STATE_FILE_VERSION) {\n throw new Error(\n `Harness state at ${filePath} has version ${String(parsed.version)}, expected ${STATE_FILE_VERSION}`,\n );\n }\n if (typeof parsed.state !== 'object' || parsed.state === null) {\n throw new Error(`Harness state at ${filePath} has no state object`);\n }\n return {\n version: STATE_FILE_VERSION,\n owner: { pid: parsed.owner?.pid ?? -1, startedAt: parsed.owner?.startedAt ?? '' },\n state: parsed.state as HarnessState,\n };\n}\n\n/**\n * Reads the state for this process, once.\n *\n * A missing pointer means nobody wrote a file for this process, so the\n * environment is all there is: a nested run, a third-party spawn, or a caller\n * older than the store. A pointer that cannot be read is reported and then\n * treated the same way, because a session that starts degraded beats a session\n * that does not start at all.\n */\nexport function loadHarnessState(\n environment: NodeJS.ProcessEnv = process.env,\n report?: HarnessStateParseReporter,\n): LoadedHarnessState {\n // The cache describes this process. Asking about another environment is a\n // question about another process, so it reads through rather than answering\n // from what this one happens to hold.\n const cacheable = environment === process.env;\n if (cacheable && loaded) return loaded;\n\n const filePath = environment[HARNESS_STATE_POINTER];\n let result: LoadedHarnessState | undefined;\n if (filePath && fs.existsSync(filePath)) {\n try {\n const file = parseStateFile(fs.readFileSync(filePath, 'utf8'), filePath);\n const owned = file.owner.pid === process.pid || file.owner.pid === UNCLAIMED_OWNER;\n result = { filePath, owned, state: file.state };\n } catch (error) {\n report?.(HARNESS_STATE_POINTER, error);\n }\n }\n result ??= { owned: false, state: readHarnessState(environment, report) };\n if (cacheable) loaded = result;\n return result;\n}\n\nexport function getHarnessState(report?: HarnessStateParseReporter): HarnessState {\n return loadHarnessState(process.env, report).state;\n}\n\nfunction stateFilePath(directory: string): string {\n return path.join(directory, STATE_FILE_NAME);\n}\n\n/** Where a process writes state when it was not given a file of its own. */\nfunction adoptedStateFilePath(): string {\n return path.join(os.tmpdir(), ADOPTED_DIRECTORY, `${String(process.pid)}.json`);\n}\n\nfunction persist(filePath: string, state: HarnessState, ownerPid = process.pid): void {\n writePrivateAtomicJson(filePath, {\n version: STATE_FILE_VERSION,\n owner: { pid: ownerPid, startedAt: new Date().toISOString() },\n state,\n } satisfies HarnessStateFile);\n}\n\n/**\n * Starts a session: writes the file and publishes the derived environment.\n *\n * The environment is a parameter because the caller is usually describing a\n * process other than itself. The launcher builds the environment for the Pi it\n * is about to spawn, and Doom Team builds the snapshot for a detached child, so\n * neither should touch its own `process.env` to do it.\n */\nexport function createHarnessSession(\n state: HarnessState,\n options: {\n directory: string;\n environment: NodeJS.ProcessEnv;\n /**\n * Leave the file for a process that does not exist yet to claim.\n *\n * What a spawner wants: the child owns its state from its first read, in a\n * directory the spawner manages, so the parent cleaning up its own session\n * cannot pull the file out from under a detached run.\n */\n unclaimed?: boolean;\n },\n): string {\n const filePath = stateFilePath(options.directory);\n persist(filePath, state, options.unclaimed ? UNCLAIMED_OWNER : process.pid);\n options.environment[HARNESS_STATE_POINTER] = filePath;\n projectHarnessEnvironment(state, options.environment);\n // Only prime the cache when this process is the one being described.\n if (!options.unclaimed && options.environment === process.env) loaded = { filePath, owned: true, state };\n return filePath;\n}\n\n/**\n * Applies a patch to this process's state.\n *\n * Copy on write: a process holding a file it does not own writes its own copy\n * and repoints itself at it, so a child can never rewrite its parent's session.\n */\nexport function updateHarnessState(\n patch: Partial<HarnessState>,\n environment: NodeJS.ProcessEnv = process.env,\n): HarnessState {\n const current = loadHarnessState(environment);\n const state = { ...current.state, ...patch };\n const filePath = current.owned && current.filePath ? current.filePath : adoptedStateFilePath();\n persist(filePath, state);\n environment[HARNESS_STATE_POINTER] = filePath;\n if (environment === process.env) loaded = { filePath, owned: true, state };\n projectHarnessEnvironment(patch, environment);\n return state;\n}\n\n/** Removes this process's own state file. Safe when there is none. */\nexport function disposeHarnessState(environment: NodeJS.ProcessEnv = process.env): void {\n const current = loaded;\n resetHarnessStore();\n if (!current?.owned || !current.filePath) return;\n fs.rmSync(current.filePath, { force: true });\n delete environment[HARNESS_STATE_POINTER];\n}\n\nexport function harnessRoot(state: Pick<HarnessState, 'root'> = getHarnessState()): string {\n return state.root ?? process.cwd();\n}\n\n/**\n * The repository root, or a hard error.\n *\n * Separate from `requireHarnessPaths` because most callers only need the root,\n * and separate from `harnessRoot` because falling back to `process.cwd()` would\n * quietly operate on whatever directory the session happens to sit in. A caller\n * that asks for the root wants the session's repository or nothing.\n */\nexport function requireHarnessRoot(state: Pick<HarnessState, 'root'> = getHarnessState()): string {\n if (!state.root) throw new Error(`${HARNESS_STATE_KEYS.root} is not set`);\n return state.root;\n}\n\n/** The root and the run's scratch directory, for callers that write to disk. */\nexport function requireHarnessPaths(state: Pick<HarnessState, 'root' | 'temporaryDirectory'> = getHarnessState()): {\n root: string;\n temporaryDirectory: string;\n} {\n const root = requireHarnessRoot(state);\n if (!state.temporaryDirectory) throw new Error(`${HARNESS_STATE_KEYS.temporaryDirectory} is not set`);\n return { root, temporaryDirectory: state.temporaryDirectory };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"harnessStore.d.cts","names":[],"sources":["../src/harnessStore.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;cAqCa;UAYI;EACf;EACA;IAAS;IAAa;;EACtB,OAAO;;UAGQ;;EAEf;EACA;EACA,OAAO;;;iBAMO;;;;;;;;;;iBA8BA,iBACd,cAAa,OAAO,YACpB,SAAS,4BACR;iBAuBa,gBAAgB,SAAS,4BAA4B;;;;;;;;;iBA6BrD,qBACd,OAAO,cACP;EACE;EACA,aAAa,OAAO;;;;;;;;EAQpB;;;;;;;;iBAkBY,mBACd,OAAO,QAAQ,eACf,cAAa,OAAO,aACnB;;iBAYa,oBAAoB,cAAa,OAAO;iBAQxC,YAAY,QAAO,KAAK;;;;;;;;;iBAYxB,mBAAmB,QAAO,KAAK;;iBAM/B,oBAAoB,QAAO,KAAK;EAC9C;EACA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"harnessStore.d.mts","names":[],"sources":["../src/harnessStore.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;cAqCa;UAYI;EACf;EACA;IAAS;IAAa;;EACtB,OAAO;;UAGQ;;EAEf;EACA;EACA,OAAO;;;iBAMO;;;;;;;;;;iBA8BA,iBACd,cAAa,OAAO,YACpB,SAAS,4BACR;iBAuBa,gBAAgB,SAAS,4BAA4B;;;;;;;;;iBA6BrD,qBACd,OAAO,cACP;EACE;EACA,aAAa,OAAO;;;;;;;;EAQpB;;;;;;;;iBAkBY,mBACd,OAAO,QAAQ,eACf,cAAa,OAAO,aACnB;;iBAYa,oBAAoB,cAAa,OAAO;iBAQxC,YAAY,QAAO,KAAK;;;;;;;;;iBAYxB,mBAAmB,QAAO,KAAK;;iBAM/B,oBAAoB,QAAO,KAAK;EAC9C;EACA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"harnessStore.mjs","names":[],"sources":["../src/harnessStore.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { writePrivateAtomicJson } from './atomicJson.ts';\nimport {\n HARNESS_STATE_KEYS,\n projectHarnessEnvironment,\n readHarnessState,\n type HarnessStateParseReporter,\n} from './harnessState.ts';\nimport type { HarnessState } from './types.ts';\n\n/**\n * Who owns the harness state, and where it lives.\n *\n * The state used to be the environment: twenty variables that every reader\n * re-parsed and every writer mutated globally. The file is the authority now,\n * and the environment is a projection published for the readers that can only\n * see an environment, which are bash hooks, the shell launchers, packages that\n * do not depend on this one, and any process spawned by any of them.\n *\n * OWNERSHIP IS PER PROCESS, AND THAT IS THE WHOLE DESIGN:\n * Doom Team subagents run detached, in their own process group, from an\n * environment snapshot taken at spawn. A child can outlive its parent. If it\n * shared the parent's file it would corrupt a session it does not own, and if\n * that file sat in the parent's run directory the parent's shutdown sweep would\n * delete it from under a live run. So every process writes a file it owns:\n * `owner.pid` records who, and a process that finds someone else's file copies\n * it before its first write.\n *\n * AVOID:\n * - Treating the file as shared mutable state between processes. Atomic writes\n * prevent torn reads, not lost updates\n * - Reading the environment directly for anything this exposes\n */\n\n/** Points at the state file. The one variable that is not derived. */\nexport const HARNESS_STATE_POINTER = 'DOOMPI_STATE';\n\nconst STATE_FILE_VERSION = 1;\nconst STATE_FILE_NAME = 'harness-state.json';\nconst ADOPTED_DIRECTORY = 'doom-harness';\n/**\n * Written for a process that does not exist yet, so the first one to read it\n * owns it. A spawner cannot know its child's pid, and a child that inherited\n * its parent's file would lose it the moment the parent cleaned up.\n */\nconst UNCLAIMED_OWNER = 0;\n\nexport interface HarnessStateFile {\n version: number;\n owner: { pid: number; startedAt: string };\n state: HarnessState;\n}\n\nexport interface LoadedHarnessState {\n /** Undefined when this process is running off the environment fallback. */\n filePath?: string;\n owned: boolean;\n state: HarnessState;\n}\n\nlet loaded: LoadedHarnessState | undefined;\n\n/** Drops the cached state. For tests, and for a process changing identity. */\nexport function resetHarnessStore(): void {\n loaded = undefined;\n}\n\nfunction parseStateFile(source: string, filePath: string): HarnessStateFile {\n const parsed = JSON.parse(source) as Partial<HarnessStateFile>;\n if (parsed.version !== STATE_FILE_VERSION) {\n throw new Error(\n `Harness state at ${filePath} has version ${String(parsed.version)}, expected ${STATE_FILE_VERSION}`,\n );\n }\n if (typeof parsed.state !== 'object' || parsed.state === null) {\n throw new Error(`Harness state at ${filePath} has no state object`);\n }\n return {\n version: STATE_FILE_VERSION,\n owner: { pid: parsed.owner?.pid ?? -1, startedAt: parsed.owner?.startedAt ?? '' },\n state: parsed.state as HarnessState,\n };\n}\n\n/**\n * Reads the state for this process, once.\n *\n * A missing pointer means nobody wrote a file for this process, so the\n * environment is all there is: a nested run, a third-party spawn, or a caller\n * older than the store. A pointer that cannot be read is reported and then\n * treated the same way, because a session that starts degraded beats a session\n * that does not start at all.\n */\nexport function loadHarnessState(\n environment: NodeJS.ProcessEnv = process.env,\n report?: HarnessStateParseReporter,\n): LoadedHarnessState {\n // The cache describes this process. Asking about another environment is a\n // question about another process, so it reads through rather than answering\n // from what this one happens to hold.\n const cacheable = environment === process.env;\n if (cacheable && loaded) return loaded;\n\n const filePath = environment[HARNESS_STATE_POINTER];\n let result: LoadedHarnessState | undefined;\n if (filePath && fs.existsSync(filePath)) {\n try {\n const file = parseStateFile(fs.readFileSync(filePath, 'utf8'), filePath);\n const owned = file.owner.pid === process.pid || file.owner.pid === UNCLAIMED_OWNER;\n result = { filePath, owned, state: file.state };\n } catch (error) {\n report?.(HARNESS_STATE_POINTER, error);\n }\n }\n result ??= { owned: false, state: readHarnessState(environment, report) };\n if (cacheable) loaded = result;\n return result;\n}\n\nexport function getHarnessState(report?: HarnessStateParseReporter): HarnessState {\n return loadHarnessState(process.env, report).state;\n}\n\nfunction stateFilePath(directory: string): string {\n return path.join(directory, STATE_FILE_NAME);\n}\n\n/** Where a process writes state when it was not given a file of its own. */\nfunction adoptedStateFilePath(): string {\n return path.join(os.tmpdir(), ADOPTED_DIRECTORY, `${String(process.pid)}.json`);\n}\n\nfunction persist(filePath: string, state: HarnessState, ownerPid = process.pid): void {\n writePrivateAtomicJson(filePath, {\n version: STATE_FILE_VERSION,\n owner: { pid: ownerPid, startedAt: new Date().toISOString() },\n state,\n } satisfies HarnessStateFile);\n}\n\n/**\n * Starts a session: writes the file and publishes the derived environment.\n *\n * The environment is a parameter because the caller is usually describing a\n * process other than itself. The launcher builds the environment for the Pi it\n * is about to spawn, and Doom Team builds the snapshot for a detached child, so\n * neither should touch its own `process.env` to do it.\n */\nexport function createHarnessSession(\n state: HarnessState,\n options: {\n directory: string;\n environment: NodeJS.ProcessEnv;\n /**\n * Leave the file for a process that does not exist yet to claim.\n *\n * What a spawner wants: the child owns its state from its first read, in a\n * directory the spawner manages, so the parent cleaning up its own session\n * cannot pull the file out from under a detached run.\n */\n unclaimed?: boolean;\n },\n): string {\n const filePath = stateFilePath(options.directory);\n persist(filePath, state, options.unclaimed ? UNCLAIMED_OWNER : process.pid);\n options.environment[HARNESS_STATE_POINTER] = filePath;\n projectHarnessEnvironment(state, options.environment);\n // Only prime the cache when this process is the one being described.\n if (!options.unclaimed && options.environment === process.env) loaded = { filePath, owned: true, state };\n return filePath;\n}\n\n/**\n * Applies a patch to this process's state.\n *\n * Copy on write: a process holding a file it does not own writes its own copy\n * and repoints itself at it, so a child can never rewrite its parent's session.\n */\nexport function updateHarnessState(\n patch: Partial<HarnessState>,\n environment: NodeJS.ProcessEnv = process.env,\n): HarnessState {\n const current = loadHarnessState(environment);\n const state = { ...current.state, ...patch };\n const filePath = current.owned && current.filePath ? current.filePath : adoptedStateFilePath();\n persist(filePath, state);\n environment[HARNESS_STATE_POINTER] = filePath;\n if (environment === process.env) loaded = { filePath, owned: true, state };\n projectHarnessEnvironment(patch, environment);\n return state;\n}\n\n/** Removes this process's own state file. Safe when there is none. */\nexport function disposeHarnessState(environment: NodeJS.ProcessEnv = process.env): void {\n const current = loaded;\n resetHarnessStore();\n if (!current?.owned || !current.filePath) return;\n fs.rmSync(current.filePath, { force: true });\n delete environment[HARNESS_STATE_POINTER];\n}\n\nexport function harnessRoot(state: Pick<HarnessState, 'root'> = getHarnessState()): string {\n return state.root ?? process.cwd();\n}\n\n/**\n * The repository root, or a hard error.\n *\n * Separate from `requireHarnessPaths` because most callers only need the root,\n * and separate from `harnessRoot` because falling back to `process.cwd()` would\n * quietly operate on whatever directory the session happens to sit in. A caller\n * that asks for the root wants the session's repository or nothing.\n */\nexport function requireHarnessRoot(state: Pick<HarnessState, 'root'> = getHarnessState()): string {\n if (!state.root) throw new Error(`${HARNESS_STATE_KEYS.root} is not set`);\n return state.root;\n}\n\n/** The root and the run's scratch directory, for callers that write to disk. */\nexport function requireHarnessPaths(state: Pick<HarnessState, 'root' | 'temporaryDirectory'> = getHarnessState()): {\n root: string;\n temporaryDirectory: string;\n} {\n const root = requireHarnessRoot(state);\n if (!state.temporaryDirectory) throw new Error(`${HARNESS_STATE_KEYS.temporaryDirectory} is not set`);\n return { root, temporaryDirectory: state.temporaryDirectory };\n}\n"],"mappings":"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"}
|
package/dist/init.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"init.cjs","names":["fs","path","randomUUID","os","globalDoomConfigDirectory"],"sources":["../src/init.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from './config.ts';\n\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\nconst UTF8_ENCODING = 'utf8';\nconst FILE_ALREADY_EXISTS = 'EEXIST';\nconst FILE_NOT_FOUND = 'ENOENT';\n\n/** The global seed files are processed in this fixed order. */\nexport const GLOBAL_DOOM_SEED_FILES = [\n 'config.yaml',\n 'modes.yaml',\n 'domains.yaml',\n 'profiles.yaml',\n 'hooks.yaml',\n] as const;\n\ntype GlobalDoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];\n\n/** Portable defaults for a new global Doom configuration directory. */\nexport const DOOM_CONFIG_TEMPLATES: Readonly<Record<GlobalDoomSeedFile, string>> = {\n 'config.yaml': `projectTrust: ask\n\n# Autonomous voice is opt-in and uses an explicitly configured Pi model.\n# voice:\n# autoCapture:\n# model: provider/model-id\n# startPhrases:\n# - hey doom\n# stopPhrases:\n# - stop speaking\n# tts:\n# engine: macos-say\n# voice: Samantha\n# rate: 190\n`,\n 'modes.yaml': `# Seeded defaults. Every layer here resolves from what Doom Pi installs or\n# from a public package, so a fresh install works with no extra setup.\n#\n# Layers and modes defined here apply to every repository. A repository's own\n# .doom/modes.yaml is read after this file and replaces any layer or mode of the\n# same name, so a repository can override one entry without restating the rest.\n#\n# A layer package is an npm specifier, or a path if it was never published:\n# packages: ['./extensions/my-ext']\n# A path is relative to the config that declares it, so it resolves beside this\n# file here (~/.pi/.doom) and against the repository root in a repository copy.\nlayers:\n guardrails:\n # repositoryHooks ships with Doom Pi, so the layer steers the agent toward\n # the governing skill and scaffolding before it writes.\n extensions: [repositoryHooks]\n hookGroups: [guardrails, scaffolding]\n vibe-lint:\n packages:\n - '@agimon-ai/vibe-lint/extensions/pi'\n team:\n packages:\n - '@agimon-ai/doompi-team/extensions/doom'\n plan-mode:\n packages:\n - '@agimon-ai/doompi-plan/extensions/doom'\n runner:\n packages:\n - '@agimon-ai/doompi-runner/extensions/pi'\n style-system:\n extensions: [styleSystem]\n goal:\n packages:\n - '@narumitw/pi-goal/src/index.ts'\n ask-user:\n packages:\n - '@juicesharp/rpiv-ask-user-question'\n# A mode is a named set of the layers above. The default keeps its real mode\n# name; it is selected only when neither --major-mode nor DOOMPI_MAJOR_MODE is set.\ndefaultMajorMode: copilot\nmajorMode:\n minimal: [guardrails, team]\n copilot: [guardrails, vibe-lint, team, plan-mode, runner, style-system, goal, ask-user]\n`,\n 'domains.yaml': `# A domain is a named set of plugin directories loaded for a session.\n# Add your own alongside default; aliases map short names onto them.\n# Defaults are a list because one session may combine several domains.\ndefaultDomains: [default]\n#\n# Each plugin path is written in full and is relative to the config that\n# declares it, so it resolves beside this file here (~/.pi/.doom) and against\n# the repository root in a repository copy:\n# plugins: ['plugins/my-plugin']\n# A repository's own .doom/domains.yaml is read after this file, replaces any\n# domain of the same name, and can replace defaultDomains.\ndomains:\n default:\n plugins: []\naliases: {}\n`,\n 'profiles.yaml': 'profiles: {}\\n',\n 'hooks.yaml': 'groups: {}\\n',\n};\n\n/** Result of filling the global seed directory. File arrays contain names in seed order. */\nexport interface GlobalDoomInitResult {\n directory: string;\n created: string[];\n preserved: string[];\n /** Existing files overwritten from the template. Only ever non-empty under `force`. */\n replaced: string[];\n}\n\nexport interface GlobalDoomInitOptions {\n /**\n * Overwrite existing seed files instead of preserving them.\n *\n * Off by default: init is safe to re-run, and hand-edited config is the\n * normal case rather than the exception.\n */\n force?: boolean;\n}\n\nfunction errorCode(error: unknown): string | undefined {\n if (!(error instanceof Error)) return undefined;\n const code = (error as NodeJS.ErrnoException).code;\n return typeof code === 'string' ? code : undefined;\n}\n\nfunction ensureDirectory(directory: string): void {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(directory);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n try {\n fs.mkdirSync(directory, { mode: PRIVATE_DIRECTORY_MODE });\n stats = fs.lstatSync(directory);\n } catch (mkdirError) {\n if (errorCode(mkdirError) !== FILE_ALREADY_EXISTS) throw mkdirError;\n stats = fs.lstatSync(directory);\n }\n }\n\n if (stats.isSymbolicLink()) {\n throw new Error(`Refusing to initialize through symlinked directory: ${directory}`);\n }\n if (!stats.isDirectory()) {\n throw new Error(`Doom config path is not a directory: ${directory}`);\n }\n}\n\nfunction ensureGlobalDoomDirectory(directory: string): void {\n ensureDirectory(path.dirname(directory));\n ensureDirectory(directory);\n}\n\nfunction classifyTarget(targetPath: string): 'missing' | 'file' {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(targetPath);\n } catch (error) {\n if (errorCode(error) === FILE_NOT_FOUND) return 'missing';\n throw error;\n }\n\n if (stats.isFile()) return 'file';\n if (stats.isSymbolicLink()) {\n try {\n if (fs.statSync(targetPath).isFile()) return 'file';\n } catch (error) {\n throw new Error(`Doom config seed symlink is not file-like: ${targetPath}`, { cause: error });\n }\n }\n throw new Error(`Doom config seed path is not file-like: ${targetPath}`);\n}\n\nfunction cleanupTemporaryFile(temporaryPath: string): void {\n try {\n fs.unlinkSync(temporaryPath);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n }\n}\n\nfunction publishExclusively(targetPath: string, content: string): boolean {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', PRIVATE_FILE_MODE);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, PRIVATE_FILE_MODE);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n try {\n fs.linkSync(temporaryPath, targetPath);\n return true;\n } catch (error) {\n if (errorCode(error) === FILE_ALREADY_EXISTS && classifyTarget(targetPath) === 'file') return false;\n throw error;\n }\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Replaces a seed file with the template, atomically.\n *\n * The rename swaps the directory entry in one step, so a reader either sees the\n * whole old file or the whole new one. Renaming onto a symlinked seed replaces\n * the link rather than writing through it.\n */\nfunction publishForcefully(targetPath: string, content: string): void {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', PRIVATE_FILE_MODE);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, PRIVATE_FILE_MODE);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n fs.renameSync(temporaryPath, targetPath);\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Fills missing global seed files.\n *\n * Existing files are left alone so that re-running init never discards\n * hand-edited config; pass `force` to overwrite them from the template.\n */\nexport function initializeGlobalDoomConfig(\n homeDirectory = os.homedir(),\n options: GlobalDoomInitOptions = {},\n): GlobalDoomInitResult {\n const directory = globalDoomConfigDirectory(homeDirectory);\n ensureGlobalDoomDirectory(directory);\n\n const created: string[] = [];\n const preserved: string[] = [];\n const replaced: string[] = [];\n for (const fileName of GLOBAL_DOOM_SEED_FILES) {\n const targetPath = path.join(directory, fileName);\n const existed = classifyTarget(targetPath) === 'file';\n\n if (options.force) {\n publishForcefully(targetPath, DOOM_CONFIG_TEMPLATES[fileName]);\n (existed ? replaced : created).push(fileName);\n continue;\n }\n if (existed) {\n preserved.push(fileName);\n continue;\n }\n if (publishExclusively(targetPath, DOOM_CONFIG_TEMPLATES[fileName])) created.push(fileName);\n else preserved.push(fileName);\n }\n\n return { directory, created, preserved, replaced };\n}\n"],"mappings":"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"}
|
package/dist/init.d.cts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.cts","names":[],"sources":["../src/init.ts"],"mappings":";;cAca;KAQR,6BAA6B;;cAGrB,uBAAuB,SAAS,OAAO;;UAiFnC;EACf;EACA;EACA;;EAEA;;UAGe;;;;;;;EAOf;;;;;;;;iBAkIc,2BACd,wBACA,UAAS,wBACR"}
|
package/dist/init.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.mts","names":[],"sources":["../src/init.ts"],"mappings":";;cAca;KAQR,6BAA6B;;cAGrB,uBAAuB,SAAS,OAAO;;UAiFnC;EACf;EACA;EACA;;EAEA;;UAGe;;;;;;;EAOf;;;;;;;;iBAkIc,2BACd,wBACA,UAAS,wBACR"}
|
package/dist/init.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"init.mjs","names":[],"sources":["../src/init.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from './config.ts';\n\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\nconst UTF8_ENCODING = 'utf8';\nconst FILE_ALREADY_EXISTS = 'EEXIST';\nconst FILE_NOT_FOUND = 'ENOENT';\n\n/** The global seed files are processed in this fixed order. */\nexport const GLOBAL_DOOM_SEED_FILES = [\n 'config.yaml',\n 'modes.yaml',\n 'domains.yaml',\n 'profiles.yaml',\n 'hooks.yaml',\n] as const;\n\ntype GlobalDoomSeedFile = (typeof GLOBAL_DOOM_SEED_FILES)[number];\n\n/** Portable defaults for a new global Doom configuration directory. */\nexport const DOOM_CONFIG_TEMPLATES: Readonly<Record<GlobalDoomSeedFile, string>> = {\n 'config.yaml': `projectTrust: ask\n\n# Autonomous voice is opt-in and uses an explicitly configured Pi model.\n# voice:\n# autoCapture:\n# model: provider/model-id\n# startPhrases:\n# - hey doom\n# stopPhrases:\n# - stop speaking\n# tts:\n# engine: macos-say\n# voice: Samantha\n# rate: 190\n`,\n 'modes.yaml': `# Seeded defaults. Every layer here resolves from what Doom Pi installs or\n# from a public package, so a fresh install works with no extra setup.\n#\n# Layers and modes defined here apply to every repository. A repository's own\n# .doom/modes.yaml is read after this file and replaces any layer or mode of the\n# same name, so a repository can override one entry without restating the rest.\n#\n# A layer package is an npm specifier, or a path if it was never published:\n# packages: ['./extensions/my-ext']\n# A path is relative to the config that declares it, so it resolves beside this\n# file here (~/.pi/.doom) and against the repository root in a repository copy.\nlayers:\n guardrails:\n # repositoryHooks ships with Doom Pi, so the layer steers the agent toward\n # the governing skill and scaffolding before it writes.\n extensions: [repositoryHooks]\n hookGroups: [guardrails, scaffolding]\n vibe-lint:\n packages:\n - '@agimon-ai/vibe-lint/extensions/pi'\n team:\n packages:\n - '@agimon-ai/doompi-team/extensions/doom'\n plan-mode:\n packages:\n - '@agimon-ai/doompi-plan/extensions/doom'\n runner:\n packages:\n - '@agimon-ai/doompi-runner/extensions/pi'\n style-system:\n extensions: [styleSystem]\n goal:\n packages:\n - '@narumitw/pi-goal/src/index.ts'\n ask-user:\n packages:\n - '@juicesharp/rpiv-ask-user-question'\n# A mode is a named set of the layers above. The default keeps its real mode\n# name; it is selected only when neither --major-mode nor DOOMPI_MAJOR_MODE is set.\ndefaultMajorMode: copilot\nmajorMode:\n minimal: [guardrails, team]\n copilot: [guardrails, vibe-lint, team, plan-mode, runner, style-system, goal, ask-user]\n`,\n 'domains.yaml': `# A domain is a named set of plugin directories loaded for a session.\n# Add your own alongside default; aliases map short names onto them.\n# Defaults are a list because one session may combine several domains.\ndefaultDomains: [default]\n#\n# Each plugin path is written in full and is relative to the config that\n# declares it, so it resolves beside this file here (~/.pi/.doom) and against\n# the repository root in a repository copy:\n# plugins: ['plugins/my-plugin']\n# A repository's own .doom/domains.yaml is read after this file, replaces any\n# domain of the same name, and can replace defaultDomains.\ndomains:\n default:\n plugins: []\naliases: {}\n`,\n 'profiles.yaml': 'profiles: {}\\n',\n 'hooks.yaml': 'groups: {}\\n',\n};\n\n/** Result of filling the global seed directory. File arrays contain names in seed order. */\nexport interface GlobalDoomInitResult {\n directory: string;\n created: string[];\n preserved: string[];\n /** Existing files overwritten from the template. Only ever non-empty under `force`. */\n replaced: string[];\n}\n\nexport interface GlobalDoomInitOptions {\n /**\n * Overwrite existing seed files instead of preserving them.\n *\n * Off by default: init is safe to re-run, and hand-edited config is the\n * normal case rather than the exception.\n */\n force?: boolean;\n}\n\nfunction errorCode(error: unknown): string | undefined {\n if (!(error instanceof Error)) return undefined;\n const code = (error as NodeJS.ErrnoException).code;\n return typeof code === 'string' ? code : undefined;\n}\n\nfunction ensureDirectory(directory: string): void {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(directory);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n try {\n fs.mkdirSync(directory, { mode: PRIVATE_DIRECTORY_MODE });\n stats = fs.lstatSync(directory);\n } catch (mkdirError) {\n if (errorCode(mkdirError) !== FILE_ALREADY_EXISTS) throw mkdirError;\n stats = fs.lstatSync(directory);\n }\n }\n\n if (stats.isSymbolicLink()) {\n throw new Error(`Refusing to initialize through symlinked directory: ${directory}`);\n }\n if (!stats.isDirectory()) {\n throw new Error(`Doom config path is not a directory: ${directory}`);\n }\n}\n\nfunction ensureGlobalDoomDirectory(directory: string): void {\n ensureDirectory(path.dirname(directory));\n ensureDirectory(directory);\n}\n\nfunction classifyTarget(targetPath: string): 'missing' | 'file' {\n let stats: fs.Stats;\n try {\n stats = fs.lstatSync(targetPath);\n } catch (error) {\n if (errorCode(error) === FILE_NOT_FOUND) return 'missing';\n throw error;\n }\n\n if (stats.isFile()) return 'file';\n if (stats.isSymbolicLink()) {\n try {\n if (fs.statSync(targetPath).isFile()) return 'file';\n } catch (error) {\n throw new Error(`Doom config seed symlink is not file-like: ${targetPath}`, { cause: error });\n }\n }\n throw new Error(`Doom config seed path is not file-like: ${targetPath}`);\n}\n\nfunction cleanupTemporaryFile(temporaryPath: string): void {\n try {\n fs.unlinkSync(temporaryPath);\n } catch (error) {\n if (errorCode(error) !== FILE_NOT_FOUND) throw error;\n }\n}\n\nfunction publishExclusively(targetPath: string, content: string): boolean {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', PRIVATE_FILE_MODE);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, PRIVATE_FILE_MODE);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n try {\n fs.linkSync(temporaryPath, targetPath);\n return true;\n } catch (error) {\n if (errorCode(error) === FILE_ALREADY_EXISTS && classifyTarget(targetPath) === 'file') return false;\n throw error;\n }\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Replaces a seed file with the template, atomically.\n *\n * The rename swaps the directory entry in one step, so a reader either sees the\n * whole old file or the whole new one. Renaming onto a symlinked seed replaces\n * the link rather than writing through it.\n */\nfunction publishForcefully(targetPath: string, content: string): void {\n const temporaryPath = `${targetPath}.${process.pid}.${randomUUID()}.tmp`;\n let fileDescriptor: number | undefined;\n try {\n fileDescriptor = fs.openSync(temporaryPath, 'wx', PRIVATE_FILE_MODE);\n fs.writeFileSync(fileDescriptor, content, UTF8_ENCODING);\n fs.fchmodSync(fileDescriptor, PRIVATE_FILE_MODE);\n fs.fsyncSync(fileDescriptor);\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n\n fs.renameSync(temporaryPath, targetPath);\n } finally {\n if (fileDescriptor !== undefined) {\n const descriptorToClose = fileDescriptor;\n fileDescriptor = undefined;\n fs.closeSync(descriptorToClose);\n }\n cleanupTemporaryFile(temporaryPath);\n }\n}\n\n/**\n * Fills missing global seed files.\n *\n * Existing files are left alone so that re-running init never discards\n * hand-edited config; pass `force` to overwrite them from the template.\n */\nexport function initializeGlobalDoomConfig(\n homeDirectory = os.homedir(),\n options: GlobalDoomInitOptions = {},\n): GlobalDoomInitResult {\n const directory = globalDoomConfigDirectory(homeDirectory);\n ensureGlobalDoomDirectory(directory);\n\n const created: string[] = [];\n const preserved: string[] = [];\n const replaced: string[] = [];\n for (const fileName of GLOBAL_DOOM_SEED_FILES) {\n const targetPath = path.join(directory, fileName);\n const existed = classifyTarget(targetPath) === 'file';\n\n if (options.force) {\n publishForcefully(targetPath, DOOM_CONFIG_TEMPLATES[fileName]);\n (existed ? replaced : created).push(fileName);\n continue;\n }\n if (existed) {\n preserved.push(fileName);\n continue;\n }\n if (publishExclusively(targetPath, DOOM_CONFIG_TEMPLATES[fileName])) created.push(fileName);\n else preserved.push(fileName);\n }\n\n return { directory, created, preserved, replaced };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"layeredConfig.cjs","names":["os","globalDoomConfigDirectory","path","fs","parseYaml"],"sources":["../src/layeredConfig.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { globalDoomConfigDirectory } from './config.ts';\n\n/** Repository-local doom-pi configuration directory, committed to git. */\nexport const DOOM_DIR = '.doom';\n\nconst TEXT_ENCODING = 'utf8';\n\n/** One `.doom` document plus the root its relative paths resolve against. */\nexport interface DoomConfigSource<TDocument> {\n /** Absolute path of the file the document was read from. */\n filePath: string;\n /**\n * Where relative paths inside this document point.\n *\n * A repository document resolves against the repository root, so `plugins/x`\n * keeps meaning the same thing it always has. A global document resolves\n * against the global `.doom` directory, so it can name plugins and extensions\n * that live beside it in `~/.pi/.doom` rather than inside whichever\n * repository happens to be open.\n */\n baseDirectory: string;\n /** Raw file contents, for callers that cache parsed results by source text. */\n text: string;\n document: TDocument;\n}\n\n/**\n * Reads one named `.doom` document from the global and repository config\n * directories, lowest precedence first.\n *\n * A missing or empty file contributes nothing, so either location on its own is\n * a complete configuration. Documents are deliberately returned unmerged:\n * merging is by name, and only the caller knows what a name means in its own\n * document, so each one folds the sources itself with the repository last.\n */\nexport function doomConfigCandidates(\n fileName: string,\n repoRoot: string,\n homeDirectory: string = os.homedir(),\n): Array<{ filePath: string; baseDirectory: string }> {\n const globalDirectory = globalDoomConfigDirectory(homeDirectory);\n return [\n { filePath: path.join(globalDirectory, fileName), baseDirectory: globalDirectory },\n { filePath: path.join(repoRoot, DOOM_DIR, fileName), baseDirectory: repoRoot },\n ];\n}\n\nexport function readDoomConfigSources<TDocument>(\n fileName: string,\n repoRoot: string,\n homeDirectory: string = os.homedir(),\n): Array<DoomConfigSource<TDocument>> {\n const candidates = doomConfigCandidates(fileName, repoRoot, homeDirectory);\n\n const sources: Array<DoomConfigSource<TDocument>> = [];\n for (const candidate of candidates) {\n if (!fs.existsSync(candidate.filePath)) continue;\n const text = fs.readFileSync(candidate.filePath, TEXT_ENCODING);\n const document = parseYaml(text) as TDocument | null | undefined;\n // An empty file parses to null and is treated as an absent one, so a seeded\n // placeholder never has to be deleted to get out of the way.\n if (document === null || document === undefined) continue;\n sources.push({ ...candidate, text, document });\n }\n return sources;\n}\n\n/**\n * Merges same-named records across sources, tagging each entry with the base\n * directory of the document that supplied it.\n *\n * Later sources win outright rather than being deep-merged: a repository that\n * redefines a name is replacing it, and a half-inherited definition whose paths\n * straddle two roots would be impossible to resolve.\n */\nexport function mergeNamedSources<TDocument, TEntry>(\n sources: ReadonlyArray<DoomConfigSource<TDocument>>,\n select: (document: TDocument) => Record<string, TEntry> | undefined,\n): Record<string, TEntry & { baseDirectory: string }> {\n const merged: Record<string, TEntry & { baseDirectory: string }> = {};\n for (const source of sources) {\n for (const [name, entry] of Object.entries(select(source.document) ?? {})) {\n merged[name] = { ...entry, baseDirectory: source.baseDirectory };\n }\n }\n return merged;\n}\n"],"mappings":"uSAOA,MAAa,EAAW,QAgCxB,SAAgB,EACd,EACA,EACA,EAAwBA,EAAAA,QAAG,QAAQ,EACiB,CACpD,IAAM,EAAkBC,EAAAA,0BAA0B,CAAa,EAC/D,MAAO,CACL,CAAE,SAAUC,EAAAA,QAAK,KAAK,EAAiB,CAAQ,EAAG,cAAe,CAAgB,EACjF,CAAE,SAAUA,EAAAA,QAAK,KAAK,EAAU,EAAU,CAAQ,EAAG,cAAe,CAAS,CAC/E,CACF,CAEA,SAAgB,EACd,EACA,EACA,EAAwBF,EAAAA,QAAG,QAAQ,EACC,CACpC,IAAM,EAAa,EAAqB,EAAU,EAAU,CAAa,EAEnE,EAA8C,CAAC,EACrD,IAAK,IAAM,KAAa,EAAY,CAClC,GAAI,CAACG,EAAAA,QAAG,WAAW,EAAU,QAAQ,EAAG,SACxC,IAAM,EAAOA,EAAAA,QAAG,aAAa,EAAU,SAAU,MAAa,EACxD,GAAA,EAAWC,EAAAA,MAAAA,CAAU,CAAI,EAG3B,GAAa,MACjB,EAAQ,KAAK,CAAE,GAAG,EAAW,OAAM,UAAS,CAAC,CAC/C,CACA,OAAO,CACT,CAUA,SAAgB,EACd,EACA,EACoD,CACpD,IAAM,EAA6D,CAAC,EACpE,IAAK,IAAM,KAAU,EACnB,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,EAAO,EAAO,QAAQ,GAAK,CAAC,CAAC,EACtE,EAAO,GAAQ,CAAE,GAAG,EAAO,cAAe,EAAO,aAAc,EAGnE,OAAO,CACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"layeredConfig.d.cts","names":[],"sources":["../src/layeredConfig.ts"],"mappings":";;cAOa;;UAKI,iBAAiB;;EAEhC;;;;;;;;;;EAUA;;EAEA;EACA,UAAU;;;;;;;;;;;iBAYI,qBACd,kBACA,kBACA,yBACC;EAAQ;EAAkB;;iBAQb,sBAAsB,WACpC,kBACA,kBACA,yBACC,MAAM,iBAAiB;;;;;;;;;iBAwBV,kBAAkB,WAAW,QAC3C,SAAS,cAAc,iBAAiB,aACxC,SAAS,UAAU,cAAc,eAAe,sBAC/C,eAAe;EAAW"}
|