@agimon-ai/doompi 0.0.1-alpha.77 → 0.0.1-alpha.79

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.
@@ -15,7 +15,7 @@ function dispatcherManifest() {
15
15
  name: _agimon_ai_doompi_core_doom_package.DOOM_PACKAGE_NAME,
16
16
  private: true,
17
17
  type: "module",
18
- doompiDispatcher: 3,
18
+ doompiDispatcher: 1,
19
19
  pi: { extensions: [`./${DISPATCHER_ENTRY}`] }
20
20
  }, null, 2)}\n`;
21
21
  }
@@ -142,29 +142,25 @@ function managedManifestVersion(directory) {
142
142
  }
143
143
  }
144
144
  function managedManifest(directory) {
145
- return managedManifestVersion(directory) === 3;
145
+ return managedManifestVersion(directory) === 1;
146
146
  }
147
- function upgradeableManagedManifest(directory) {
148
- const version = managedManifestVersion(directory);
149
- return version !== void 0 && version > 0 && version <= 3;
150
- }
151
- /** Whether init's dispatcher package has a supported protocol and complete entry. */
147
+ /** Whether init's dispatcher package has the expected generated content. */
152
148
  function piExtensionDispatcherIsCurrent(piDirectory) {
153
149
  const directory = piExtensionDispatcherPath(piDirectory);
154
150
  const stat = node_fs.default.lstatSync(directory, { throwIfNoEntry: false });
155
151
  if (!stat?.isDirectory() || stat.isSymbolicLink() || !managedManifest(directory)) return false;
156
- return node_fs.default.lstatSync(node_path.default.join(directory, DISPATCHER_ENTRY), { throwIfNoEntry: false })?.isFile() === true;
152
+ try {
153
+ return node_fs.default.readFileSync(node_path.default.join(directory, PACKAGE_MANIFEST), "utf8") === dispatcherManifest() && node_fs.default.readFileSync(node_path.default.join(directory, DISPATCHER_ENTRY), "utf8") === dispatcherSource();
154
+ } catch {
155
+ return false;
156
+ }
157
157
  }
158
158
  /** Whether an existing managed dispatcher is stale but safe to upgrade in place. */
159
159
  function piExtensionDispatcherIsUpgradeable(piDirectory) {
160
160
  const directory = piExtensionDispatcherPath(piDirectory);
161
161
  const stat = node_fs.default.lstatSync(directory, { throwIfNoEntry: false });
162
162
  if (!stat?.isDirectory() || stat.isSymbolicLink()) return false;
163
- return upgradeableManagedManifest(directory) && !managedManifest(directory);
164
- }
165
- /** Installed protocol version of the dispatcher package, if it is DoomPi-owned. */
166
- function piExtensionDispatcherVersion(piDirectory) {
167
- return managedManifestVersion(piExtensionDispatcherPath(piDirectory));
163
+ return managedManifest(directory) && !piExtensionDispatcherIsCurrent(piDirectory);
168
164
  }
169
165
  function legacyLinkIsManaged(linkPath) {
170
166
  try {
@@ -180,7 +176,7 @@ function writePiExtensionDispatcher(piDirectory) {
180
176
  if (current?.isSymbolicLink()) {
181
177
  if (!legacyLinkIsManaged(directory)) throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);
182
178
  node_fs.default.rmSync(directory, { force: true });
183
- } else if (current && (!current.isDirectory() || !upgradeableManagedManifest(directory))) throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);
179
+ } else if (current && (!current.isDirectory() || !managedManifest(directory))) throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);
184
180
  const scope = node_path.default.dirname(directory);
185
181
  const scopeStat = node_fs.default.lstatSync(scope, { throwIfNoEntry: false });
186
182
  if (scopeStat && !scopeStat.isDirectory()) throw new Error(`Refusing to use unmanaged Pi extension scope: ${scope}`);
@@ -199,7 +195,6 @@ function writePiExtensionDispatcher(piDirectory) {
199
195
  exports.piExtensionDispatcherIsCurrent = piExtensionDispatcherIsCurrent;
200
196
  exports.piExtensionDispatcherIsUpgradeable = piExtensionDispatcherIsUpgradeable;
201
197
  exports.piExtensionDispatcherPath = piExtensionDispatcherPath;
202
- exports.piExtensionDispatcherVersion = piExtensionDispatcherVersion;
203
198
  exports.writePiExtensionDispatcher = writePiExtensionDispatcher;
204
199
 
205
200
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["DOOM_PACKAGE_NAME","SYNC_REGISTRATION_VERSION","LEGACY_SYNC_REGISTRATION_VERSION","DOOMPI_API_VERSION","path","fs","manifestName"],"sources":["../../../../src/builders/cli/piExtensionDispatcher/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { DOOM_PACKAGE_NAME, manifestName } from '@agimon-ai/doompi-core/doom-package';\nimport { writeFileAtomic } from '@agimon-ai/doompi-core/runtime-json';\nimport {\n DOOMPI_API_VERSION,\n LEGACY_SYNC_REGISTRATION_VERSION,\n SYNC_REGISTRATION_VERSION,\n} from '@agimon-ai/doompi-core/sync-registration';\n\n/** Protocol marker proving that the user package path is managed by DoomPi init. */\nexport const PI_DISPATCHER_VERSION = 3;\n\nconst DISPATCHER_ENTRY = 'dispatcher.mjs';\nconst PACKAGE_MANIFEST = 'package.json';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nfunction dispatcherManifest(): string {\n return `${JSON.stringify(\n {\n name: DOOM_PACKAGE_NAME,\n private: true,\n type: 'module',\n doompiDispatcher: PI_DISPATCHER_VERSION,\n pi: { extensions: [`./${DISPATCHER_ENTRY}`] },\n },\n null,\n 2,\n )}\\n`;\n}\n\nfunction dispatcherSource(): string {\n return `import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nconst REGISTRATION_VERSION = ${String(SYNC_REGISTRATION_VERSION)};\nconst LEGACY_REGISTRATION_VERSION = ${String(LEGACY_SYNC_REGISTRATION_VERSION)};\nconst API_VERSION = ${String(DOOMPI_API_VERSION)};\nconst PACKAGE_NAME = ${JSON.stringify(DOOM_PACKAGE_NAME)};\nconst WARNING = 'warning';\n\nfunction canonical(target) {\n return fs.realpathSync.native(path.resolve(target));\n}\n\nfunction isFile(target) {\n try { return fs.statSync(target).isFile(); } catch { return false; }\n}\n\nfunction isDirectory(target) {\n try { return fs.statSync(target).isDirectory(); } catch { return false; }\n}\n\nfunction repositoryRoot(start) {\n let directory = path.resolve(start);\n while (true) {\n const git = path.join(directory, '.git');\n if (isDirectory(path.join(directory, '.doom')) || isFile(path.join(directory, '.pi', 'settings.json')) || isDirectory(git) || isFile(git)) return canonical(directory);\n const parent = path.dirname(directory);\n if (parent === directory) return undefined;\n directory = parent;\n }\n}\n\nfunction gitDirectory(root) {\n const target = path.join(root, '.git');\n try {\n const stat = fs.statSync(target);\n if (stat.isDirectory()) return canonical(target);\n if (!stat.isFile()) return undefined;\n const match = /^gitdir:\\\\s*(.+)$/imu.exec(fs.readFileSync(target, 'utf8').trim());\n return match?.[1] ? canonical(path.resolve(root, match[1].trim())) : undefined;\n } catch { return undefined; }\n}\n\nfunction commonDirectory(root) {\n const git = gitDirectory(root);\n if (!git) return undefined;\n try {\n const relative = fs.readFileSync(path.join(git, 'commondir'), 'utf8').trim();\n return relative ? canonical(path.resolve(git, relative)) : git;\n } catch { return git; }\n}\n\nfunction identity(root) {\n const common = commonDirectory(root);\n const token = common ? \\`git:\\${common}\\` : \\`root:\\${root}\\`;\n const hash = (value) => crypto.createHash('sha256').update(value, 'utf8').digest('hex').slice(0, 32);\n return { repositoryId: hash(token), worktreeId: hash(\\`\\${token}\\\\0worktree:\\${root}\\`) };\n}\n\nfunction inside(directory, target) {\n const relative = path.relative(canonical(directory), canonical(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction registration(root) {\n const ids = identity(root);\n const recordPath = path.join(os.homedir(), '.pi', '.doom', 'sync', 'registrations', ids.repositoryId, \\`\\${ids.worktreeId}.json\\`);\n const value = JSON.parse(fs.readFileSync(recordPath, 'utf8'));\n if ((value.version !== REGISTRATION_VERSION && value.version !== LEGACY_REGISTRATION_VERSION) || canonical(value.root) !== root) throw new Error('registration identity mismatch');\n if (value.identity?.repositoryId !== ids.repositoryId || value.identity?.worktreeId !== ids.worktreeId) throw new Error('registration worktree mismatch');\n if (!inside(value.generationRoot, value.statePath) || !inside(value.package.root, value.package.entry)) throw new Error('registration path escapes its owner');\n const stateHash = crypto.createHash('sha256').update(fs.readFileSync(value.statePath)).digest('hex');\n if (stateHash !== value.stateSha256) throw new Error('registration state hash mismatch');\n const manifestPath = path.join(canonical(value.package.root), 'package.json');\n if (canonical(value.package.manifestPath) !== canonical(manifestPath)) throw new Error('registration package manifest mismatch');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n if (manifest.name !== PACKAGE_NAME || typeof manifest.version !== 'string') throw new Error('registration package mismatch');\n if (value.version === LEGACY_REGISTRATION_VERSION && value.package.apiVersion === undefined) {\n if (manifest.version !== value.package.version) throw new Error('registration package mismatch');\n } else {\n if (!Number.isSafeInteger(value.package.apiVersion) || value.package.apiVersion < 1) throw new Error('invalid package API version');\n if (manifest.doompiApiVersion !== value.package.apiVersion) throw new Error('registration package API mismatch');\n if (value.package.apiVersion !== API_VERSION) throw new Error('unsupported package API version');\n }\n const entries = manifest.pi?.extensions;\n if (!Array.isArray(entries) || !entries.some((entry) => typeof entry === 'string' && canonical(path.resolve(value.package.root, entry)) === canonical(value.package.entry))) throw new Error('registration package entry mismatch');\n return value;\n}\n\nfunction report(pi, target, message) {\n pi.on('session_start', (_event, context) => context.ui.notify(\\`doompi could not load \\${target}: \\${message}. Run doompi init, then doompi sync.\\`, WARNING));\n}\nexport default async function doompiDispatcher(pi) {\n const repository = repositoryRoot(process.cwd());\n try {\n const root = repository ?? canonical(path.join(os.homedir(), '.pi', '.doom'));\n const record = registration(root);\n const loaded = await import(pathToFileURL(record.package.entry).href);\n if (typeof loaded.default !== 'function') throw new Error('recorded package entry has no extension factory');\n await loaded.default(pi);\n } catch (error) {\n report(pi, repository ? 'this repository' : 'the global composition', error instanceof Error ? error.message : String(error));\n }\n}\n`;\n}\n\n/** Stable path Pi derives from the user settings package spelling. */\nexport function piExtensionDispatcherPath(piDirectory: string): string {\n return path.join(piDirectory, ...DOOM_PACKAGE_NAME.split('/'));\n}\n\nfunction managedManifestVersion(directory: string): number | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(directory, PACKAGE_MANIFEST), 'utf8')) as {\n name?: unknown;\n doompiDispatcher?: unknown;\n };\n return parsed.name === DOOM_PACKAGE_NAME && Number.isSafeInteger(parsed.doompiDispatcher)\n ? (parsed.doompiDispatcher as number)\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction managedManifest(directory: string): boolean {\n return managedManifestVersion(directory) === PI_DISPATCHER_VERSION;\n}\n\nfunction upgradeableManagedManifest(directory: string): boolean {\n const version = managedManifestVersion(directory);\n return version !== undefined && version > 0 && version <= PI_DISPATCHER_VERSION;\n}\n\n/** Whether init's dispatcher package has a supported protocol and complete entry. */\nexport function piExtensionDispatcherIsCurrent(piDirectory: string): boolean {\n const directory = piExtensionDispatcherPath(piDirectory);\n const stat = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (!stat?.isDirectory() || stat.isSymbolicLink() || !managedManifest(directory)) return false;\n return fs.lstatSync(path.join(directory, DISPATCHER_ENTRY), { throwIfNoEntry: false })?.isFile() === true;\n}\n\n/** Whether an existing managed dispatcher is stale but safe to upgrade in place. */\nexport function piExtensionDispatcherIsUpgradeable(piDirectory: string): boolean {\n const directory = piExtensionDispatcherPath(piDirectory);\n const stat = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (!stat?.isDirectory() || stat.isSymbolicLink()) return false;\n return upgradeableManagedManifest(directory) && !managedManifest(directory);\n}\n\n/** Installed protocol version of the dispatcher package, if it is DoomPi-owned. */\nexport function piExtensionDispatcherVersion(piDirectory: string): number | undefined {\n return managedManifestVersion(piExtensionDispatcherPath(piDirectory));\n}\n\nfunction legacyLinkIsManaged(linkPath: string): boolean {\n try {\n return manifestName(fs.realpathSync(linkPath)) === DOOM_PACKAGE_NAME;\n } catch {\n return true;\n }\n}\n\n/** Installs or repairs the init-owned dependency-free dispatcher package. */\nexport function writePiExtensionDispatcher(piDirectory: string): string {\n const directory = piExtensionDispatcherPath(piDirectory);\n const current = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (current?.isSymbolicLink()) {\n if (!legacyLinkIsManaged(directory)) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n fs.rmSync(directory, { force: true });\n } else if (current && (!current.isDirectory() || !upgradeableManagedManifest(directory))) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n\n const scope = path.dirname(directory);\n const scopeStat = fs.lstatSync(scope, { throwIfNoEntry: false });\n if (scopeStat && !scopeStat.isDirectory()) {\n throw new Error(`Refusing to use unmanaged Pi extension scope: ${scope}`);\n }\n fs.mkdirSync(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n fs.chmodSync(directory, PRIVATE_DIRECTORY_MODE);\n writeFileAtomic(path.join(directory, PACKAGE_MANIFEST), dispatcherManifest());\n writeFileAtomic(path.join(directory, DISPATCHER_ENTRY), dispatcherSource());\n fs.chmodSync(path.join(directory, PACKAGE_MANIFEST), PRIVATE_FILE_MODE);\n fs.chmodSync(path.join(directory, DISPATCHER_ENTRY), PRIVATE_FILE_MODE);\n return directory;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["DOOM_PACKAGE_NAME","SYNC_REGISTRATION_VERSION","LEGACY_SYNC_REGISTRATION_VERSION","DOOMPI_API_VERSION","path","fs","manifestName"],"sources":["../../../../src/builders/cli/piExtensionDispatcher/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { DOOM_PACKAGE_NAME, manifestName } from '@agimon-ai/doompi-core/doom-package';\nimport { writeFileAtomic } from '@agimon-ai/doompi-core/runtime-json';\nimport {\n DOOMPI_API_VERSION,\n LEGACY_SYNC_REGISTRATION_VERSION,\n SYNC_REGISTRATION_VERSION,\n} from '@agimon-ai/doompi-core/sync-registration';\n\n/** Protocol marker proving that the user package path is managed by DoomPi init. */\nexport const PI_DISPATCHER_VERSION = 1;\n\nconst DISPATCHER_ENTRY = 'dispatcher.mjs';\nconst PACKAGE_MANIFEST = 'package.json';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nfunction dispatcherManifest(): string {\n return `${JSON.stringify(\n {\n name: DOOM_PACKAGE_NAME,\n private: true,\n type: 'module',\n doompiDispatcher: PI_DISPATCHER_VERSION,\n pi: { extensions: [`./${DISPATCHER_ENTRY}`] },\n },\n null,\n 2,\n )}\\n`;\n}\n\nfunction dispatcherSource(): string {\n return `import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nconst REGISTRATION_VERSION = ${String(SYNC_REGISTRATION_VERSION)};\nconst LEGACY_REGISTRATION_VERSION = ${String(LEGACY_SYNC_REGISTRATION_VERSION)};\nconst API_VERSION = ${String(DOOMPI_API_VERSION)};\nconst PACKAGE_NAME = ${JSON.stringify(DOOM_PACKAGE_NAME)};\nconst WARNING = 'warning';\n\nfunction canonical(target) {\n return fs.realpathSync.native(path.resolve(target));\n}\n\nfunction isFile(target) {\n try { return fs.statSync(target).isFile(); } catch { return false; }\n}\n\nfunction isDirectory(target) {\n try { return fs.statSync(target).isDirectory(); } catch { return false; }\n}\n\nfunction repositoryRoot(start) {\n let directory = path.resolve(start);\n while (true) {\n const git = path.join(directory, '.git');\n if (isDirectory(path.join(directory, '.doom')) || isFile(path.join(directory, '.pi', 'settings.json')) || isDirectory(git) || isFile(git)) return canonical(directory);\n const parent = path.dirname(directory);\n if (parent === directory) return undefined;\n directory = parent;\n }\n}\n\nfunction gitDirectory(root) {\n const target = path.join(root, '.git');\n try {\n const stat = fs.statSync(target);\n if (stat.isDirectory()) return canonical(target);\n if (!stat.isFile()) return undefined;\n const match = /^gitdir:\\\\s*(.+)$/imu.exec(fs.readFileSync(target, 'utf8').trim());\n return match?.[1] ? canonical(path.resolve(root, match[1].trim())) : undefined;\n } catch { return undefined; }\n}\n\nfunction commonDirectory(root) {\n const git = gitDirectory(root);\n if (!git) return undefined;\n try {\n const relative = fs.readFileSync(path.join(git, 'commondir'), 'utf8').trim();\n return relative ? canonical(path.resolve(git, relative)) : git;\n } catch { return git; }\n}\n\nfunction identity(root) {\n const common = commonDirectory(root);\n const token = common ? \\`git:\\${common}\\` : \\`root:\\${root}\\`;\n const hash = (value) => crypto.createHash('sha256').update(value, 'utf8').digest('hex').slice(0, 32);\n return { repositoryId: hash(token), worktreeId: hash(\\`\\${token}\\\\0worktree:\\${root}\\`) };\n}\n\nfunction inside(directory, target) {\n const relative = path.relative(canonical(directory), canonical(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction registration(root) {\n const ids = identity(root);\n const recordPath = path.join(os.homedir(), '.pi', '.doom', 'sync', 'registrations', ids.repositoryId, \\`\\${ids.worktreeId}.json\\`);\n const value = JSON.parse(fs.readFileSync(recordPath, 'utf8'));\n if ((value.version !== REGISTRATION_VERSION && value.version !== LEGACY_REGISTRATION_VERSION) || canonical(value.root) !== root) throw new Error('registration identity mismatch');\n if (value.identity?.repositoryId !== ids.repositoryId || value.identity?.worktreeId !== ids.worktreeId) throw new Error('registration worktree mismatch');\n if (!inside(value.generationRoot, value.statePath) || !inside(value.package.root, value.package.entry)) throw new Error('registration path escapes its owner');\n const stateHash = crypto.createHash('sha256').update(fs.readFileSync(value.statePath)).digest('hex');\n if (stateHash !== value.stateSha256) throw new Error('registration state hash mismatch');\n const manifestPath = path.join(canonical(value.package.root), 'package.json');\n if (canonical(value.package.manifestPath) !== canonical(manifestPath)) throw new Error('registration package manifest mismatch');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n if (manifest.name !== PACKAGE_NAME || typeof manifest.version !== 'string') throw new Error('registration package mismatch');\n if (value.version === LEGACY_REGISTRATION_VERSION && value.package.apiVersion === undefined) {\n if (manifest.version !== value.package.version) throw new Error('registration package mismatch');\n } else {\n if (!Number.isSafeInteger(value.package.apiVersion) || value.package.apiVersion < 1) throw new Error('invalid package API version');\n if (manifest.doompiApiVersion !== value.package.apiVersion) throw new Error('registration package API mismatch');\n if (value.package.apiVersion !== API_VERSION) throw new Error('unsupported package API version');\n }\n const entries = manifest.pi?.extensions;\n if (!Array.isArray(entries) || !entries.some((entry) => typeof entry === 'string' && canonical(path.resolve(value.package.root, entry)) === canonical(value.package.entry))) throw new Error('registration package entry mismatch');\n return value;\n}\n\nfunction report(pi, target, message) {\n pi.on('session_start', (_event, context) => context.ui.notify(\\`doompi could not load \\${target}: \\${message}. Run doompi init, then doompi sync.\\`, WARNING));\n}\nexport default async function doompiDispatcher(pi) {\n const repository = repositoryRoot(process.cwd());\n try {\n const root = repository ?? canonical(path.join(os.homedir(), '.pi', '.doom'));\n const record = registration(root);\n const loaded = await import(pathToFileURL(record.package.entry).href);\n if (typeof loaded.default !== 'function') throw new Error('recorded package entry has no extension factory');\n await loaded.default(pi);\n } catch (error) {\n report(pi, repository ? 'this repository' : 'the global composition', error instanceof Error ? error.message : String(error));\n }\n}\n`;\n}\n\n/** Stable path Pi derives from the user settings package spelling. */\nexport function piExtensionDispatcherPath(piDirectory: string): string {\n return path.join(piDirectory, ...DOOM_PACKAGE_NAME.split('/'));\n}\n\nfunction managedManifestVersion(directory: string): number | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(directory, PACKAGE_MANIFEST), 'utf8')) as {\n name?: unknown;\n doompiDispatcher?: unknown;\n };\n return parsed.name === DOOM_PACKAGE_NAME && Number.isSafeInteger(parsed.doompiDispatcher)\n ? (parsed.doompiDispatcher as number)\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction managedManifest(directory: string): boolean {\n return managedManifestVersion(directory) === PI_DISPATCHER_VERSION;\n}\n\n/** Whether init's dispatcher package has the expected generated content. */\nexport function piExtensionDispatcherIsCurrent(piDirectory: string): boolean {\n const directory = piExtensionDispatcherPath(piDirectory);\n const stat = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (!stat?.isDirectory() || stat.isSymbolicLink() || !managedManifest(directory)) return false;\n try {\n return (\n fs.readFileSync(path.join(directory, PACKAGE_MANIFEST), 'utf8') === dispatcherManifest() &&\n fs.readFileSync(path.join(directory, DISPATCHER_ENTRY), 'utf8') === dispatcherSource()\n );\n } catch {\n return false;\n }\n}\n\n/** Whether an existing managed dispatcher is stale but safe to upgrade in place. */\nexport function piExtensionDispatcherIsUpgradeable(piDirectory: string): boolean {\n const directory = piExtensionDispatcherPath(piDirectory);\n const stat = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (!stat?.isDirectory() || stat.isSymbolicLink()) return false;\n return managedManifest(directory) && !piExtensionDispatcherIsCurrent(piDirectory);\n}\n\nfunction legacyLinkIsManaged(linkPath: string): boolean {\n try {\n return manifestName(fs.realpathSync(linkPath)) === DOOM_PACKAGE_NAME;\n } catch {\n return true;\n }\n}\n\n/** Installs or repairs the init-owned dependency-free dispatcher package. */\nexport function writePiExtensionDispatcher(piDirectory: string): string {\n const directory = piExtensionDispatcherPath(piDirectory);\n const current = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (current?.isSymbolicLink()) {\n if (!legacyLinkIsManaged(directory)) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n fs.rmSync(directory, { force: true });\n } else if (current && (!current.isDirectory() || !managedManifest(directory))) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n\n const scope = path.dirname(directory);\n const scopeStat = fs.lstatSync(scope, { throwIfNoEntry: false });\n if (scopeStat && !scopeStat.isDirectory()) {\n throw new Error(`Refusing to use unmanaged Pi extension scope: ${scope}`);\n }\n fs.mkdirSync(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n fs.chmodSync(directory, PRIVATE_DIRECTORY_MODE);\n writeFileAtomic(path.join(directory, PACKAGE_MANIFEST), dispatcherManifest());\n writeFileAtomic(path.join(directory, DISPATCHER_ENTRY), dispatcherSource());\n fs.chmodSync(path.join(directory, PACKAGE_MANIFEST), PRIVATE_FILE_MODE);\n fs.chmodSync(path.join(directory, DISPATCHER_ENTRY), PRIVATE_FILE_MODE);\n return directory;\n}\n"],"mappings":"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"}
@@ -12,7 +12,7 @@ function dispatcherManifest() {
12
12
  name: DOOM_PACKAGE_NAME,
13
13
  private: true,
14
14
  type: "module",
15
- doompiDispatcher: 3,
15
+ doompiDispatcher: 1,
16
16
  pi: { extensions: [`./${DISPATCHER_ENTRY}`] }
17
17
  }, null, 2)}\n`;
18
18
  }
@@ -139,29 +139,25 @@ function managedManifestVersion(directory) {
139
139
  }
140
140
  }
141
141
  function managedManifest(directory) {
142
- return managedManifestVersion(directory) === 3;
142
+ return managedManifestVersion(directory) === 1;
143
143
  }
144
- function upgradeableManagedManifest(directory) {
145
- const version = managedManifestVersion(directory);
146
- return version !== void 0 && version > 0 && version <= 3;
147
- }
148
- /** Whether init's dispatcher package has a supported protocol and complete entry. */
144
+ /** Whether init's dispatcher package has the expected generated content. */
149
145
  function piExtensionDispatcherIsCurrent(piDirectory) {
150
146
  const directory = piExtensionDispatcherPath(piDirectory);
151
147
  const stat = fs.lstatSync(directory, { throwIfNoEntry: false });
152
148
  if (!stat?.isDirectory() || stat.isSymbolicLink() || !managedManifest(directory)) return false;
153
- return fs.lstatSync(path.join(directory, DISPATCHER_ENTRY), { throwIfNoEntry: false })?.isFile() === true;
149
+ try {
150
+ return fs.readFileSync(path.join(directory, PACKAGE_MANIFEST), "utf8") === dispatcherManifest() && fs.readFileSync(path.join(directory, DISPATCHER_ENTRY), "utf8") === dispatcherSource();
151
+ } catch {
152
+ return false;
153
+ }
154
154
  }
155
155
  /** Whether an existing managed dispatcher is stale but safe to upgrade in place. */
156
156
  function piExtensionDispatcherIsUpgradeable(piDirectory) {
157
157
  const directory = piExtensionDispatcherPath(piDirectory);
158
158
  const stat = fs.lstatSync(directory, { throwIfNoEntry: false });
159
159
  if (!stat?.isDirectory() || stat.isSymbolicLink()) return false;
160
- return upgradeableManagedManifest(directory) && !managedManifest(directory);
161
- }
162
- /** Installed protocol version of the dispatcher package, if it is DoomPi-owned. */
163
- function piExtensionDispatcherVersion(piDirectory) {
164
- return managedManifestVersion(piExtensionDispatcherPath(piDirectory));
160
+ return managedManifest(directory) && !piExtensionDispatcherIsCurrent(piDirectory);
165
161
  }
166
162
  function legacyLinkIsManaged(linkPath) {
167
163
  try {
@@ -177,7 +173,7 @@ function writePiExtensionDispatcher(piDirectory) {
177
173
  if (current?.isSymbolicLink()) {
178
174
  if (!legacyLinkIsManaged(directory)) throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);
179
175
  fs.rmSync(directory, { force: true });
180
- } else if (current && (!current.isDirectory() || !upgradeableManagedManifest(directory))) throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);
176
+ } else if (current && (!current.isDirectory() || !managedManifest(directory))) throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);
181
177
  const scope = path.dirname(directory);
182
178
  const scopeStat = fs.lstatSync(scope, { throwIfNoEntry: false });
183
179
  if (scopeStat && !scopeStat.isDirectory()) throw new Error(`Refusing to use unmanaged Pi extension scope: ${scope}`);
@@ -193,6 +189,6 @@ function writePiExtensionDispatcher(piDirectory) {
193
189
  return directory;
194
190
  }
195
191
  //#endregion
196
- export { piExtensionDispatcherIsCurrent, piExtensionDispatcherIsUpgradeable, piExtensionDispatcherPath, piExtensionDispatcherVersion, writePiExtensionDispatcher };
192
+ export { piExtensionDispatcherIsCurrent, piExtensionDispatcherIsUpgradeable, piExtensionDispatcherPath, writePiExtensionDispatcher };
197
193
 
198
194
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/builders/cli/piExtensionDispatcher/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { DOOM_PACKAGE_NAME, manifestName } from '@agimon-ai/doompi-core/doom-package';\nimport { writeFileAtomic } from '@agimon-ai/doompi-core/runtime-json';\nimport {\n DOOMPI_API_VERSION,\n LEGACY_SYNC_REGISTRATION_VERSION,\n SYNC_REGISTRATION_VERSION,\n} from '@agimon-ai/doompi-core/sync-registration';\n\n/** Protocol marker proving that the user package path is managed by DoomPi init. */\nexport const PI_DISPATCHER_VERSION = 3;\n\nconst DISPATCHER_ENTRY = 'dispatcher.mjs';\nconst PACKAGE_MANIFEST = 'package.json';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nfunction dispatcherManifest(): string {\n return `${JSON.stringify(\n {\n name: DOOM_PACKAGE_NAME,\n private: true,\n type: 'module',\n doompiDispatcher: PI_DISPATCHER_VERSION,\n pi: { extensions: [`./${DISPATCHER_ENTRY}`] },\n },\n null,\n 2,\n )}\\n`;\n}\n\nfunction dispatcherSource(): string {\n return `import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nconst REGISTRATION_VERSION = ${String(SYNC_REGISTRATION_VERSION)};\nconst LEGACY_REGISTRATION_VERSION = ${String(LEGACY_SYNC_REGISTRATION_VERSION)};\nconst API_VERSION = ${String(DOOMPI_API_VERSION)};\nconst PACKAGE_NAME = ${JSON.stringify(DOOM_PACKAGE_NAME)};\nconst WARNING = 'warning';\n\nfunction canonical(target) {\n return fs.realpathSync.native(path.resolve(target));\n}\n\nfunction isFile(target) {\n try { return fs.statSync(target).isFile(); } catch { return false; }\n}\n\nfunction isDirectory(target) {\n try { return fs.statSync(target).isDirectory(); } catch { return false; }\n}\n\nfunction repositoryRoot(start) {\n let directory = path.resolve(start);\n while (true) {\n const git = path.join(directory, '.git');\n if (isDirectory(path.join(directory, '.doom')) || isFile(path.join(directory, '.pi', 'settings.json')) || isDirectory(git) || isFile(git)) return canonical(directory);\n const parent = path.dirname(directory);\n if (parent === directory) return undefined;\n directory = parent;\n }\n}\n\nfunction gitDirectory(root) {\n const target = path.join(root, '.git');\n try {\n const stat = fs.statSync(target);\n if (stat.isDirectory()) return canonical(target);\n if (!stat.isFile()) return undefined;\n const match = /^gitdir:\\\\s*(.+)$/imu.exec(fs.readFileSync(target, 'utf8').trim());\n return match?.[1] ? canonical(path.resolve(root, match[1].trim())) : undefined;\n } catch { return undefined; }\n}\n\nfunction commonDirectory(root) {\n const git = gitDirectory(root);\n if (!git) return undefined;\n try {\n const relative = fs.readFileSync(path.join(git, 'commondir'), 'utf8').trim();\n return relative ? canonical(path.resolve(git, relative)) : git;\n } catch { return git; }\n}\n\nfunction identity(root) {\n const common = commonDirectory(root);\n const token = common ? \\`git:\\${common}\\` : \\`root:\\${root}\\`;\n const hash = (value) => crypto.createHash('sha256').update(value, 'utf8').digest('hex').slice(0, 32);\n return { repositoryId: hash(token), worktreeId: hash(\\`\\${token}\\\\0worktree:\\${root}\\`) };\n}\n\nfunction inside(directory, target) {\n const relative = path.relative(canonical(directory), canonical(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction registration(root) {\n const ids = identity(root);\n const recordPath = path.join(os.homedir(), '.pi', '.doom', 'sync', 'registrations', ids.repositoryId, \\`\\${ids.worktreeId}.json\\`);\n const value = JSON.parse(fs.readFileSync(recordPath, 'utf8'));\n if ((value.version !== REGISTRATION_VERSION && value.version !== LEGACY_REGISTRATION_VERSION) || canonical(value.root) !== root) throw new Error('registration identity mismatch');\n if (value.identity?.repositoryId !== ids.repositoryId || value.identity?.worktreeId !== ids.worktreeId) throw new Error('registration worktree mismatch');\n if (!inside(value.generationRoot, value.statePath) || !inside(value.package.root, value.package.entry)) throw new Error('registration path escapes its owner');\n const stateHash = crypto.createHash('sha256').update(fs.readFileSync(value.statePath)).digest('hex');\n if (stateHash !== value.stateSha256) throw new Error('registration state hash mismatch');\n const manifestPath = path.join(canonical(value.package.root), 'package.json');\n if (canonical(value.package.manifestPath) !== canonical(manifestPath)) throw new Error('registration package manifest mismatch');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n if (manifest.name !== PACKAGE_NAME || typeof manifest.version !== 'string') throw new Error('registration package mismatch');\n if (value.version === LEGACY_REGISTRATION_VERSION && value.package.apiVersion === undefined) {\n if (manifest.version !== value.package.version) throw new Error('registration package mismatch');\n } else {\n if (!Number.isSafeInteger(value.package.apiVersion) || value.package.apiVersion < 1) throw new Error('invalid package API version');\n if (manifest.doompiApiVersion !== value.package.apiVersion) throw new Error('registration package API mismatch');\n if (value.package.apiVersion !== API_VERSION) throw new Error('unsupported package API version');\n }\n const entries = manifest.pi?.extensions;\n if (!Array.isArray(entries) || !entries.some((entry) => typeof entry === 'string' && canonical(path.resolve(value.package.root, entry)) === canonical(value.package.entry))) throw new Error('registration package entry mismatch');\n return value;\n}\n\nfunction report(pi, target, message) {\n pi.on('session_start', (_event, context) => context.ui.notify(\\`doompi could not load \\${target}: \\${message}. Run doompi init, then doompi sync.\\`, WARNING));\n}\nexport default async function doompiDispatcher(pi) {\n const repository = repositoryRoot(process.cwd());\n try {\n const root = repository ?? canonical(path.join(os.homedir(), '.pi', '.doom'));\n const record = registration(root);\n const loaded = await import(pathToFileURL(record.package.entry).href);\n if (typeof loaded.default !== 'function') throw new Error('recorded package entry has no extension factory');\n await loaded.default(pi);\n } catch (error) {\n report(pi, repository ? 'this repository' : 'the global composition', error instanceof Error ? error.message : String(error));\n }\n}\n`;\n}\n\n/** Stable path Pi derives from the user settings package spelling. */\nexport function piExtensionDispatcherPath(piDirectory: string): string {\n return path.join(piDirectory, ...DOOM_PACKAGE_NAME.split('/'));\n}\n\nfunction managedManifestVersion(directory: string): number | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(directory, PACKAGE_MANIFEST), 'utf8')) as {\n name?: unknown;\n doompiDispatcher?: unknown;\n };\n return parsed.name === DOOM_PACKAGE_NAME && Number.isSafeInteger(parsed.doompiDispatcher)\n ? (parsed.doompiDispatcher as number)\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction managedManifest(directory: string): boolean {\n return managedManifestVersion(directory) === PI_DISPATCHER_VERSION;\n}\n\nfunction upgradeableManagedManifest(directory: string): boolean {\n const version = managedManifestVersion(directory);\n return version !== undefined && version > 0 && version <= PI_DISPATCHER_VERSION;\n}\n\n/** Whether init's dispatcher package has a supported protocol and complete entry. */\nexport function piExtensionDispatcherIsCurrent(piDirectory: string): boolean {\n const directory = piExtensionDispatcherPath(piDirectory);\n const stat = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (!stat?.isDirectory() || stat.isSymbolicLink() || !managedManifest(directory)) return false;\n return fs.lstatSync(path.join(directory, DISPATCHER_ENTRY), { throwIfNoEntry: false })?.isFile() === true;\n}\n\n/** Whether an existing managed dispatcher is stale but safe to upgrade in place. */\nexport function piExtensionDispatcherIsUpgradeable(piDirectory: string): boolean {\n const directory = piExtensionDispatcherPath(piDirectory);\n const stat = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (!stat?.isDirectory() || stat.isSymbolicLink()) return false;\n return upgradeableManagedManifest(directory) && !managedManifest(directory);\n}\n\n/** Installed protocol version of the dispatcher package, if it is DoomPi-owned. */\nexport function piExtensionDispatcherVersion(piDirectory: string): number | undefined {\n return managedManifestVersion(piExtensionDispatcherPath(piDirectory));\n}\n\nfunction legacyLinkIsManaged(linkPath: string): boolean {\n try {\n return manifestName(fs.realpathSync(linkPath)) === DOOM_PACKAGE_NAME;\n } catch {\n return true;\n }\n}\n\n/** Installs or repairs the init-owned dependency-free dispatcher package. */\nexport function writePiExtensionDispatcher(piDirectory: string): string {\n const directory = piExtensionDispatcherPath(piDirectory);\n const current = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (current?.isSymbolicLink()) {\n if (!legacyLinkIsManaged(directory)) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n fs.rmSync(directory, { force: true });\n } else if (current && (!current.isDirectory() || !upgradeableManagedManifest(directory))) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n\n const scope = path.dirname(directory);\n const scopeStat = fs.lstatSync(scope, { throwIfNoEntry: false });\n if (scopeStat && !scopeStat.isDirectory()) {\n throw new Error(`Refusing to use unmanaged Pi extension scope: ${scope}`);\n }\n fs.mkdirSync(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n fs.chmodSync(directory, PRIVATE_DIRECTORY_MODE);\n writeFileAtomic(path.join(directory, PACKAGE_MANIFEST), dispatcherManifest());\n writeFileAtomic(path.join(directory, DISPATCHER_ENTRY), dispatcherSource());\n fs.chmodSync(path.join(directory, PACKAGE_MANIFEST), PRIVATE_FILE_MODE);\n fs.chmodSync(path.join(directory, DISPATCHER_ENTRY), PRIVATE_FILE_MODE);\n return directory;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/builders/cli/piExtensionDispatcher/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { DOOM_PACKAGE_NAME, manifestName } from '@agimon-ai/doompi-core/doom-package';\nimport { writeFileAtomic } from '@agimon-ai/doompi-core/runtime-json';\nimport {\n DOOMPI_API_VERSION,\n LEGACY_SYNC_REGISTRATION_VERSION,\n SYNC_REGISTRATION_VERSION,\n} from '@agimon-ai/doompi-core/sync-registration';\n\n/** Protocol marker proving that the user package path is managed by DoomPi init. */\nexport const PI_DISPATCHER_VERSION = 1;\n\nconst DISPATCHER_ENTRY = 'dispatcher.mjs';\nconst PACKAGE_MANIFEST = 'package.json';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nfunction dispatcherManifest(): string {\n return `${JSON.stringify(\n {\n name: DOOM_PACKAGE_NAME,\n private: true,\n type: 'module',\n doompiDispatcher: PI_DISPATCHER_VERSION,\n pi: { extensions: [`./${DISPATCHER_ENTRY}`] },\n },\n null,\n 2,\n )}\\n`;\n}\n\nfunction dispatcherSource(): string {\n return `import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nconst REGISTRATION_VERSION = ${String(SYNC_REGISTRATION_VERSION)};\nconst LEGACY_REGISTRATION_VERSION = ${String(LEGACY_SYNC_REGISTRATION_VERSION)};\nconst API_VERSION = ${String(DOOMPI_API_VERSION)};\nconst PACKAGE_NAME = ${JSON.stringify(DOOM_PACKAGE_NAME)};\nconst WARNING = 'warning';\n\nfunction canonical(target) {\n return fs.realpathSync.native(path.resolve(target));\n}\n\nfunction isFile(target) {\n try { return fs.statSync(target).isFile(); } catch { return false; }\n}\n\nfunction isDirectory(target) {\n try { return fs.statSync(target).isDirectory(); } catch { return false; }\n}\n\nfunction repositoryRoot(start) {\n let directory = path.resolve(start);\n while (true) {\n const git = path.join(directory, '.git');\n if (isDirectory(path.join(directory, '.doom')) || isFile(path.join(directory, '.pi', 'settings.json')) || isDirectory(git) || isFile(git)) return canonical(directory);\n const parent = path.dirname(directory);\n if (parent === directory) return undefined;\n directory = parent;\n }\n}\n\nfunction gitDirectory(root) {\n const target = path.join(root, '.git');\n try {\n const stat = fs.statSync(target);\n if (stat.isDirectory()) return canonical(target);\n if (!stat.isFile()) return undefined;\n const match = /^gitdir:\\\\s*(.+)$/imu.exec(fs.readFileSync(target, 'utf8').trim());\n return match?.[1] ? canonical(path.resolve(root, match[1].trim())) : undefined;\n } catch { return undefined; }\n}\n\nfunction commonDirectory(root) {\n const git = gitDirectory(root);\n if (!git) return undefined;\n try {\n const relative = fs.readFileSync(path.join(git, 'commondir'), 'utf8').trim();\n return relative ? canonical(path.resolve(git, relative)) : git;\n } catch { return git; }\n}\n\nfunction identity(root) {\n const common = commonDirectory(root);\n const token = common ? \\`git:\\${common}\\` : \\`root:\\${root}\\`;\n const hash = (value) => crypto.createHash('sha256').update(value, 'utf8').digest('hex').slice(0, 32);\n return { repositoryId: hash(token), worktreeId: hash(\\`\\${token}\\\\0worktree:\\${root}\\`) };\n}\n\nfunction inside(directory, target) {\n const relative = path.relative(canonical(directory), canonical(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction registration(root) {\n const ids = identity(root);\n const recordPath = path.join(os.homedir(), '.pi', '.doom', 'sync', 'registrations', ids.repositoryId, \\`\\${ids.worktreeId}.json\\`);\n const value = JSON.parse(fs.readFileSync(recordPath, 'utf8'));\n if ((value.version !== REGISTRATION_VERSION && value.version !== LEGACY_REGISTRATION_VERSION) || canonical(value.root) !== root) throw new Error('registration identity mismatch');\n if (value.identity?.repositoryId !== ids.repositoryId || value.identity?.worktreeId !== ids.worktreeId) throw new Error('registration worktree mismatch');\n if (!inside(value.generationRoot, value.statePath) || !inside(value.package.root, value.package.entry)) throw new Error('registration path escapes its owner');\n const stateHash = crypto.createHash('sha256').update(fs.readFileSync(value.statePath)).digest('hex');\n if (stateHash !== value.stateSha256) throw new Error('registration state hash mismatch');\n const manifestPath = path.join(canonical(value.package.root), 'package.json');\n if (canonical(value.package.manifestPath) !== canonical(manifestPath)) throw new Error('registration package manifest mismatch');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n if (manifest.name !== PACKAGE_NAME || typeof manifest.version !== 'string') throw new Error('registration package mismatch');\n if (value.version === LEGACY_REGISTRATION_VERSION && value.package.apiVersion === undefined) {\n if (manifest.version !== value.package.version) throw new Error('registration package mismatch');\n } else {\n if (!Number.isSafeInteger(value.package.apiVersion) || value.package.apiVersion < 1) throw new Error('invalid package API version');\n if (manifest.doompiApiVersion !== value.package.apiVersion) throw new Error('registration package API mismatch');\n if (value.package.apiVersion !== API_VERSION) throw new Error('unsupported package API version');\n }\n const entries = manifest.pi?.extensions;\n if (!Array.isArray(entries) || !entries.some((entry) => typeof entry === 'string' && canonical(path.resolve(value.package.root, entry)) === canonical(value.package.entry))) throw new Error('registration package entry mismatch');\n return value;\n}\n\nfunction report(pi, target, message) {\n pi.on('session_start', (_event, context) => context.ui.notify(\\`doompi could not load \\${target}: \\${message}. Run doompi init, then doompi sync.\\`, WARNING));\n}\nexport default async function doompiDispatcher(pi) {\n const repository = repositoryRoot(process.cwd());\n try {\n const root = repository ?? canonical(path.join(os.homedir(), '.pi', '.doom'));\n const record = registration(root);\n const loaded = await import(pathToFileURL(record.package.entry).href);\n if (typeof loaded.default !== 'function') throw new Error('recorded package entry has no extension factory');\n await loaded.default(pi);\n } catch (error) {\n report(pi, repository ? 'this repository' : 'the global composition', error instanceof Error ? error.message : String(error));\n }\n}\n`;\n}\n\n/** Stable path Pi derives from the user settings package spelling. */\nexport function piExtensionDispatcherPath(piDirectory: string): string {\n return path.join(piDirectory, ...DOOM_PACKAGE_NAME.split('/'));\n}\n\nfunction managedManifestVersion(directory: string): number | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(directory, PACKAGE_MANIFEST), 'utf8')) as {\n name?: unknown;\n doompiDispatcher?: unknown;\n };\n return parsed.name === DOOM_PACKAGE_NAME && Number.isSafeInteger(parsed.doompiDispatcher)\n ? (parsed.doompiDispatcher as number)\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction managedManifest(directory: string): boolean {\n return managedManifestVersion(directory) === PI_DISPATCHER_VERSION;\n}\n\n/** Whether init's dispatcher package has the expected generated content. */\nexport function piExtensionDispatcherIsCurrent(piDirectory: string): boolean {\n const directory = piExtensionDispatcherPath(piDirectory);\n const stat = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (!stat?.isDirectory() || stat.isSymbolicLink() || !managedManifest(directory)) return false;\n try {\n return (\n fs.readFileSync(path.join(directory, PACKAGE_MANIFEST), 'utf8') === dispatcherManifest() &&\n fs.readFileSync(path.join(directory, DISPATCHER_ENTRY), 'utf8') === dispatcherSource()\n );\n } catch {\n return false;\n }\n}\n\n/** Whether an existing managed dispatcher is stale but safe to upgrade in place. */\nexport function piExtensionDispatcherIsUpgradeable(piDirectory: string): boolean {\n const directory = piExtensionDispatcherPath(piDirectory);\n const stat = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (!stat?.isDirectory() || stat.isSymbolicLink()) return false;\n return managedManifest(directory) && !piExtensionDispatcherIsCurrent(piDirectory);\n}\n\nfunction legacyLinkIsManaged(linkPath: string): boolean {\n try {\n return manifestName(fs.realpathSync(linkPath)) === DOOM_PACKAGE_NAME;\n } catch {\n return true;\n }\n}\n\n/** Installs or repairs the init-owned dependency-free dispatcher package. */\nexport function writePiExtensionDispatcher(piDirectory: string): string {\n const directory = piExtensionDispatcherPath(piDirectory);\n const current = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (current?.isSymbolicLink()) {\n if (!legacyLinkIsManaged(directory)) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n fs.rmSync(directory, { force: true });\n } else if (current && (!current.isDirectory() || !managedManifest(directory))) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n\n const scope = path.dirname(directory);\n const scopeStat = fs.lstatSync(scope, { throwIfNoEntry: false });\n if (scopeStat && !scopeStat.isDirectory()) {\n throw new Error(`Refusing to use unmanaged Pi extension scope: ${scope}`);\n }\n fs.mkdirSync(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n fs.chmodSync(directory, PRIVATE_DIRECTORY_MODE);\n writeFileAtomic(path.join(directory, PACKAGE_MANIFEST), dispatcherManifest());\n writeFileAtomic(path.join(directory, DISPATCHER_ENTRY), dispatcherSource());\n fs.chmodSync(path.join(directory, PACKAGE_MANIFEST), PRIVATE_FILE_MODE);\n fs.chmodSync(path.join(directory, DISPATCHER_ENTRY), PRIVATE_FILE_MODE);\n return directory;\n}\n"],"mappings":"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"}
@@ -316,9 +316,8 @@ async function synchronize(args, environment = process.env, currentDirectory = p
316
316
  const agentDirectory = (0, _agimon_ai_doompi_core_runtime_pi_settings.piAgentDirectory)(scopedEnvironment, homeDirectory);
317
317
  if ((commandOptions.settingsMode ?? "persisted") === "persisted" && !check) {
318
318
  if (require_index$7.piExtensionDispatcherIsUpgradeable(agentDirectory)) {
319
- const previousVersion = require_index$7.piExtensionDispatcherVersion(agentDirectory);
320
319
  require_index$8.writePiExtensionAlias(agentDirectory);
321
- output.write(`repair: upgraded Pi user dispatcher from protocol ${String(previousVersion)} to ${String(3)}\n`);
320
+ output.write("repair: refreshed Pi user dispatcher\n");
322
321
  }
323
322
  const drift = piIntegrationDrift(agentDirectory);
324
323
  if (drift.length > 0) throw new Error(`DoomPi Pi integration is not ready:\n${drift.map((entry) => ` ${entry}`).join("\n")}`);
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["path","os","globalDoomConfigDirectory","resolveDoomConfigurationRoot","HARNESS_STATE_POINTER","DOOMPI_MAJOR_MODE_ENV","DOOMPI_DOMAINS_ENV","DOOMPI_PROFILE_ENV","loadDoomConfigLenient","loadMajorModesConfig","createLayerResolvers","resolveExtensionComposition","PERSONA_ENTRY","filterHookDisabledLayers","resolveLayers","piThemeDirectory","DEFAULT_THEME_NAME","readPiSettings","mergePiSettings","serializePiSettings","piExtensionAliasIsCurrent","DEFAULT_THEME","fs","syncStateRootMatches","computeInputsHash","recordResolvedEntries","readBootstrapStatus","piAgentDirectory","projectRegistersDoom","DUPLICATE_REGISTRATION_DRIFT","readSyncDrift","spawnSync","doomPiPackageRoot","DOOMPI_API_VERSION","readSyncRegistration","SYNC_REGISTRATION_VERSION","loadMajorModesConfigLenient","loadDomains","parseHarnessArgs","piExtensionDispatcherIsUpgradeable","piExtensionDispatcherVersion","missingLayerPackageSpecifiers","readLocatedSyncState","SyncProgress","acquireSyncLocationLock","resolveSyncLocation","crypto","syncGenerationDirectory","buildHarnessContext","ensureLayerPackages","SYNC_STATE_VERSION","computeWebSourcesHash","loadHarnessState","buildSyncedRuntime","syncWebBundle","syncServerBundle","DOOM_SERVER_BUNDLE_FILE","computeServerSourcesHash","writeSyncState","writeProjectPiSettings","readMcpServerNames","syncStateSha256"],"sources":["../../../../src/cli/commands/sync/index.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\nimport { loadDomains } from '@agimon-ai/doompi-config/domains';\nimport { filterHookDisabledLayers, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport { loadMajorModesConfig, loadMajorModesConfigLenient } from '@agimon-ai/doompi-config/majorModes';\nimport type { ConfigDiagnostic } from '@agimon-ai/doompi-config/types';\nimport {\n mergePiSettings,\n piAgentDirectory,\n piThemeDirectory,\n readPiSettings,\n serializePiSettings,\n} from '@agimon-ai/doompi-core/runtime-pi-settings';\nimport { DOOM_SERVER_BUNDLE_FILE } from '@agimon-ai/doompi-core/server-facet';\nimport {\n acquireSyncLocationLock,\n resolveSyncLocation,\n syncGenerationDirectory,\n} from '@agimon-ai/doompi-core/sync-location';\nimport {\n DOOMPI_API_VERSION,\n publishSyncRegistration,\n readSyncRegistration,\n SYNC_REGISTRATION_VERSION,\n syncStateSha256,\n type SyncPackageRegistration,\n} from '@agimon-ai/doompi-core/sync-registration';\nimport { DEFAULT_THEME, DEFAULT_THEME_NAME } from '@agimon-ai/doompi-ui/theme';\n\nimport { buildSyncedRuntime } from '../../../builders/cli';\nimport { readBootstrapStatus } from '../../../builders/cli/bootstrapLocator';\nimport {\n createLayerResolvers,\n type ExtensionComposition,\n PERSONA_ENTRY,\n resolveExtensionComposition,\n} from '../../../builders/cli/extensionAssembler';\nimport { buildHarnessContext } from '../../../builders/cli/harnessContext';\nimport {\n doomPiPackageRoot,\n piExtensionAliasIsCurrent,\n writePiExtensionAlias,\n} from '../../../builders/cli/piExtensionAlias';\nimport {\n PI_DISPATCHER_VERSION,\n piExtensionDispatcherIsUpgradeable,\n piExtensionDispatcherVersion,\n} from '../../../builders/cli/piExtensionDispatcher';\nimport {\n DUPLICATE_REGISTRATION_DRIFT,\n projectRegistersDoom,\n writeProjectPiSettings,\n} from '../../../builders/cli/projectSettings';\nimport { syncServerBundle } from '../../../builders/server';\nimport { syncWebBundle } from '../../../builders/web';\nimport { HARNESS_STATE_POINTER, loadHarnessState } from '../../../composition/harnessState';\nimport { ensureLayerPackages, missingLayerPackageSpecifiers } from '../../../composition/layerPackageInstaller';\nimport { loadDoomConfigLenient } from '../../../composition/projectTrust';\nimport { resolveDoomConfigurationRoot } from '../../../composition/repository';\nimport { readSyncDrift } from '../../../composition/syncDrift';\nimport {\n computeInputsHash,\n computeWebSourcesHash,\n computeServerSourcesHash,\n readLocatedSyncState,\n readMcpServerNames,\n recordResolvedEntries,\n SYNC_STATE_VERSION,\n type SyncSelection,\n type SyncState,\n syncStateRootMatches,\n writeSyncState,\n} from '../../../composition/syncState';\nimport type { HarnessOptions } from '../../../composition/types/harness';\nimport { DOOMPI_DOMAINS_ENV, DOOMPI_MAJOR_MODE_ENV, DOOMPI_PROFILE_ENV } from '../../matrixOptions';\nimport { parseHarnessArgs } from '../../options';\nimport { SyncProgress, type SyncProgressOutput } from './presenter';\n\n/**\n * `doom-pi sync`: resolve the matrix once and write it where plain Pi finds it.\n *\n * The doom-emacs split. Everything that needs a real Node process (module\n * resolution, staging skills and agents, generating the MCP config) happens\n * here, and the doom-pi extension then only reads what this produced. The\n * launcher is untouched and keeps resolving the same matrix per run.\n */\n\nconst SYNC_COMMAND = 'sync';\nconst CHECK_OPTION = '--check';\n/** Republishes even when nothing drifted, for a generation suspected of being damaged. */\nconst FORCE_OPTION = '--force';\nconst GLOBAL_OPTION = '--global';\nconst HARNESS_ROOT_ENV = 'DOOMPI_ROOT';\nconst PERSONA_FILE_ENV = 'DOOMPI_PERSONA_FILE';\nconst HOOK_EMITTER = path.join('tools', 'harness', 'emit-hooks.mjs');\nconst NONE = '(none)';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst SYNC_LABEL = 'sync';\nconst RUNTIME_LABEL = 'runtime';\nconst WEB_LABEL = 'web';\nconst API_LABEL = 'api';\n\nexport interface SyncRoots {\n globalOnly: boolean;\n globalRoot: string;\n sourceRoot: string;\n targetRoot: string;\n}\n\n/** Resolves the configuration source and publication destination for one sync. */\nexport function resolveSyncRoots(\n args: readonly string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n homeDirectory = environment.HOME ?? os.homedir(),\n): SyncRoots {\n const globalRoot = globalDoomConfigDirectory(homeDirectory);\n const inheritedRoot = environment[HARNESS_ROOT_ENV];\n const sourceRoot = inheritedRoot\n ? path.resolve(inheritedRoot)\n : resolveDoomConfigurationRoot(currentDirectory, homeDirectory);\n const globalOnly = args.includes(GLOBAL_OPTION);\n return { globalOnly, globalRoot, sourceRoot, targetRoot: globalOnly ? globalRoot : sourceRoot };\n}\n\nconst GLOBAL_SCOPE_INHERITED_KEYS = [\n HARNESS_ROOT_ENV,\n HARNESS_STATE_POINTER,\n DOOMPI_MAJOR_MODE_ENV,\n DOOMPI_DOMAINS_ENV,\n DOOMPI_PROFILE_ENV,\n] as const;\n\n/** Removes workspace-only state before a global runtime is resolved. */\nexport function environmentForSyncScope(environment: NodeJS.ProcessEnv, globalOnly: boolean): NodeJS.ProcessEnv {\n if (!globalOnly) return environment;\n const scoped = { ...environment };\n for (const key of GLOBAL_SCOPE_INHERITED_KEYS) delete scoped[key];\n return scoped;\n}\n\n/**\n * Harness variables worth recording, by prefix or exact name.\n *\n * An allowlist rather than the whole environment: the state file is a snapshot\n * of resolved configuration, and dumping `process.env` into it would write\n * every credential the sync happened to run with onto disk.\n */\nconst RECORDED_PREFIXES = ['DOOMPI_'];\nconst RECORDED_KEYS = ['CLAUDE_PROJECT_DIR', 'CODEX_REPO_ROOT', 'ORIGINAL_REPO_PATH', 'MCP_UI_VIEWER'];\n/**\n * Launcher-only values a synced session must not inherit.\n *\n * The child extension list is recomposed on every load, and the subagent binary\n * points at `pi.sh`, which a session started as plain `pi` should not shell out\n * to: Doom Team resolves Pi's own CLI when the variable is absent.\n */\nconst EXCLUDED_KEYS = new Set([\n 'DOOMPI_CHILD_EXTENSIONS',\n 'DOOMPI_COMPOSED',\n 'DOOMPI_MUTE',\n 'DOOMPI_TEMP_DIR',\n // A pointer to the syncing process's own state file. Recording it would hand\n // every later session a path to a state that died with this one.\n HARNESS_STATE_POINTER,\n 'PI_SUBAGENT_PI_BINARY',\n]);\n\ntype SyncOutput = SyncProgressOutput;\n\nexport type SyncSettingsMode = 'persisted' | 'embedded';\n\nexport interface SyncCommandOptions {\n settingsMode?: SyncSettingsMode;\n /** Test/embedding override; normal CLI execution uses the process home. */\n homeDirectory?: string;\n /** Internal pipeline seam when the caller owns the worktree lock. */\n lockHeld?: boolean;\n}\n\nexport interface SyncResult {\n statePath: string;\n /** Omitted when DPI supplies the integration as a process-local overlay. */\n settingsPath?: string;\n /** Set only when the repository still carried its own DoomPi registration. */\n projectSettingsPath?: string;\n selection: SyncSelection;\n mcpServers: string[];\n skillCount: number;\n agentCount: number;\n}\n\nexport function recordedEnvironment(environment: NodeJS.ProcessEnv): Record<string, string> {\n const recorded: Record<string, string> = {};\n for (const [key, value] of Object.entries(environment)) {\n if (value === undefined || EXCLUDED_KEYS.has(key)) continue;\n if (RECORDED_KEYS.includes(key) || RECORDED_PREFIXES.some((prefix) => key.startsWith(prefix))) {\n recorded[key] = value;\n }\n }\n return recorded;\n}\n\n/**\n * Reports the config keys sync chose to ignore.\n *\n * Never fatal. A key nobody recognises is usually a config written for another\n * version of a layer, and refusing to build over it is worse than proceeding\n * without it. The strict check lives in `doompi doctor`.\n */\nfunction writeConfigDiagnostics(diagnostics: readonly ConfigDiagnostic[], output: SyncOutput): void {\n if (diagnostics.length === 0) return;\n const lines = diagnostics.map((entry) => ` ${entry.filePath}: ${entry.path}`).join('\\n');\n output.write(\n `config: ignored ${String(diagnostics.length)} unsupported key(s); run doompi doctor for the strict check\\n${lines}\\n`,\n );\n}\n/**\n * Layers the repository's declared selection under the usual resolution.\n *\n * `.doom/config.yaml` holds what the repository selects by default, the way\n * init.el does for doom-emacs. Seeding the environment the parser reads keeps\n * the precedence the launcher already documents: an explicit flag wins, then an\n * exported variable, then the declared default.\n */\nexport function selectionEnvironment(\n repoRoot: string,\n environment: NodeJS.ProcessEnv,\n homeDirectory?: string,\n): NodeJS.ProcessEnv {\n const { selection } = loadDoomConfigLenient(repoRoot, homeDirectory).config;\n if (!selection) return environment;\n return {\n ...environment,\n ...(selection.majorMode && !environment[DOOMPI_MAJOR_MODE_ENV]\n ? { [DOOMPI_MAJOR_MODE_ENV]: selection.majorMode }\n : {}),\n ...(selection.profile && !environment[DOOMPI_PROFILE_ENV] ? { [DOOMPI_PROFILE_ENV]: selection.profile } : {}),\n ...(selection.domains && environment[DOOMPI_DOMAINS_ENV] === undefined\n ? { [DOOMPI_DOMAINS_ENV]: selection.domains.join(',') }\n : {}),\n };\n}\n\nexport function toSelection(\n options: Pick<HarnessOptions, 'majorMode' | 'domains' | 'profile' | 'preset'>,\n): SyncSelection {\n return {\n majorMode: options.majorMode,\n domains: options.domains,\n profile: options.profile,\n preset: options.preset,\n };\n}\n\nexport function selectionCompositionFingerprint(\n repoRoot: string,\n options: Pick<HarnessOptions, 'agents' | 'hooks' | 'majorMode' | 'mcp' | 'preset'>,\n homeDirectory: string = os.homedir(),\n): string {\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);\n const resolvers = createLayerResolvers(repoRoot);\n return resolveExtensionComposition({\n agents: options.agents,\n autoStop: false,\n mute: false,\n preset: options.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorMode: options.majorMode,\n layers: filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n ),\n majorModesConfig,\n resolvers,\n }).fingerprint;\n}\n\n/** Settings, dispatcher and theme differences an init would fix, independent of sync state. */\nfunction piIntegrationDrift(agentDirectory: string): string[] {\n const drift: string[] = [];\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const settings = readPiSettings(agentDirectory);\n const merged = mergePiSettings(settings, agentDirectory, { themePath, themeName: DEFAULT_THEME_NAME });\n if (serializePiSettings(merged) !== serializePiSettings(settings)) {\n drift.push('Pi user settings are out of date; run doompi init');\n }\n if (!piExtensionAliasIsCurrent(agentDirectory)) drift.push('Pi user dispatcher is out of date; run doompi init');\n const expectedTheme = `${JSON.stringify(DEFAULT_THEME, null, 2)}\\n`;\n if (!fs.existsSync(themePath) || fs.readFileSync(themePath, 'utf8') !== expectedTheme) {\n drift.push('Pi user theme is out of date; run doompi init');\n }\n return drift;\n}\n\n/** Differences between what a sync would produce and what is on disk. */\nexport function collectDrift(\n repoRoot: string,\n selection: SyncSelection,\n state: SyncState | undefined,\n environment: NodeJS.ProcessEnv = process.env,\n settingsMode: SyncSettingsMode = 'persisted',\n expectedCompositionFingerprint?: string,\n): string[] {\n if (!state) return ['no sync state: run doompi sync'];\n const drift: string[] = [];\n if (syncRegistrationNeedsApiMigration(repoRoot, environment.HOME ?? os.homedir())) {\n drift.push('DoomPi registration needs API migration');\n }\n if (!syncStateRootMatches(repoRoot, state.root)) drift.push('sync state belongs to a different repository');\n const recorded = state.selection;\n if (\n recorded.majorMode !== selection.majorMode ||\n recorded.profile !== selection.profile ||\n recorded.preset !== selection.preset ||\n recorded.domains.join(',') !== selection.domains.join(',')\n ) {\n drift.push('selection changed since the last sync');\n }\n // Hashed against the recorded selection, not the requested one, so a\n // selection change is reported once rather than as two findings.\n if (computeInputsHash(repoRoot, recorded, environment.HOME ?? os.homedir()) !== state.inputsHash) {\n drift.push('.doom configuration changed');\n }\n // Re-resolving is what catches a dependency upgrade moving a package, which\n // the inputs hash deliberately does not read.\n if (\n JSON.stringify(\n recordResolvedEntries(\n loadMajorModesConfig(repoRoot, environment.HOME ?? os.homedir()),\n createLayerResolvers(repoRoot),\n ),\n ) !== JSON.stringify(state.resolved)\n ) {\n drift.push('resolved extension paths changed');\n }\n if (expectedCompositionFingerprint && state.compositionFingerprint !== expectedCompositionFingerprint) {\n drift.push('extension composition changed');\n }\n try {\n if (!readBootstrapStatus(repoRoot, undefined, environment.HOME ?? os.homedir()).fresh) {\n drift.push('precompiled runtime is missing or stale');\n }\n } catch {\n drift.push('precompiled runtime is missing or stale');\n }\n\n if (settingsMode === 'persisted') {\n const agentDirectory = piAgentDirectory(environment);\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n drift.push(...piIntegrationDrift(agentDirectory));\n if (projectRegistersDoom(repoRoot)) drift.push(DUPLICATE_REGISTRATION_DRIFT);\n if (state.baseline.themePath !== themePath || state.baseline.themeName !== DEFAULT_THEME_NAME) {\n drift.push('synced theme location is out of date');\n }\n }\n if (\n readSyncDrift({ repoRoot, homeDirectory: environment.HOME ?? os.homedir() }).reasons.includes('server-bundle-stale')\n ) {\n drift.push('server bundle is missing or stale');\n }\n return drift;\n}\n\n/** Regenerates the hook files the other frontends read before any harness code runs. */\nfunction emitFrontendHooks(repoRoot: string, output: SyncOutput): void {\n const emitter = path.join(repoRoot, HOOK_EMITTER);\n if (!fs.existsSync(emitter)) return;\n const result = spawnSync(process.execPath, [emitter, '--write'], { cwd: repoRoot, encoding: 'utf8' });\n if (result.status === 0) {\n output.write('hooks: regenerated for Claude Code and Codex\\n');\n return;\n }\n output.write(`hooks: emit-hooks failed (${result.stderr?.trim() || `exit ${String(result.status)}`})\\n`);\n}\n\nexport function formatSyncResult(result: SyncResult, runner = 'pi'): string {\n const { selection } = result;\n return [\n `mode: ${selection.majorMode}`,\n `domains: ${selection.domains.join(', ') || NONE}`,\n `profile: ${selection.profile ?? NONE}`,\n `skills: ${result.skillCount}`,\n `agents: ${result.agentCount}`,\n `mcp: ${result.mcpServers.join(', ') || NONE}`,\n `state: ${result.statePath}`,\n ...(result.settingsPath ? [`settings: ${result.settingsPath}`] : []),\n ...(result.projectSettingsPath\n ? [`project: removed duplicate registration from ${result.projectSettingsPath}`]\n : []),\n '',\n `Run ${runner} from the repository root to use it.`,\n '',\n ].join('\\n');\n}\n\n/**\n * The DoomPi that produced this generation, which is the one that can load it.\n *\n * Always the executing package, never another copy the repository happens to\n * install. A generation is not portable between two installations: the bundles\n * are compiled from the building package's own extension entries, the recorded\n * compiler inputs are its files, and the state names its bootstrap entry. Naming\n * a second copy here publishes a registration whose package disagrees with the\n * state it points at, and Pi's dispatcher then loads a harness that rejects the\n * bootstrap as stale on every session, with no sync able to fix it.\n *\n * A repository that wants its own copy to own its sessions runs sync with that\n * copy's CLI, which makes it the executing package.\n */\nfunction packageRegistrationFor(): SyncPackageRegistration {\n const root = fs.realpathSync(doomPiPackageRoot());\n const manifestPath = path.join(root, 'package.json');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as {\n version?: unknown;\n doompiApiVersion?: unknown;\n pi?: { extensions?: unknown };\n };\n const version = manifest.version;\n const apiVersion = manifest.doompiApiVersion;\n const extensions = manifest.pi?.extensions;\n const extension = Array.isArray(extensions) ? extensions.find((value) => typeof value === 'string') : undefined;\n if (\n typeof version !== 'string' ||\n typeof apiVersion !== 'number' ||\n !Number.isSafeInteger(apiVersion) ||\n apiVersion < 1 ||\n apiVersion !== DOOMPI_API_VERSION ||\n typeof extension !== 'string'\n ) {\n throw new Error(`Installed DoomPi package at ${root} has no supported API-versioned Pi extension entry`);\n }\n return {\n root,\n version,\n apiVersion: apiVersion as number,\n manifestPath,\n entry: fs.realpathSync(path.resolve(root, extension)),\n };\n}\n\n/** Returns true when a valid legacy registration must be republished with API metadata. */\nexport function syncRegistrationNeedsApiMigration(repoRoot: string, homeDirectory: string): boolean {\n try {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n return (\n registration !== undefined &&\n (registration.version !== SYNC_REGISTRATION_VERSION || registration.package.apiVersion === undefined)\n );\n } catch {\n return false;\n }\n}\n\n/** Resolves the matrix, stages it into home-scoped worktree storage, and publishes one generation. */\nexport async function synchronize(\n args: string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n output: SyncOutput = process.stdout,\n commandOptions: SyncCommandOptions = {},\n): Promise<number> {\n const check = args.includes(CHECK_OPTION);\n const force = args.includes(FORCE_OPTION);\n const rest = args.slice(1).filter((argument) => ![CHECK_OPTION, FORCE_OPTION, GLOBAL_OPTION].includes(argument));\n const homeDirectory = commandOptions.homeDirectory ?? environment.HOME ?? os.homedir();\n const roots = resolveSyncRoots(args, environment, currentDirectory, homeDirectory);\n const { globalOnly, globalRoot, targetRoot: repoRoot } = roots;\n const scopedEnvironment = environmentForSyncScope(environment, globalOnly);\n if (globalOnly && !check) fs.mkdirSync(globalRoot, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n // Sync tolerates keys it does not recognise so a config written against a\n // different version cannot break a build. `doompi doctor` reports them.\n const modes = loadMajorModesConfigLenient(repoRoot, homeDirectory);\n const configDiagnostics = [...loadDoomConfigLenient(repoRoot, homeDirectory).diagnostics, ...modes.diagnostics];\n const defaultMajorMode = modes.config.defaultMajorMode;\n const defaultDomains = loadDomains(repoRoot, homeDirectory).defaultDomains;\n const parsed = parseHarnessArgs(\n rest,\n selectionEnvironment(repoRoot, scopedEnvironment, homeDirectory),\n globalOnly ? globalRoot : currentDirectory,\n defaultMajorMode,\n defaultDomains,\n );\n const selection = toSelection(parsed.options);\n const agentDirectory = piAgentDirectory(scopedEnvironment, homeDirectory);\n if ((commandOptions.settingsMode ?? 'persisted') === 'persisted' && !check) {\n if (piExtensionDispatcherIsUpgradeable(agentDirectory)) {\n const previousVersion = piExtensionDispatcherVersion(agentDirectory);\n writePiExtensionAlias(agentDirectory);\n output.write(\n `repair: upgraded Pi user dispatcher from protocol ${String(previousVersion)} to ${String(PI_DISPATCHER_VERSION)}\\n`,\n );\n }\n const drift = piIntegrationDrift(agentDirectory);\n if (drift.length > 0) {\n throw new Error(`DoomPi Pi integration is not ready:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}`);\n }\n }\n writeConfigDiagnostics(configDiagnostics, output);\n if (check) {\n const majorModesConfig = modes.config;\n const missingPackages = missingLayerPackageSpecifiers(\n majorModesConfig,\n Object.keys(majorModesConfig.layers),\n createLayerResolvers(repoRoot),\n );\n if (missingPackages.length > 0) {\n output.write(\n `doompi sync is out of date:\\n${missingPackages\n .map((specifier) => ` configured package is not installed: ${specifier}`)\n .join('\\n')}\\n`,\n );\n return 1;\n }\n let located: ReturnType<typeof readLocatedSyncState>;\n try {\n located = readLocatedSyncState(repoRoot, homeDirectory);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n output.write(`doompi sync is out of date:\\n ${detail}\\n`);\n return 1;\n }\n const expectedCompositionFingerprint = selectionCompositionFingerprint(repoRoot, parsed.options, homeDirectory);\n const drift = collectDrift(\n repoRoot,\n selection,\n located?.state,\n scopedEnvironment,\n commandOptions.settingsMode ?? 'persisted',\n expectedCompositionFingerprint,\n );\n if (drift.length === 0) {\n output.write('doompi sync is up to date\\n');\n return 0;\n }\n output.write(`doompi sync is out of date:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}\\n`);\n return 1;\n }\n\n // Publishing an identical generation is not a no-op: it moves the\n // registration, so every attached cockpit reloads and the previous\n // generation becomes garbage. Same inputs, same published result.\n const driftOptions = {\n repoRoot,\n homeDirectory,\n requireWebBundle: Boolean(environment.DOOMPI_WEB_PACKAGE_ROOT),\n };\n const registrationNeedsMigration = syncRegistrationNeedsApiMigration(repoRoot, homeDirectory);\n if (!force && !registrationNeedsMigration && readSyncDrift(driftOptions).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n\n const progress = new SyncProgress(output);\n const releaseLock = commandOptions.lockHeld\n ? undefined\n : await acquireSyncLocationLock(resolveSyncLocation(repoRoot, homeDirectory));\n let result: SyncResult;\n try {\n // A concurrent publisher may have resolved the drift while this command\n // waited for the lock. Avoid moving the registration for no change.\n const registrationNeedsMigration = syncRegistrationNeedsApiMigration(repoRoot, homeDirectory);\n if (!force && !registrationNeedsMigration && readSyncDrift(driftOptions).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n result = await stageSync(repoRoot, parsed.options, scopedEnvironment, homeDirectory, progress, commandOptions);\n } finally {\n await releaseLock?.();\n }\n emitFrontendHooks(repoRoot, output);\n output.write(formatSyncResult(result, (commandOptions.settingsMode ?? 'persisted') === 'embedded' ? 'dpi' : 'pi'));\n return 0;\n}\n\nasync function stageSync(\n repoRoot: string,\n options: Omit<HarnessOptions, 'repoRoot'>,\n environment: NodeJS.ProcessEnv,\n homeDirectory: string,\n progress: SyncProgress,\n commandOptions: SyncCommandOptions = {},\n): Promise<SyncResult> {\n const location = resolveSyncLocation(repoRoot, homeDirectory);\n const generation = `${Date.now().toString(36)}-${crypto.randomUUID()}`;\n const directory = syncGenerationDirectory(location, generation);\n await fs.promises.mkdir(location.generationsDirectory, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n // The leaf is created without `recursive`, so an existing path is an error\n // rather than something to adopt: the cockpit signs and serves whatever the\n // published generation holds, and sync must only ever publish bytes it\n // wrote itself into a directory it just created.\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE });\n\n try {\n const staged = progress.start(SYNC_LABEL, 'resolving the matrix and staging resources');\n const context = await buildHarnessContext({\n ...options,\n repoRoot: location.root,\n homeDirectory,\n cwd: location.root,\n resourceDirectory: directory,\n });\n await ensureLayerPackages({\n repoRoot: location.root,\n config: context.majorModesConfig,\n layers: Object.keys(context.majorModesConfig.layers),\n environment,\n });\n staged(`${String(context.resources.skillCount)} skills, ${String(context.resources.agentCount)} agents`);\n const selection = toSelection(options);\n const resolvers = createLayerResolvers(location.root);\n const resolved = recordResolvedEntries(context.majorModesConfig, resolvers);\n const compositionFingerprint = selectionCompositionFingerprint(location.root, options, homeDirectory);\n const agentDirectory = piAgentDirectory(environment, homeDirectory);\n const persistedThemePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const themePath =\n (commandOptions.settingsMode ?? 'persisted') === 'persisted' ? persistedThemePath : context.defaultThemePath;\n const state: SyncState = {\n version: SYNC_STATE_VERSION,\n root: location.root,\n identity: location.identity,\n inputsHash: computeInputsHash(location.root, selection, homeDirectory),\n webSourcesHash: computeWebSourcesHash(resolved),\n compositionFingerprint,\n selection,\n env: recordedEnvironment(context.environment),\n fileState: {\n profileEnvironment: loadHarnessState(context.environment).state.profileEnvironment,\n pluginHooks: context.resources.pluginHooks,\n mcpProjection: context.resources.mcpProjection,\n },\n resolved,\n baseline: {\n mcpConfigPath: context.resources.mcpConfigPath,\n personaFile: context.environment[PERSONA_FILE_ENV],\n themePath,\n themeName: DEFAULT_THEME_NAME,\n },\n };\n\n // Runtime compilation writes the package dist files consumed by both the web\n // and server bundlers. Finish it first so a package clean cannot race either\n // consumer, then run the independent web and server builds together.\n let resolveCompositions!: (compositions: readonly ExtensionComposition[]) => void;\n let rejectCompositions!: (reason?: unknown) => void;\n const compositionsReady = new Promise<readonly ExtensionComposition[]>((resolve, reject) => {\n resolveCompositions = resolve;\n rejectCompositions = reject;\n });\n const runtimeProgress = progress.start(RUNTIME_LABEL, 'precompiling the mode bundles');\n const runtimeBuild = buildSyncedRuntime(location.root, environment, homeDirectory, {\n state,\n directory,\n onCompositionsResolved: resolveCompositions,\n }).then((synced) => {\n runtimeProgress(`${String(Object.keys(synced.bundles).length)} mode bundles`);\n return synced;\n });\n void runtimeBuild.catch(rejectCompositions);\n const webBuild = (async () => {\n await runtimeBuild;\n const webProgress = progress.start(WEB_LABEL, 'bundling the web cockpit plugins');\n const web = await syncWebBundle({\n repoRoot: location.root,\n resolvedEntries: state.resolved,\n environment,\n outputDirectory: path.join(directory, 'web-bundle'),\n onNotice: (message) => progress.line(WEB_LABEL, message),\n });\n if (web.status === 'failed') throw new Error(`Cockpit bundle failed: ${web.reason}`);\n webProgress(web.status === 'bundled' ? `cockpit bundled with plugins: ${web.pluginIds.join(', ')}` : web.reason);\n return web;\n })();\n const serverBuild = (async () => {\n const compositions = await compositionsReady;\n await runtimeBuild;\n const apiProgress = progress.start(API_LABEL, 'compiling the server bundle');\n const apiDirectory = path.join(directory, 'api');\n const fingerprint = crypto\n .createHash('sha256')\n .update(JSON.stringify([...new Set(compositions.map((composition) => composition.fingerprint))]))\n .digest('hex');\n const server = await syncServerBundle({\n repositoryRoot: location.root,\n generation,\n fingerprint,\n compositions,\n outputDirectory: apiDirectory,\n cacheDirectory: path.join(directory, 'cache'),\n sharedCacheDirectory: location.sharedCacheDirectory,\n });\n apiProgress(`${server.descriptor.entries.length} server facet(s) compiled`);\n for (const gap of server.contractGaps) progress.line(API_LABEL, `API contract incomplete: ${gap}`);\n return { server, fingerprint, apiDirectory };\n })();\n const [runtimeResult, webResult, serverResult] = await Promise.allSettled([runtimeBuild, webBuild, serverBuild]);\n if (runtimeResult.status === 'rejected') throw runtimeResult.reason;\n if (webResult.status === 'rejected') throw webResult.reason;\n if (serverResult.status === 'rejected') throw serverResult.reason;\n const synced = runtimeResult.value;\n const web = webResult.value;\n const { server, fingerprint, apiDirectory } = serverResult.value;\n const descriptorPath = path.join(apiDirectory, DOOM_SERVER_BUNDLE_FILE);\n const finalState: SyncState = {\n ...synced.state,\n serverBundle: {\n descriptorPath,\n fingerprint,\n compilerManifests: server.compilerManifests,\n sourcesHash: computeServerSourcesHash(synced.state.resolved),\n },\n };\n const statePath = await writeSyncState(\n location.root,\n finalState,\n homeDirectory,\n path.join(directory, 'state.json'),\n );\n const projectSettingsPath =\n (commandOptions.settingsMode ?? 'persisted') === 'persisted'\n ? writeProjectPiSettings(location.root, homeDirectory)\n : undefined;\n const result: SyncResult = {\n statePath,\n ...(projectSettingsPath ? { projectSettingsPath } : {}),\n selection,\n mcpServers: synced.state.baseline.mcpConfigPath ? readMcpServerNames(synced.state.baseline.mcpConfigPath) : [],\n skillCount: context.resources.skillCount,\n agentCount: context.resources.agentCount,\n };\n publishSyncRegistration(\n location.root,\n {\n version: SYNC_REGISTRATION_VERSION,\n root: location.root,\n identity: location.identity,\n generation,\n generationRoot: directory,\n statePath,\n stateSha256: syncStateSha256(statePath),\n webDirectory: web.status === 'bundled' ? web.assetsDir : null,\n apiDirectory,\n serverBundle: { path: descriptorPath, fingerprint, sha256: syncStateSha256(descriptorPath) },\n package: packageRegistrationFor(),\n },\n homeDirectory,\n );\n // ponytail: retain generations until host-owned drain evidence can prove no session uses them.\n // Directory age and an open-file check cannot establish that a lazy import is finished.\n return result;\n } catch (error) {\n await fs.promises.rm(directory, { recursive: true, force: true });\n throw error;\n }\n}\n\n/** Compatibility API. Executables call the command function directly. */\nexport class SyncCommand {\n readonly name = SYNC_COMMAND;\n private readonly settingsMode: SyncSettingsMode;\n private readonly homeDirectory: string | undefined;\n private readonly lockHeld: boolean;\n\n constructor(options: SyncCommandOptions = {}) {\n this.settingsMode = options.settingsMode ?? 'persisted';\n this.homeDirectory = options.homeDirectory;\n this.lockHeld = options.lockHeld ?? false;\n }\n\n matches(args: string[]): boolean {\n return args[0] === this.name;\n }\n\n async execute(\n args: string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n output: SyncOutput = process.stdout,\n ): Promise<number> {\n return synchronize(args, environment, currentDirectory, output, {\n settingsMode: this.settingsMode,\n homeDirectory: this.homeDirectory,\n lockHeld: this.lockHeld,\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["path","os","globalDoomConfigDirectory","resolveDoomConfigurationRoot","HARNESS_STATE_POINTER","DOOMPI_MAJOR_MODE_ENV","DOOMPI_DOMAINS_ENV","DOOMPI_PROFILE_ENV","loadDoomConfigLenient","loadMajorModesConfig","createLayerResolvers","resolveExtensionComposition","PERSONA_ENTRY","filterHookDisabledLayers","resolveLayers","piThemeDirectory","DEFAULT_THEME_NAME","readPiSettings","mergePiSettings","serializePiSettings","piExtensionAliasIsCurrent","DEFAULT_THEME","fs","syncStateRootMatches","computeInputsHash","recordResolvedEntries","readBootstrapStatus","piAgentDirectory","projectRegistersDoom","DUPLICATE_REGISTRATION_DRIFT","readSyncDrift","spawnSync","doomPiPackageRoot","DOOMPI_API_VERSION","readSyncRegistration","SYNC_REGISTRATION_VERSION","loadMajorModesConfigLenient","loadDomains","parseHarnessArgs","piExtensionDispatcherIsUpgradeable","missingLayerPackageSpecifiers","readLocatedSyncState","SyncProgress","acquireSyncLocationLock","resolveSyncLocation","crypto","syncGenerationDirectory","buildHarnessContext","ensureLayerPackages","SYNC_STATE_VERSION","computeWebSourcesHash","loadHarnessState","buildSyncedRuntime","syncWebBundle","syncServerBundle","DOOM_SERVER_BUNDLE_FILE","computeServerSourcesHash","writeSyncState","writeProjectPiSettings","readMcpServerNames","syncStateSha256"],"sources":["../../../../src/cli/commands/sync/index.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\nimport { loadDomains } from '@agimon-ai/doompi-config/domains';\nimport { filterHookDisabledLayers, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport { loadMajorModesConfig, loadMajorModesConfigLenient } from '@agimon-ai/doompi-config/majorModes';\nimport type { ConfigDiagnostic } from '@agimon-ai/doompi-config/types';\nimport {\n mergePiSettings,\n piAgentDirectory,\n piThemeDirectory,\n readPiSettings,\n serializePiSettings,\n} from '@agimon-ai/doompi-core/runtime-pi-settings';\nimport { DOOM_SERVER_BUNDLE_FILE } from '@agimon-ai/doompi-core/server-facet';\nimport {\n acquireSyncLocationLock,\n resolveSyncLocation,\n syncGenerationDirectory,\n} from '@agimon-ai/doompi-core/sync-location';\nimport {\n DOOMPI_API_VERSION,\n publishSyncRegistration,\n readSyncRegistration,\n SYNC_REGISTRATION_VERSION,\n syncStateSha256,\n type SyncPackageRegistration,\n} from '@agimon-ai/doompi-core/sync-registration';\nimport { DEFAULT_THEME, DEFAULT_THEME_NAME } from '@agimon-ai/doompi-ui/theme';\n\nimport { buildSyncedRuntime } from '../../../builders/cli';\nimport { readBootstrapStatus } from '../../../builders/cli/bootstrapLocator';\nimport {\n createLayerResolvers,\n type ExtensionComposition,\n PERSONA_ENTRY,\n resolveExtensionComposition,\n} from '../../../builders/cli/extensionAssembler';\nimport { buildHarnessContext } from '../../../builders/cli/harnessContext';\nimport {\n doomPiPackageRoot,\n piExtensionAliasIsCurrent,\n writePiExtensionAlias,\n} from '../../../builders/cli/piExtensionAlias';\nimport { piExtensionDispatcherIsUpgradeable } from '../../../builders/cli/piExtensionDispatcher';\nimport {\n DUPLICATE_REGISTRATION_DRIFT,\n projectRegistersDoom,\n writeProjectPiSettings,\n} from '../../../builders/cli/projectSettings';\nimport { syncServerBundle } from '../../../builders/server';\nimport { syncWebBundle } from '../../../builders/web';\nimport { HARNESS_STATE_POINTER, loadHarnessState } from '../../../composition/harnessState';\nimport { ensureLayerPackages, missingLayerPackageSpecifiers } from '../../../composition/layerPackageInstaller';\nimport { loadDoomConfigLenient } from '../../../composition/projectTrust';\nimport { resolveDoomConfigurationRoot } from '../../../composition/repository';\nimport { readSyncDrift } from '../../../composition/syncDrift';\nimport {\n computeInputsHash,\n computeWebSourcesHash,\n computeServerSourcesHash,\n readLocatedSyncState,\n readMcpServerNames,\n recordResolvedEntries,\n SYNC_STATE_VERSION,\n type SyncSelection,\n type SyncState,\n syncStateRootMatches,\n writeSyncState,\n} from '../../../composition/syncState';\nimport type { HarnessOptions } from '../../../composition/types/harness';\nimport { DOOMPI_DOMAINS_ENV, DOOMPI_MAJOR_MODE_ENV, DOOMPI_PROFILE_ENV } from '../../matrixOptions';\nimport { parseHarnessArgs } from '../../options';\nimport { SyncProgress, type SyncProgressOutput } from './presenter';\n\n/**\n * `doom-pi sync`: resolve the matrix once and write it where plain Pi finds it.\n *\n * The doom-emacs split. Everything that needs a real Node process (module\n * resolution, staging skills and agents, generating the MCP config) happens\n * here, and the doom-pi extension then only reads what this produced. The\n * launcher is untouched and keeps resolving the same matrix per run.\n */\n\nconst SYNC_COMMAND = 'sync';\nconst CHECK_OPTION = '--check';\n/** Republishes even when nothing drifted, for a generation suspected of being damaged. */\nconst FORCE_OPTION = '--force';\nconst GLOBAL_OPTION = '--global';\nconst HARNESS_ROOT_ENV = 'DOOMPI_ROOT';\nconst PERSONA_FILE_ENV = 'DOOMPI_PERSONA_FILE';\nconst HOOK_EMITTER = path.join('tools', 'harness', 'emit-hooks.mjs');\nconst NONE = '(none)';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst SYNC_LABEL = 'sync';\nconst RUNTIME_LABEL = 'runtime';\nconst WEB_LABEL = 'web';\nconst API_LABEL = 'api';\n\nexport interface SyncRoots {\n globalOnly: boolean;\n globalRoot: string;\n sourceRoot: string;\n targetRoot: string;\n}\n\n/** Resolves the configuration source and publication destination for one sync. */\nexport function resolveSyncRoots(\n args: readonly string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n homeDirectory = environment.HOME ?? os.homedir(),\n): SyncRoots {\n const globalRoot = globalDoomConfigDirectory(homeDirectory);\n const inheritedRoot = environment[HARNESS_ROOT_ENV];\n const sourceRoot = inheritedRoot\n ? path.resolve(inheritedRoot)\n : resolveDoomConfigurationRoot(currentDirectory, homeDirectory);\n const globalOnly = args.includes(GLOBAL_OPTION);\n return { globalOnly, globalRoot, sourceRoot, targetRoot: globalOnly ? globalRoot : sourceRoot };\n}\n\nconst GLOBAL_SCOPE_INHERITED_KEYS = [\n HARNESS_ROOT_ENV,\n HARNESS_STATE_POINTER,\n DOOMPI_MAJOR_MODE_ENV,\n DOOMPI_DOMAINS_ENV,\n DOOMPI_PROFILE_ENV,\n] as const;\n\n/** Removes workspace-only state before a global runtime is resolved. */\nexport function environmentForSyncScope(environment: NodeJS.ProcessEnv, globalOnly: boolean): NodeJS.ProcessEnv {\n if (!globalOnly) return environment;\n const scoped = { ...environment };\n for (const key of GLOBAL_SCOPE_INHERITED_KEYS) delete scoped[key];\n return scoped;\n}\n\n/**\n * Harness variables worth recording, by prefix or exact name.\n *\n * An allowlist rather than the whole environment: the state file is a snapshot\n * of resolved configuration, and dumping `process.env` into it would write\n * every credential the sync happened to run with onto disk.\n */\nconst RECORDED_PREFIXES = ['DOOMPI_'];\nconst RECORDED_KEYS = ['CLAUDE_PROJECT_DIR', 'CODEX_REPO_ROOT', 'ORIGINAL_REPO_PATH', 'MCP_UI_VIEWER'];\n/**\n * Launcher-only values a synced session must not inherit.\n *\n * The child extension list is recomposed on every load, and the subagent binary\n * points at `pi.sh`, which a session started as plain `pi` should not shell out\n * to: Doom Team resolves Pi's own CLI when the variable is absent.\n */\nconst EXCLUDED_KEYS = new Set([\n 'DOOMPI_CHILD_EXTENSIONS',\n 'DOOMPI_COMPOSED',\n 'DOOMPI_MUTE',\n 'DOOMPI_TEMP_DIR',\n // A pointer to the syncing process's own state file. Recording it would hand\n // every later session a path to a state that died with this one.\n HARNESS_STATE_POINTER,\n 'PI_SUBAGENT_PI_BINARY',\n]);\n\ntype SyncOutput = SyncProgressOutput;\n\nexport type SyncSettingsMode = 'persisted' | 'embedded';\n\nexport interface SyncCommandOptions {\n settingsMode?: SyncSettingsMode;\n /** Test/embedding override; normal CLI execution uses the process home. */\n homeDirectory?: string;\n /** Internal pipeline seam when the caller owns the worktree lock. */\n lockHeld?: boolean;\n}\n\nexport interface SyncResult {\n statePath: string;\n /** Omitted when DPI supplies the integration as a process-local overlay. */\n settingsPath?: string;\n /** Set only when the repository still carried its own DoomPi registration. */\n projectSettingsPath?: string;\n selection: SyncSelection;\n mcpServers: string[];\n skillCount: number;\n agentCount: number;\n}\n\nexport function recordedEnvironment(environment: NodeJS.ProcessEnv): Record<string, string> {\n const recorded: Record<string, string> = {};\n for (const [key, value] of Object.entries(environment)) {\n if (value === undefined || EXCLUDED_KEYS.has(key)) continue;\n if (RECORDED_KEYS.includes(key) || RECORDED_PREFIXES.some((prefix) => key.startsWith(prefix))) {\n recorded[key] = value;\n }\n }\n return recorded;\n}\n\n/**\n * Reports the config keys sync chose to ignore.\n *\n * Never fatal. A key nobody recognises is usually a config written for another\n * version of a layer, and refusing to build over it is worse than proceeding\n * without it. The strict check lives in `doompi doctor`.\n */\nfunction writeConfigDiagnostics(diagnostics: readonly ConfigDiagnostic[], output: SyncOutput): void {\n if (diagnostics.length === 0) return;\n const lines = diagnostics.map((entry) => ` ${entry.filePath}: ${entry.path}`).join('\\n');\n output.write(\n `config: ignored ${String(diagnostics.length)} unsupported key(s); run doompi doctor for the strict check\\n${lines}\\n`,\n );\n}\n/**\n * Layers the repository's declared selection under the usual resolution.\n *\n * `.doom/config.yaml` holds what the repository selects by default, the way\n * init.el does for doom-emacs. Seeding the environment the parser reads keeps\n * the precedence the launcher already documents: an explicit flag wins, then an\n * exported variable, then the declared default.\n */\nexport function selectionEnvironment(\n repoRoot: string,\n environment: NodeJS.ProcessEnv,\n homeDirectory?: string,\n): NodeJS.ProcessEnv {\n const { selection } = loadDoomConfigLenient(repoRoot, homeDirectory).config;\n if (!selection) return environment;\n return {\n ...environment,\n ...(selection.majorMode && !environment[DOOMPI_MAJOR_MODE_ENV]\n ? { [DOOMPI_MAJOR_MODE_ENV]: selection.majorMode }\n : {}),\n ...(selection.profile && !environment[DOOMPI_PROFILE_ENV] ? { [DOOMPI_PROFILE_ENV]: selection.profile } : {}),\n ...(selection.domains && environment[DOOMPI_DOMAINS_ENV] === undefined\n ? { [DOOMPI_DOMAINS_ENV]: selection.domains.join(',') }\n : {}),\n };\n}\n\nexport function toSelection(\n options: Pick<HarnessOptions, 'majorMode' | 'domains' | 'profile' | 'preset'>,\n): SyncSelection {\n return {\n majorMode: options.majorMode,\n domains: options.domains,\n profile: options.profile,\n preset: options.preset,\n };\n}\n\nexport function selectionCompositionFingerprint(\n repoRoot: string,\n options: Pick<HarnessOptions, 'agents' | 'hooks' | 'majorMode' | 'mcp' | 'preset'>,\n homeDirectory: string = os.homedir(),\n): string {\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);\n const resolvers = createLayerResolvers(repoRoot);\n return resolveExtensionComposition({\n agents: options.agents,\n autoStop: false,\n mute: false,\n preset: options.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorMode: options.majorMode,\n layers: filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n ),\n majorModesConfig,\n resolvers,\n }).fingerprint;\n}\n\n/** Settings, dispatcher and theme differences an init would fix, independent of sync state. */\nfunction piIntegrationDrift(agentDirectory: string): string[] {\n const drift: string[] = [];\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const settings = readPiSettings(agentDirectory);\n const merged = mergePiSettings(settings, agentDirectory, { themePath, themeName: DEFAULT_THEME_NAME });\n if (serializePiSettings(merged) !== serializePiSettings(settings)) {\n drift.push('Pi user settings are out of date; run doompi init');\n }\n if (!piExtensionAliasIsCurrent(agentDirectory)) drift.push('Pi user dispatcher is out of date; run doompi init');\n const expectedTheme = `${JSON.stringify(DEFAULT_THEME, null, 2)}\\n`;\n if (!fs.existsSync(themePath) || fs.readFileSync(themePath, 'utf8') !== expectedTheme) {\n drift.push('Pi user theme is out of date; run doompi init');\n }\n return drift;\n}\n\n/** Differences between what a sync would produce and what is on disk. */\nexport function collectDrift(\n repoRoot: string,\n selection: SyncSelection,\n state: SyncState | undefined,\n environment: NodeJS.ProcessEnv = process.env,\n settingsMode: SyncSettingsMode = 'persisted',\n expectedCompositionFingerprint?: string,\n): string[] {\n if (!state) return ['no sync state: run doompi sync'];\n const drift: string[] = [];\n if (syncRegistrationNeedsApiMigration(repoRoot, environment.HOME ?? os.homedir())) {\n drift.push('DoomPi registration needs API migration');\n }\n if (!syncStateRootMatches(repoRoot, state.root)) drift.push('sync state belongs to a different repository');\n const recorded = state.selection;\n if (\n recorded.majorMode !== selection.majorMode ||\n recorded.profile !== selection.profile ||\n recorded.preset !== selection.preset ||\n recorded.domains.join(',') !== selection.domains.join(',')\n ) {\n drift.push('selection changed since the last sync');\n }\n // Hashed against the recorded selection, not the requested one, so a\n // selection change is reported once rather than as two findings.\n if (computeInputsHash(repoRoot, recorded, environment.HOME ?? os.homedir()) !== state.inputsHash) {\n drift.push('.doom configuration changed');\n }\n // Re-resolving is what catches a dependency upgrade moving a package, which\n // the inputs hash deliberately does not read.\n if (\n JSON.stringify(\n recordResolvedEntries(\n loadMajorModesConfig(repoRoot, environment.HOME ?? os.homedir()),\n createLayerResolvers(repoRoot),\n ),\n ) !== JSON.stringify(state.resolved)\n ) {\n drift.push('resolved extension paths changed');\n }\n if (expectedCompositionFingerprint && state.compositionFingerprint !== expectedCompositionFingerprint) {\n drift.push('extension composition changed');\n }\n try {\n if (!readBootstrapStatus(repoRoot, undefined, environment.HOME ?? os.homedir()).fresh) {\n drift.push('precompiled runtime is missing or stale');\n }\n } catch {\n drift.push('precompiled runtime is missing or stale');\n }\n\n if (settingsMode === 'persisted') {\n const agentDirectory = piAgentDirectory(environment);\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n drift.push(...piIntegrationDrift(agentDirectory));\n if (projectRegistersDoom(repoRoot)) drift.push(DUPLICATE_REGISTRATION_DRIFT);\n if (state.baseline.themePath !== themePath || state.baseline.themeName !== DEFAULT_THEME_NAME) {\n drift.push('synced theme location is out of date');\n }\n }\n if (\n readSyncDrift({ repoRoot, homeDirectory: environment.HOME ?? os.homedir() }).reasons.includes('server-bundle-stale')\n ) {\n drift.push('server bundle is missing or stale');\n }\n return drift;\n}\n\n/** Regenerates the hook files the other frontends read before any harness code runs. */\nfunction emitFrontendHooks(repoRoot: string, output: SyncOutput): void {\n const emitter = path.join(repoRoot, HOOK_EMITTER);\n if (!fs.existsSync(emitter)) return;\n const result = spawnSync(process.execPath, [emitter, '--write'], { cwd: repoRoot, encoding: 'utf8' });\n if (result.status === 0) {\n output.write('hooks: regenerated for Claude Code and Codex\\n');\n return;\n }\n output.write(`hooks: emit-hooks failed (${result.stderr?.trim() || `exit ${String(result.status)}`})\\n`);\n}\n\nexport function formatSyncResult(result: SyncResult, runner = 'pi'): string {\n const { selection } = result;\n return [\n `mode: ${selection.majorMode}`,\n `domains: ${selection.domains.join(', ') || NONE}`,\n `profile: ${selection.profile ?? NONE}`,\n `skills: ${result.skillCount}`,\n `agents: ${result.agentCount}`,\n `mcp: ${result.mcpServers.join(', ') || NONE}`,\n `state: ${result.statePath}`,\n ...(result.settingsPath ? [`settings: ${result.settingsPath}`] : []),\n ...(result.projectSettingsPath\n ? [`project: removed duplicate registration from ${result.projectSettingsPath}`]\n : []),\n '',\n `Run ${runner} from the repository root to use it.`,\n '',\n ].join('\\n');\n}\n\n/**\n * The DoomPi that produced this generation, which is the one that can load it.\n *\n * Always the executing package, never another copy the repository happens to\n * install. A generation is not portable between two installations: the bundles\n * are compiled from the building package's own extension entries, the recorded\n * compiler inputs are its files, and the state names its bootstrap entry. Naming\n * a second copy here publishes a registration whose package disagrees with the\n * state it points at, and Pi's dispatcher then loads a harness that rejects the\n * bootstrap as stale on every session, with no sync able to fix it.\n *\n * A repository that wants its own copy to own its sessions runs sync with that\n * copy's CLI, which makes it the executing package.\n */\nfunction packageRegistrationFor(): SyncPackageRegistration {\n const root = fs.realpathSync(doomPiPackageRoot());\n const manifestPath = path.join(root, 'package.json');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as {\n version?: unknown;\n doompiApiVersion?: unknown;\n pi?: { extensions?: unknown };\n };\n const version = manifest.version;\n const apiVersion = manifest.doompiApiVersion;\n const extensions = manifest.pi?.extensions;\n const extension = Array.isArray(extensions) ? extensions.find((value) => typeof value === 'string') : undefined;\n if (\n typeof version !== 'string' ||\n typeof apiVersion !== 'number' ||\n !Number.isSafeInteger(apiVersion) ||\n apiVersion < 1 ||\n apiVersion !== DOOMPI_API_VERSION ||\n typeof extension !== 'string'\n ) {\n throw new Error(`Installed DoomPi package at ${root} has no supported API-versioned Pi extension entry`);\n }\n return {\n root,\n version,\n apiVersion: apiVersion as number,\n manifestPath,\n entry: fs.realpathSync(path.resolve(root, extension)),\n };\n}\n\n/** Returns true when a valid legacy registration must be republished with API metadata. */\nexport function syncRegistrationNeedsApiMigration(repoRoot: string, homeDirectory: string): boolean {\n try {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n return (\n registration !== undefined &&\n (registration.version !== SYNC_REGISTRATION_VERSION || registration.package.apiVersion === undefined)\n );\n } catch {\n return false;\n }\n}\n\n/** Resolves the matrix, stages it into home-scoped worktree storage, and publishes one generation. */\nexport async function synchronize(\n args: string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n output: SyncOutput = process.stdout,\n commandOptions: SyncCommandOptions = {},\n): Promise<number> {\n const check = args.includes(CHECK_OPTION);\n const force = args.includes(FORCE_OPTION);\n const rest = args.slice(1).filter((argument) => ![CHECK_OPTION, FORCE_OPTION, GLOBAL_OPTION].includes(argument));\n const homeDirectory = commandOptions.homeDirectory ?? environment.HOME ?? os.homedir();\n const roots = resolveSyncRoots(args, environment, currentDirectory, homeDirectory);\n const { globalOnly, globalRoot, targetRoot: repoRoot } = roots;\n const scopedEnvironment = environmentForSyncScope(environment, globalOnly);\n if (globalOnly && !check) fs.mkdirSync(globalRoot, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n // Sync tolerates keys it does not recognise so a config written against a\n // different version cannot break a build. `doompi doctor` reports them.\n const modes = loadMajorModesConfigLenient(repoRoot, homeDirectory);\n const configDiagnostics = [...loadDoomConfigLenient(repoRoot, homeDirectory).diagnostics, ...modes.diagnostics];\n const defaultMajorMode = modes.config.defaultMajorMode;\n const defaultDomains = loadDomains(repoRoot, homeDirectory).defaultDomains;\n const parsed = parseHarnessArgs(\n rest,\n selectionEnvironment(repoRoot, scopedEnvironment, homeDirectory),\n globalOnly ? globalRoot : currentDirectory,\n defaultMajorMode,\n defaultDomains,\n );\n const selection = toSelection(parsed.options);\n const agentDirectory = piAgentDirectory(scopedEnvironment, homeDirectory);\n if ((commandOptions.settingsMode ?? 'persisted') === 'persisted' && !check) {\n if (piExtensionDispatcherIsUpgradeable(agentDirectory)) {\n writePiExtensionAlias(agentDirectory);\n output.write('repair: refreshed Pi user dispatcher\\n');\n }\n const drift = piIntegrationDrift(agentDirectory);\n if (drift.length > 0) {\n throw new Error(`DoomPi Pi integration is not ready:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}`);\n }\n }\n writeConfigDiagnostics(configDiagnostics, output);\n if (check) {\n const majorModesConfig = modes.config;\n const missingPackages = missingLayerPackageSpecifiers(\n majorModesConfig,\n Object.keys(majorModesConfig.layers),\n createLayerResolvers(repoRoot),\n );\n if (missingPackages.length > 0) {\n output.write(\n `doompi sync is out of date:\\n${missingPackages\n .map((specifier) => ` configured package is not installed: ${specifier}`)\n .join('\\n')}\\n`,\n );\n return 1;\n }\n let located: ReturnType<typeof readLocatedSyncState>;\n try {\n located = readLocatedSyncState(repoRoot, homeDirectory);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n output.write(`doompi sync is out of date:\\n ${detail}\\n`);\n return 1;\n }\n const expectedCompositionFingerprint = selectionCompositionFingerprint(repoRoot, parsed.options, homeDirectory);\n const drift = collectDrift(\n repoRoot,\n selection,\n located?.state,\n scopedEnvironment,\n commandOptions.settingsMode ?? 'persisted',\n expectedCompositionFingerprint,\n );\n if (drift.length === 0) {\n output.write('doompi sync is up to date\\n');\n return 0;\n }\n output.write(`doompi sync is out of date:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}\\n`);\n return 1;\n }\n\n // Publishing an identical generation is not a no-op: it moves the\n // registration, so every attached cockpit reloads and the previous\n // generation becomes garbage. Same inputs, same published result.\n const driftOptions = {\n repoRoot,\n homeDirectory,\n requireWebBundle: Boolean(environment.DOOMPI_WEB_PACKAGE_ROOT),\n };\n const registrationNeedsMigration = syncRegistrationNeedsApiMigration(repoRoot, homeDirectory);\n if (!force && !registrationNeedsMigration && readSyncDrift(driftOptions).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n\n const progress = new SyncProgress(output);\n const releaseLock = commandOptions.lockHeld\n ? undefined\n : await acquireSyncLocationLock(resolveSyncLocation(repoRoot, homeDirectory));\n let result: SyncResult;\n try {\n // A concurrent publisher may have resolved the drift while this command\n // waited for the lock. Avoid moving the registration for no change.\n const registrationNeedsMigration = syncRegistrationNeedsApiMigration(repoRoot, homeDirectory);\n if (!force && !registrationNeedsMigration && readSyncDrift(driftOptions).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n result = await stageSync(repoRoot, parsed.options, scopedEnvironment, homeDirectory, progress, commandOptions);\n } finally {\n await releaseLock?.();\n }\n emitFrontendHooks(repoRoot, output);\n output.write(formatSyncResult(result, (commandOptions.settingsMode ?? 'persisted') === 'embedded' ? 'dpi' : 'pi'));\n return 0;\n}\n\nasync function stageSync(\n repoRoot: string,\n options: Omit<HarnessOptions, 'repoRoot'>,\n environment: NodeJS.ProcessEnv,\n homeDirectory: string,\n progress: SyncProgress,\n commandOptions: SyncCommandOptions = {},\n): Promise<SyncResult> {\n const location = resolveSyncLocation(repoRoot, homeDirectory);\n const generation = `${Date.now().toString(36)}-${crypto.randomUUID()}`;\n const directory = syncGenerationDirectory(location, generation);\n await fs.promises.mkdir(location.generationsDirectory, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n // The leaf is created without `recursive`, so an existing path is an error\n // rather than something to adopt: the cockpit signs and serves whatever the\n // published generation holds, and sync must only ever publish bytes it\n // wrote itself into a directory it just created.\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE });\n\n try {\n const staged = progress.start(SYNC_LABEL, 'resolving the matrix and staging resources');\n const context = await buildHarnessContext({\n ...options,\n repoRoot: location.root,\n homeDirectory,\n cwd: location.root,\n resourceDirectory: directory,\n });\n await ensureLayerPackages({\n repoRoot: location.root,\n config: context.majorModesConfig,\n layers: Object.keys(context.majorModesConfig.layers),\n environment,\n });\n staged(`${String(context.resources.skillCount)} skills, ${String(context.resources.agentCount)} agents`);\n const selection = toSelection(options);\n const resolvers = createLayerResolvers(location.root);\n const resolved = recordResolvedEntries(context.majorModesConfig, resolvers);\n const compositionFingerprint = selectionCompositionFingerprint(location.root, options, homeDirectory);\n const agentDirectory = piAgentDirectory(environment, homeDirectory);\n const persistedThemePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const themePath =\n (commandOptions.settingsMode ?? 'persisted') === 'persisted' ? persistedThemePath : context.defaultThemePath;\n const state: SyncState = {\n version: SYNC_STATE_VERSION,\n root: location.root,\n identity: location.identity,\n inputsHash: computeInputsHash(location.root, selection, homeDirectory),\n webSourcesHash: computeWebSourcesHash(resolved),\n compositionFingerprint,\n selection,\n env: recordedEnvironment(context.environment),\n fileState: {\n profileEnvironment: loadHarnessState(context.environment).state.profileEnvironment,\n pluginHooks: context.resources.pluginHooks,\n mcpProjection: context.resources.mcpProjection,\n },\n resolved,\n baseline: {\n mcpConfigPath: context.resources.mcpConfigPath,\n personaFile: context.environment[PERSONA_FILE_ENV],\n themePath,\n themeName: DEFAULT_THEME_NAME,\n },\n };\n\n // Runtime compilation writes the package dist files consumed by both the web\n // and server bundlers. Finish it first so a package clean cannot race either\n // consumer, then run the independent web and server builds together.\n let resolveCompositions!: (compositions: readonly ExtensionComposition[]) => void;\n let rejectCompositions!: (reason?: unknown) => void;\n const compositionsReady = new Promise<readonly ExtensionComposition[]>((resolve, reject) => {\n resolveCompositions = resolve;\n rejectCompositions = reject;\n });\n const runtimeProgress = progress.start(RUNTIME_LABEL, 'precompiling the mode bundles');\n const runtimeBuild = buildSyncedRuntime(location.root, environment, homeDirectory, {\n state,\n directory,\n onCompositionsResolved: resolveCompositions,\n }).then((synced) => {\n runtimeProgress(`${String(Object.keys(synced.bundles).length)} mode bundles`);\n return synced;\n });\n void runtimeBuild.catch(rejectCompositions);\n const webBuild = (async () => {\n await runtimeBuild;\n const webProgress = progress.start(WEB_LABEL, 'bundling the web cockpit plugins');\n const web = await syncWebBundle({\n repoRoot: location.root,\n resolvedEntries: state.resolved,\n environment,\n outputDirectory: path.join(directory, 'web-bundle'),\n onNotice: (message) => progress.line(WEB_LABEL, message),\n });\n if (web.status === 'failed') throw new Error(`Cockpit bundle failed: ${web.reason}`);\n webProgress(web.status === 'bundled' ? `cockpit bundled with plugins: ${web.pluginIds.join(', ')}` : web.reason);\n return web;\n })();\n const serverBuild = (async () => {\n const compositions = await compositionsReady;\n await runtimeBuild;\n const apiProgress = progress.start(API_LABEL, 'compiling the server bundle');\n const apiDirectory = path.join(directory, 'api');\n const fingerprint = crypto\n .createHash('sha256')\n .update(JSON.stringify([...new Set(compositions.map((composition) => composition.fingerprint))]))\n .digest('hex');\n const server = await syncServerBundle({\n repositoryRoot: location.root,\n generation,\n fingerprint,\n compositions,\n outputDirectory: apiDirectory,\n cacheDirectory: path.join(directory, 'cache'),\n sharedCacheDirectory: location.sharedCacheDirectory,\n });\n apiProgress(`${server.descriptor.entries.length} server facet(s) compiled`);\n for (const gap of server.contractGaps) progress.line(API_LABEL, `API contract incomplete: ${gap}`);\n return { server, fingerprint, apiDirectory };\n })();\n const [runtimeResult, webResult, serverResult] = await Promise.allSettled([runtimeBuild, webBuild, serverBuild]);\n if (runtimeResult.status === 'rejected') throw runtimeResult.reason;\n if (webResult.status === 'rejected') throw webResult.reason;\n if (serverResult.status === 'rejected') throw serverResult.reason;\n const synced = runtimeResult.value;\n const web = webResult.value;\n const { server, fingerprint, apiDirectory } = serverResult.value;\n const descriptorPath = path.join(apiDirectory, DOOM_SERVER_BUNDLE_FILE);\n const finalState: SyncState = {\n ...synced.state,\n serverBundle: {\n descriptorPath,\n fingerprint,\n compilerManifests: server.compilerManifests,\n sourcesHash: computeServerSourcesHash(synced.state.resolved),\n },\n };\n const statePath = await writeSyncState(\n location.root,\n finalState,\n homeDirectory,\n path.join(directory, 'state.json'),\n );\n const projectSettingsPath =\n (commandOptions.settingsMode ?? 'persisted') === 'persisted'\n ? writeProjectPiSettings(location.root, homeDirectory)\n : undefined;\n const result: SyncResult = {\n statePath,\n ...(projectSettingsPath ? { projectSettingsPath } : {}),\n selection,\n mcpServers: synced.state.baseline.mcpConfigPath ? readMcpServerNames(synced.state.baseline.mcpConfigPath) : [],\n skillCount: context.resources.skillCount,\n agentCount: context.resources.agentCount,\n };\n publishSyncRegistration(\n location.root,\n {\n version: SYNC_REGISTRATION_VERSION,\n root: location.root,\n identity: location.identity,\n generation,\n generationRoot: directory,\n statePath,\n stateSha256: syncStateSha256(statePath),\n webDirectory: web.status === 'bundled' ? web.assetsDir : null,\n apiDirectory,\n serverBundle: { path: descriptorPath, fingerprint, sha256: syncStateSha256(descriptorPath) },\n package: packageRegistrationFor(),\n },\n homeDirectory,\n );\n // ponytail: retain generations until host-owned drain evidence can prove no session uses them.\n // Directory age and an open-file check cannot establish that a lazy import is finished.\n return result;\n } catch (error) {\n await fs.promises.rm(directory, { recursive: true, force: true });\n throw error;\n }\n}\n\n/** Compatibility API. Executables call the command function directly. */\nexport class SyncCommand {\n readonly name = SYNC_COMMAND;\n private readonly settingsMode: SyncSettingsMode;\n private readonly homeDirectory: string | undefined;\n private readonly lockHeld: boolean;\n\n constructor(options: SyncCommandOptions = {}) {\n this.settingsMode = options.settingsMode ?? 'persisted';\n this.homeDirectory = options.homeDirectory;\n this.lockHeld = options.lockHeld ?? false;\n }\n\n matches(args: string[]): boolean {\n return args[0] === this.name;\n }\n\n async execute(\n args: string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n output: SyncOutput = process.stdout,\n ): Promise<number> {\n return synchronize(args, environment, currentDirectory, output, {\n settingsMode: this.settingsMode,\n homeDirectory: this.homeDirectory,\n lockHeld: this.lockHeld,\n });\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/cli/commands/sync/index.ts"],"mappings":";;;;iBA2GiB;EACf;EACA;EACA;EACA;;;wBAIc,iBACd,yBACA,cAAa,OAAO,YACpB,2BACA,yBACC;;wBAmBa,wBAAwB,aAAa,OAAO,YAAY,sBAAsB,OAAO;KAkChG,aAAa;YAEN;iBAEK;EACf,eAAe;;EAEf;;EAEA;;iBAGe;EACf;;EAEA;;EAEA;EACA,WAAW;EACX;EACA;EACA;;wBAGc,oBAAoB,aAAa,OAAO,aAAa;;;;;;;;;wBAiCrD,qBACd,kBACA,aAAa,OAAO,YACpB,yBACC,OAAO;wBAeM,YACd,SAAS,KAAK,kEACb;wBASa,gCACd,kBACA,SAAS,KAAK,sEACd;;wBAuCc,aACd,kBACA,WAAW,eACX,OAAO,uBACP,cAAa,OAAO,YACpB,eAAc,kBACd;wBA0Ec,iBAAiB,QAAQ,YAAY;;wBAkErC,kCAAkC,kBAAkB;;wBAa9C,YACpB,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,YACR,iBAAgB,qBACf;;qBAwSU;WACF;mBACQ;mBACA;mBACA;EAEjB,YAAY,UAAS;EAMrB,QAAQ;EAIF,QACJ,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,aACP"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/cli/commands/sync/index.ts"],"mappings":";;;;iBAuGiB;EACf;EACA;EACA;EACA;;;wBAIc,iBACd,yBACA,cAAa,OAAO,YACpB,2BACA,yBACC;;wBAmBa,wBAAwB,aAAa,OAAO,YAAY,sBAAsB,OAAO;KAkChG,aAAa;YAEN;iBAEK;EACf,eAAe;;EAEf;;EAEA;;iBAGe;EACf;;EAEA;;EAEA;EACA,WAAW;EACX;EACA;EACA;;wBAGc,oBAAoB,aAAa,OAAO,aAAa;;;;;;;;;wBAiCrD,qBACd,kBACA,aAAa,OAAO,YACpB,yBACC,OAAO;wBAeM,YACd,SAAS,KAAK,kEACb;wBASa,gCACd,kBACA,SAAS,KAAK,sEACd;;wBAuCc,aACd,kBACA,WAAW,eACX,OAAO,uBACP,cAAa,OAAO,YACpB,eAAc,kBACd;wBA0Ec,iBAAiB,QAAQ,YAAY;;wBAkErC,kCAAkC,kBAAkB;;wBAa9C,YACpB,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,YACR,iBAAgB,qBACf;;qBAqSU;WACF;mBACQ;mBACA;mBACA;EAEjB,YAAY,UAAS;EAMrB,QAAQ;EAIF,QACJ,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,aACP"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/cli/commands/sync/index.ts"],"mappings":";;;;iBA2GiB;EACf;EACA;EACA;EACA;;;wBAIc,iBACd,yBACA,cAAa,OAAO,YACpB,2BACA,yBACC;;wBAmBa,wBAAwB,aAAa,OAAO,YAAY,sBAAsB,OAAO;KAkChG,aAAa;YAEN;iBAEK;EACf,eAAe;;EAEf;;EAEA;;iBAGe;EACf;;EAEA;;EAEA;EACA,WAAW;EACX;EACA;EACA;;wBAGc,oBAAoB,aAAa,OAAO,aAAa;;;;;;;;;wBAiCrD,qBACd,kBACA,aAAa,OAAO,YACpB,yBACC,OAAO;wBAeM,YACd,SAAS,KAAK,kEACb;wBASa,gCACd,kBACA,SAAS,KAAK,sEACd;;wBAuCc,aACd,kBACA,WAAW,eACX,OAAO,uBACP,cAAa,OAAO,YACpB,eAAc,kBACd;wBA0Ec,iBAAiB,QAAQ,YAAY;;wBAkErC,kCAAkC,kBAAkB;;wBAa9C,YACpB,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,YACR,iBAAgB,qBACf;;qBAwSU;WACF;mBACQ;mBACA;mBACA;EAEjB,YAAY,UAAS;EAMrB,QAAQ;EAIF,QACJ,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,aACP"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/cli/commands/sync/index.ts"],"mappings":";;;;iBAuGiB;EACf;EACA;EACA;EACA;;;wBAIc,iBACd,yBACA,cAAa,OAAO,YACpB,2BACA,yBACC;;wBAmBa,wBAAwB,aAAa,OAAO,YAAY,sBAAsB,OAAO;KAkChG,aAAa;YAEN;iBAEK;EACf,eAAe;;EAEf;;EAEA;;iBAGe;EACf;;EAEA;;EAEA;EACA,WAAW;EACX;EACA;EACA;;wBAGc,oBAAoB,aAAa,OAAO,aAAa;;;;;;;;;wBAiCrD,qBACd,kBACA,aAAa,OAAO,YACpB,yBACC,OAAO;wBAeM,YACd,SAAS,KAAK,kEACb;wBASa,gCACd,kBACA,SAAS,KAAK,sEACd;;wBAuCc,aACd,kBACA,WAAW,eACX,OAAO,uBACP,cAAa,OAAO,YACpB,eAAc,kBACd;wBA0Ec,iBAAiB,QAAQ,YAAY;;wBAkErC,kCAAkC,kBAAkB;;wBAa9C,YACpB,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,YACR,iBAAgB,qBACf;;qBAqSU;WACF;mBACQ;mBACA;mBACA;EAEjB,YAAY,UAAS;EAMrB,QAAQ;EAIF,QACJ,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,aACP"}
@@ -10,7 +10,7 @@ import { SYNC_STATE_VERSION, computeInputsHash, computeServerSourcesHash, comput
10
10
  import { buildSyncedRuntime } from "../../../builders/cli/index.mjs";
11
11
  import { readBootstrapStatus } from "../../../builders/cli/bootstrapLocator/index.mjs";
12
12
  import { buildHarnessContext } from "../../../builders/cli/harnessContext.mjs";
13
- import { piExtensionDispatcherIsUpgradeable, piExtensionDispatcherVersion } from "../../../builders/cli/piExtensionDispatcher/index.mjs";
13
+ import { piExtensionDispatcherIsUpgradeable } from "../../../builders/cli/piExtensionDispatcher/index.mjs";
14
14
  import { doomPiPackageRoot, piExtensionAliasIsCurrent, writePiExtensionAlias } from "../../../builders/cli/piExtensionAlias/index.mjs";
15
15
  import { DUPLICATE_REGISTRATION_DRIFT, projectRegistersDoom, writeProjectPiSettings } from "../../../builders/cli/projectSettings.mjs";
16
16
  import { syncServerBundle } from "../../../builders/server/index.mjs";
@@ -309,9 +309,8 @@ async function synchronize(args, environment = process.env, currentDirectory = p
309
309
  const agentDirectory = piAgentDirectory(scopedEnvironment, homeDirectory);
310
310
  if ((commandOptions.settingsMode ?? "persisted") === "persisted" && !check) {
311
311
  if (piExtensionDispatcherIsUpgradeable(agentDirectory)) {
312
- const previousVersion = piExtensionDispatcherVersion(agentDirectory);
313
312
  writePiExtensionAlias(agentDirectory);
314
- output.write(`repair: upgraded Pi user dispatcher from protocol ${String(previousVersion)} to ${String(3)}\n`);
313
+ output.write("repair: refreshed Pi user dispatcher\n");
315
314
  }
316
315
  const drift = piIntegrationDrift(agentDirectory);
317
316
  if (drift.length > 0) throw new Error(`DoomPi Pi integration is not ready:\n${drift.map((entry) => ` ${entry}`).join("\n")}`);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["loadDoomConfigLenient"],"sources":["../../../../src/cli/commands/sync/index.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\nimport { loadDomains } from '@agimon-ai/doompi-config/domains';\nimport { filterHookDisabledLayers, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport { loadMajorModesConfig, loadMajorModesConfigLenient } from '@agimon-ai/doompi-config/majorModes';\nimport type { ConfigDiagnostic } from '@agimon-ai/doompi-config/types';\nimport {\n mergePiSettings,\n piAgentDirectory,\n piThemeDirectory,\n readPiSettings,\n serializePiSettings,\n} from '@agimon-ai/doompi-core/runtime-pi-settings';\nimport { DOOM_SERVER_BUNDLE_FILE } from '@agimon-ai/doompi-core/server-facet';\nimport {\n acquireSyncLocationLock,\n resolveSyncLocation,\n syncGenerationDirectory,\n} from '@agimon-ai/doompi-core/sync-location';\nimport {\n DOOMPI_API_VERSION,\n publishSyncRegistration,\n readSyncRegistration,\n SYNC_REGISTRATION_VERSION,\n syncStateSha256,\n type SyncPackageRegistration,\n} from '@agimon-ai/doompi-core/sync-registration';\nimport { DEFAULT_THEME, DEFAULT_THEME_NAME } from '@agimon-ai/doompi-ui/theme';\n\nimport { buildSyncedRuntime } from '../../../builders/cli';\nimport { readBootstrapStatus } from '../../../builders/cli/bootstrapLocator';\nimport {\n createLayerResolvers,\n type ExtensionComposition,\n PERSONA_ENTRY,\n resolveExtensionComposition,\n} from '../../../builders/cli/extensionAssembler';\nimport { buildHarnessContext } from '../../../builders/cli/harnessContext';\nimport {\n doomPiPackageRoot,\n piExtensionAliasIsCurrent,\n writePiExtensionAlias,\n} from '../../../builders/cli/piExtensionAlias';\nimport {\n PI_DISPATCHER_VERSION,\n piExtensionDispatcherIsUpgradeable,\n piExtensionDispatcherVersion,\n} from '../../../builders/cli/piExtensionDispatcher';\nimport {\n DUPLICATE_REGISTRATION_DRIFT,\n projectRegistersDoom,\n writeProjectPiSettings,\n} from '../../../builders/cli/projectSettings';\nimport { syncServerBundle } from '../../../builders/server';\nimport { syncWebBundle } from '../../../builders/web';\nimport { HARNESS_STATE_POINTER, loadHarnessState } from '../../../composition/harnessState';\nimport { ensureLayerPackages, missingLayerPackageSpecifiers } from '../../../composition/layerPackageInstaller';\nimport { loadDoomConfigLenient } from '../../../composition/projectTrust';\nimport { resolveDoomConfigurationRoot } from '../../../composition/repository';\nimport { readSyncDrift } from '../../../composition/syncDrift';\nimport {\n computeInputsHash,\n computeWebSourcesHash,\n computeServerSourcesHash,\n readLocatedSyncState,\n readMcpServerNames,\n recordResolvedEntries,\n SYNC_STATE_VERSION,\n type SyncSelection,\n type SyncState,\n syncStateRootMatches,\n writeSyncState,\n} from '../../../composition/syncState';\nimport type { HarnessOptions } from '../../../composition/types/harness';\nimport { DOOMPI_DOMAINS_ENV, DOOMPI_MAJOR_MODE_ENV, DOOMPI_PROFILE_ENV } from '../../matrixOptions';\nimport { parseHarnessArgs } from '../../options';\nimport { SyncProgress, type SyncProgressOutput } from './presenter';\n\n/**\n * `doom-pi sync`: resolve the matrix once and write it where plain Pi finds it.\n *\n * The doom-emacs split. Everything that needs a real Node process (module\n * resolution, staging skills and agents, generating the MCP config) happens\n * here, and the doom-pi extension then only reads what this produced. The\n * launcher is untouched and keeps resolving the same matrix per run.\n */\n\nconst SYNC_COMMAND = 'sync';\nconst CHECK_OPTION = '--check';\n/** Republishes even when nothing drifted, for a generation suspected of being damaged. */\nconst FORCE_OPTION = '--force';\nconst GLOBAL_OPTION = '--global';\nconst HARNESS_ROOT_ENV = 'DOOMPI_ROOT';\nconst PERSONA_FILE_ENV = 'DOOMPI_PERSONA_FILE';\nconst HOOK_EMITTER = path.join('tools', 'harness', 'emit-hooks.mjs');\nconst NONE = '(none)';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst SYNC_LABEL = 'sync';\nconst RUNTIME_LABEL = 'runtime';\nconst WEB_LABEL = 'web';\nconst API_LABEL = 'api';\n\nexport interface SyncRoots {\n globalOnly: boolean;\n globalRoot: string;\n sourceRoot: string;\n targetRoot: string;\n}\n\n/** Resolves the configuration source and publication destination for one sync. */\nexport function resolveSyncRoots(\n args: readonly string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n homeDirectory = environment.HOME ?? os.homedir(),\n): SyncRoots {\n const globalRoot = globalDoomConfigDirectory(homeDirectory);\n const inheritedRoot = environment[HARNESS_ROOT_ENV];\n const sourceRoot = inheritedRoot\n ? path.resolve(inheritedRoot)\n : resolveDoomConfigurationRoot(currentDirectory, homeDirectory);\n const globalOnly = args.includes(GLOBAL_OPTION);\n return { globalOnly, globalRoot, sourceRoot, targetRoot: globalOnly ? globalRoot : sourceRoot };\n}\n\nconst GLOBAL_SCOPE_INHERITED_KEYS = [\n HARNESS_ROOT_ENV,\n HARNESS_STATE_POINTER,\n DOOMPI_MAJOR_MODE_ENV,\n DOOMPI_DOMAINS_ENV,\n DOOMPI_PROFILE_ENV,\n] as const;\n\n/** Removes workspace-only state before a global runtime is resolved. */\nexport function environmentForSyncScope(environment: NodeJS.ProcessEnv, globalOnly: boolean): NodeJS.ProcessEnv {\n if (!globalOnly) return environment;\n const scoped = { ...environment };\n for (const key of GLOBAL_SCOPE_INHERITED_KEYS) delete scoped[key];\n return scoped;\n}\n\n/**\n * Harness variables worth recording, by prefix or exact name.\n *\n * An allowlist rather than the whole environment: the state file is a snapshot\n * of resolved configuration, and dumping `process.env` into it would write\n * every credential the sync happened to run with onto disk.\n */\nconst RECORDED_PREFIXES = ['DOOMPI_'];\nconst RECORDED_KEYS = ['CLAUDE_PROJECT_DIR', 'CODEX_REPO_ROOT', 'ORIGINAL_REPO_PATH', 'MCP_UI_VIEWER'];\n/**\n * Launcher-only values a synced session must not inherit.\n *\n * The child extension list is recomposed on every load, and the subagent binary\n * points at `pi.sh`, which a session started as plain `pi` should not shell out\n * to: Doom Team resolves Pi's own CLI when the variable is absent.\n */\nconst EXCLUDED_KEYS = new Set([\n 'DOOMPI_CHILD_EXTENSIONS',\n 'DOOMPI_COMPOSED',\n 'DOOMPI_MUTE',\n 'DOOMPI_TEMP_DIR',\n // A pointer to the syncing process's own state file. Recording it would hand\n // every later session a path to a state that died with this one.\n HARNESS_STATE_POINTER,\n 'PI_SUBAGENT_PI_BINARY',\n]);\n\ntype SyncOutput = SyncProgressOutput;\n\nexport type SyncSettingsMode = 'persisted' | 'embedded';\n\nexport interface SyncCommandOptions {\n settingsMode?: SyncSettingsMode;\n /** Test/embedding override; normal CLI execution uses the process home. */\n homeDirectory?: string;\n /** Internal pipeline seam when the caller owns the worktree lock. */\n lockHeld?: boolean;\n}\n\nexport interface SyncResult {\n statePath: string;\n /** Omitted when DPI supplies the integration as a process-local overlay. */\n settingsPath?: string;\n /** Set only when the repository still carried its own DoomPi registration. */\n projectSettingsPath?: string;\n selection: SyncSelection;\n mcpServers: string[];\n skillCount: number;\n agentCount: number;\n}\n\nexport function recordedEnvironment(environment: NodeJS.ProcessEnv): Record<string, string> {\n const recorded: Record<string, string> = {};\n for (const [key, value] of Object.entries(environment)) {\n if (value === undefined || EXCLUDED_KEYS.has(key)) continue;\n if (RECORDED_KEYS.includes(key) || RECORDED_PREFIXES.some((prefix) => key.startsWith(prefix))) {\n recorded[key] = value;\n }\n }\n return recorded;\n}\n\n/**\n * Reports the config keys sync chose to ignore.\n *\n * Never fatal. A key nobody recognises is usually a config written for another\n * version of a layer, and refusing to build over it is worse than proceeding\n * without it. The strict check lives in `doompi doctor`.\n */\nfunction writeConfigDiagnostics(diagnostics: readonly ConfigDiagnostic[], output: SyncOutput): void {\n if (diagnostics.length === 0) return;\n const lines = diagnostics.map((entry) => ` ${entry.filePath}: ${entry.path}`).join('\\n');\n output.write(\n `config: ignored ${String(diagnostics.length)} unsupported key(s); run doompi doctor for the strict check\\n${lines}\\n`,\n );\n}\n/**\n * Layers the repository's declared selection under the usual resolution.\n *\n * `.doom/config.yaml` holds what the repository selects by default, the way\n * init.el does for doom-emacs. Seeding the environment the parser reads keeps\n * the precedence the launcher already documents: an explicit flag wins, then an\n * exported variable, then the declared default.\n */\nexport function selectionEnvironment(\n repoRoot: string,\n environment: NodeJS.ProcessEnv,\n homeDirectory?: string,\n): NodeJS.ProcessEnv {\n const { selection } = loadDoomConfigLenient(repoRoot, homeDirectory).config;\n if (!selection) return environment;\n return {\n ...environment,\n ...(selection.majorMode && !environment[DOOMPI_MAJOR_MODE_ENV]\n ? { [DOOMPI_MAJOR_MODE_ENV]: selection.majorMode }\n : {}),\n ...(selection.profile && !environment[DOOMPI_PROFILE_ENV] ? { [DOOMPI_PROFILE_ENV]: selection.profile } : {}),\n ...(selection.domains && environment[DOOMPI_DOMAINS_ENV] === undefined\n ? { [DOOMPI_DOMAINS_ENV]: selection.domains.join(',') }\n : {}),\n };\n}\n\nexport function toSelection(\n options: Pick<HarnessOptions, 'majorMode' | 'domains' | 'profile' | 'preset'>,\n): SyncSelection {\n return {\n majorMode: options.majorMode,\n domains: options.domains,\n profile: options.profile,\n preset: options.preset,\n };\n}\n\nexport function selectionCompositionFingerprint(\n repoRoot: string,\n options: Pick<HarnessOptions, 'agents' | 'hooks' | 'majorMode' | 'mcp' | 'preset'>,\n homeDirectory: string = os.homedir(),\n): string {\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);\n const resolvers = createLayerResolvers(repoRoot);\n return resolveExtensionComposition({\n agents: options.agents,\n autoStop: false,\n mute: false,\n preset: options.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorMode: options.majorMode,\n layers: filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n ),\n majorModesConfig,\n resolvers,\n }).fingerprint;\n}\n\n/** Settings, dispatcher and theme differences an init would fix, independent of sync state. */\nfunction piIntegrationDrift(agentDirectory: string): string[] {\n const drift: string[] = [];\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const settings = readPiSettings(agentDirectory);\n const merged = mergePiSettings(settings, agentDirectory, { themePath, themeName: DEFAULT_THEME_NAME });\n if (serializePiSettings(merged) !== serializePiSettings(settings)) {\n drift.push('Pi user settings are out of date; run doompi init');\n }\n if (!piExtensionAliasIsCurrent(agentDirectory)) drift.push('Pi user dispatcher is out of date; run doompi init');\n const expectedTheme = `${JSON.stringify(DEFAULT_THEME, null, 2)}\\n`;\n if (!fs.existsSync(themePath) || fs.readFileSync(themePath, 'utf8') !== expectedTheme) {\n drift.push('Pi user theme is out of date; run doompi init');\n }\n return drift;\n}\n\n/** Differences between what a sync would produce and what is on disk. */\nexport function collectDrift(\n repoRoot: string,\n selection: SyncSelection,\n state: SyncState | undefined,\n environment: NodeJS.ProcessEnv = process.env,\n settingsMode: SyncSettingsMode = 'persisted',\n expectedCompositionFingerprint?: string,\n): string[] {\n if (!state) return ['no sync state: run doompi sync'];\n const drift: string[] = [];\n if (syncRegistrationNeedsApiMigration(repoRoot, environment.HOME ?? os.homedir())) {\n drift.push('DoomPi registration needs API migration');\n }\n if (!syncStateRootMatches(repoRoot, state.root)) drift.push('sync state belongs to a different repository');\n const recorded = state.selection;\n if (\n recorded.majorMode !== selection.majorMode ||\n recorded.profile !== selection.profile ||\n recorded.preset !== selection.preset ||\n recorded.domains.join(',') !== selection.domains.join(',')\n ) {\n drift.push('selection changed since the last sync');\n }\n // Hashed against the recorded selection, not the requested one, so a\n // selection change is reported once rather than as two findings.\n if (computeInputsHash(repoRoot, recorded, environment.HOME ?? os.homedir()) !== state.inputsHash) {\n drift.push('.doom configuration changed');\n }\n // Re-resolving is what catches a dependency upgrade moving a package, which\n // the inputs hash deliberately does not read.\n if (\n JSON.stringify(\n recordResolvedEntries(\n loadMajorModesConfig(repoRoot, environment.HOME ?? os.homedir()),\n createLayerResolvers(repoRoot),\n ),\n ) !== JSON.stringify(state.resolved)\n ) {\n drift.push('resolved extension paths changed');\n }\n if (expectedCompositionFingerprint && state.compositionFingerprint !== expectedCompositionFingerprint) {\n drift.push('extension composition changed');\n }\n try {\n if (!readBootstrapStatus(repoRoot, undefined, environment.HOME ?? os.homedir()).fresh) {\n drift.push('precompiled runtime is missing or stale');\n }\n } catch {\n drift.push('precompiled runtime is missing or stale');\n }\n\n if (settingsMode === 'persisted') {\n const agentDirectory = piAgentDirectory(environment);\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n drift.push(...piIntegrationDrift(agentDirectory));\n if (projectRegistersDoom(repoRoot)) drift.push(DUPLICATE_REGISTRATION_DRIFT);\n if (state.baseline.themePath !== themePath || state.baseline.themeName !== DEFAULT_THEME_NAME) {\n drift.push('synced theme location is out of date');\n }\n }\n if (\n readSyncDrift({ repoRoot, homeDirectory: environment.HOME ?? os.homedir() }).reasons.includes('server-bundle-stale')\n ) {\n drift.push('server bundle is missing or stale');\n }\n return drift;\n}\n\n/** Regenerates the hook files the other frontends read before any harness code runs. */\nfunction emitFrontendHooks(repoRoot: string, output: SyncOutput): void {\n const emitter = path.join(repoRoot, HOOK_EMITTER);\n if (!fs.existsSync(emitter)) return;\n const result = spawnSync(process.execPath, [emitter, '--write'], { cwd: repoRoot, encoding: 'utf8' });\n if (result.status === 0) {\n output.write('hooks: regenerated for Claude Code and Codex\\n');\n return;\n }\n output.write(`hooks: emit-hooks failed (${result.stderr?.trim() || `exit ${String(result.status)}`})\\n`);\n}\n\nexport function formatSyncResult(result: SyncResult, runner = 'pi'): string {\n const { selection } = result;\n return [\n `mode: ${selection.majorMode}`,\n `domains: ${selection.domains.join(', ') || NONE}`,\n `profile: ${selection.profile ?? NONE}`,\n `skills: ${result.skillCount}`,\n `agents: ${result.agentCount}`,\n `mcp: ${result.mcpServers.join(', ') || NONE}`,\n `state: ${result.statePath}`,\n ...(result.settingsPath ? [`settings: ${result.settingsPath}`] : []),\n ...(result.projectSettingsPath\n ? [`project: removed duplicate registration from ${result.projectSettingsPath}`]\n : []),\n '',\n `Run ${runner} from the repository root to use it.`,\n '',\n ].join('\\n');\n}\n\n/**\n * The DoomPi that produced this generation, which is the one that can load it.\n *\n * Always the executing package, never another copy the repository happens to\n * install. A generation is not portable between two installations: the bundles\n * are compiled from the building package's own extension entries, the recorded\n * compiler inputs are its files, and the state names its bootstrap entry. Naming\n * a second copy here publishes a registration whose package disagrees with the\n * state it points at, and Pi's dispatcher then loads a harness that rejects the\n * bootstrap as stale on every session, with no sync able to fix it.\n *\n * A repository that wants its own copy to own its sessions runs sync with that\n * copy's CLI, which makes it the executing package.\n */\nfunction packageRegistrationFor(): SyncPackageRegistration {\n const root = fs.realpathSync(doomPiPackageRoot());\n const manifestPath = path.join(root, 'package.json');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as {\n version?: unknown;\n doompiApiVersion?: unknown;\n pi?: { extensions?: unknown };\n };\n const version = manifest.version;\n const apiVersion = manifest.doompiApiVersion;\n const extensions = manifest.pi?.extensions;\n const extension = Array.isArray(extensions) ? extensions.find((value) => typeof value === 'string') : undefined;\n if (\n typeof version !== 'string' ||\n typeof apiVersion !== 'number' ||\n !Number.isSafeInteger(apiVersion) ||\n apiVersion < 1 ||\n apiVersion !== DOOMPI_API_VERSION ||\n typeof extension !== 'string'\n ) {\n throw new Error(`Installed DoomPi package at ${root} has no supported API-versioned Pi extension entry`);\n }\n return {\n root,\n version,\n apiVersion: apiVersion as number,\n manifestPath,\n entry: fs.realpathSync(path.resolve(root, extension)),\n };\n}\n\n/** Returns true when a valid legacy registration must be republished with API metadata. */\nexport function syncRegistrationNeedsApiMigration(repoRoot: string, homeDirectory: string): boolean {\n try {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n return (\n registration !== undefined &&\n (registration.version !== SYNC_REGISTRATION_VERSION || registration.package.apiVersion === undefined)\n );\n } catch {\n return false;\n }\n}\n\n/** Resolves the matrix, stages it into home-scoped worktree storage, and publishes one generation. */\nexport async function synchronize(\n args: string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n output: SyncOutput = process.stdout,\n commandOptions: SyncCommandOptions = {},\n): Promise<number> {\n const check = args.includes(CHECK_OPTION);\n const force = args.includes(FORCE_OPTION);\n const rest = args.slice(1).filter((argument) => ![CHECK_OPTION, FORCE_OPTION, GLOBAL_OPTION].includes(argument));\n const homeDirectory = commandOptions.homeDirectory ?? environment.HOME ?? os.homedir();\n const roots = resolveSyncRoots(args, environment, currentDirectory, homeDirectory);\n const { globalOnly, globalRoot, targetRoot: repoRoot } = roots;\n const scopedEnvironment = environmentForSyncScope(environment, globalOnly);\n if (globalOnly && !check) fs.mkdirSync(globalRoot, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n // Sync tolerates keys it does not recognise so a config written against a\n // different version cannot break a build. `doompi doctor` reports them.\n const modes = loadMajorModesConfigLenient(repoRoot, homeDirectory);\n const configDiagnostics = [...loadDoomConfigLenient(repoRoot, homeDirectory).diagnostics, ...modes.diagnostics];\n const defaultMajorMode = modes.config.defaultMajorMode;\n const defaultDomains = loadDomains(repoRoot, homeDirectory).defaultDomains;\n const parsed = parseHarnessArgs(\n rest,\n selectionEnvironment(repoRoot, scopedEnvironment, homeDirectory),\n globalOnly ? globalRoot : currentDirectory,\n defaultMajorMode,\n defaultDomains,\n );\n const selection = toSelection(parsed.options);\n const agentDirectory = piAgentDirectory(scopedEnvironment, homeDirectory);\n if ((commandOptions.settingsMode ?? 'persisted') === 'persisted' && !check) {\n if (piExtensionDispatcherIsUpgradeable(agentDirectory)) {\n const previousVersion = piExtensionDispatcherVersion(agentDirectory);\n writePiExtensionAlias(agentDirectory);\n output.write(\n `repair: upgraded Pi user dispatcher from protocol ${String(previousVersion)} to ${String(PI_DISPATCHER_VERSION)}\\n`,\n );\n }\n const drift = piIntegrationDrift(agentDirectory);\n if (drift.length > 0) {\n throw new Error(`DoomPi Pi integration is not ready:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}`);\n }\n }\n writeConfigDiagnostics(configDiagnostics, output);\n if (check) {\n const majorModesConfig = modes.config;\n const missingPackages = missingLayerPackageSpecifiers(\n majorModesConfig,\n Object.keys(majorModesConfig.layers),\n createLayerResolvers(repoRoot),\n );\n if (missingPackages.length > 0) {\n output.write(\n `doompi sync is out of date:\\n${missingPackages\n .map((specifier) => ` configured package is not installed: ${specifier}`)\n .join('\\n')}\\n`,\n );\n return 1;\n }\n let located: ReturnType<typeof readLocatedSyncState>;\n try {\n located = readLocatedSyncState(repoRoot, homeDirectory);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n output.write(`doompi sync is out of date:\\n ${detail}\\n`);\n return 1;\n }\n const expectedCompositionFingerprint = selectionCompositionFingerprint(repoRoot, parsed.options, homeDirectory);\n const drift = collectDrift(\n repoRoot,\n selection,\n located?.state,\n scopedEnvironment,\n commandOptions.settingsMode ?? 'persisted',\n expectedCompositionFingerprint,\n );\n if (drift.length === 0) {\n output.write('doompi sync is up to date\\n');\n return 0;\n }\n output.write(`doompi sync is out of date:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}\\n`);\n return 1;\n }\n\n // Publishing an identical generation is not a no-op: it moves the\n // registration, so every attached cockpit reloads and the previous\n // generation becomes garbage. Same inputs, same published result.\n const driftOptions = {\n repoRoot,\n homeDirectory,\n requireWebBundle: Boolean(environment.DOOMPI_WEB_PACKAGE_ROOT),\n };\n const registrationNeedsMigration = syncRegistrationNeedsApiMigration(repoRoot, homeDirectory);\n if (!force && !registrationNeedsMigration && readSyncDrift(driftOptions).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n\n const progress = new SyncProgress(output);\n const releaseLock = commandOptions.lockHeld\n ? undefined\n : await acquireSyncLocationLock(resolveSyncLocation(repoRoot, homeDirectory));\n let result: SyncResult;\n try {\n // A concurrent publisher may have resolved the drift while this command\n // waited for the lock. Avoid moving the registration for no change.\n const registrationNeedsMigration = syncRegistrationNeedsApiMigration(repoRoot, homeDirectory);\n if (!force && !registrationNeedsMigration && readSyncDrift(driftOptions).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n result = await stageSync(repoRoot, parsed.options, scopedEnvironment, homeDirectory, progress, commandOptions);\n } finally {\n await releaseLock?.();\n }\n emitFrontendHooks(repoRoot, output);\n output.write(formatSyncResult(result, (commandOptions.settingsMode ?? 'persisted') === 'embedded' ? 'dpi' : 'pi'));\n return 0;\n}\n\nasync function stageSync(\n repoRoot: string,\n options: Omit<HarnessOptions, 'repoRoot'>,\n environment: NodeJS.ProcessEnv,\n homeDirectory: string,\n progress: SyncProgress,\n commandOptions: SyncCommandOptions = {},\n): Promise<SyncResult> {\n const location = resolveSyncLocation(repoRoot, homeDirectory);\n const generation = `${Date.now().toString(36)}-${crypto.randomUUID()}`;\n const directory = syncGenerationDirectory(location, generation);\n await fs.promises.mkdir(location.generationsDirectory, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n // The leaf is created without `recursive`, so an existing path is an error\n // rather than something to adopt: the cockpit signs and serves whatever the\n // published generation holds, and sync must only ever publish bytes it\n // wrote itself into a directory it just created.\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE });\n\n try {\n const staged = progress.start(SYNC_LABEL, 'resolving the matrix and staging resources');\n const context = await buildHarnessContext({\n ...options,\n repoRoot: location.root,\n homeDirectory,\n cwd: location.root,\n resourceDirectory: directory,\n });\n await ensureLayerPackages({\n repoRoot: location.root,\n config: context.majorModesConfig,\n layers: Object.keys(context.majorModesConfig.layers),\n environment,\n });\n staged(`${String(context.resources.skillCount)} skills, ${String(context.resources.agentCount)} agents`);\n const selection = toSelection(options);\n const resolvers = createLayerResolvers(location.root);\n const resolved = recordResolvedEntries(context.majorModesConfig, resolvers);\n const compositionFingerprint = selectionCompositionFingerprint(location.root, options, homeDirectory);\n const agentDirectory = piAgentDirectory(environment, homeDirectory);\n const persistedThemePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const themePath =\n (commandOptions.settingsMode ?? 'persisted') === 'persisted' ? persistedThemePath : context.defaultThemePath;\n const state: SyncState = {\n version: SYNC_STATE_VERSION,\n root: location.root,\n identity: location.identity,\n inputsHash: computeInputsHash(location.root, selection, homeDirectory),\n webSourcesHash: computeWebSourcesHash(resolved),\n compositionFingerprint,\n selection,\n env: recordedEnvironment(context.environment),\n fileState: {\n profileEnvironment: loadHarnessState(context.environment).state.profileEnvironment,\n pluginHooks: context.resources.pluginHooks,\n mcpProjection: context.resources.mcpProjection,\n },\n resolved,\n baseline: {\n mcpConfigPath: context.resources.mcpConfigPath,\n personaFile: context.environment[PERSONA_FILE_ENV],\n themePath,\n themeName: DEFAULT_THEME_NAME,\n },\n };\n\n // Runtime compilation writes the package dist files consumed by both the web\n // and server bundlers. Finish it first so a package clean cannot race either\n // consumer, then run the independent web and server builds together.\n let resolveCompositions!: (compositions: readonly ExtensionComposition[]) => void;\n let rejectCompositions!: (reason?: unknown) => void;\n const compositionsReady = new Promise<readonly ExtensionComposition[]>((resolve, reject) => {\n resolveCompositions = resolve;\n rejectCompositions = reject;\n });\n const runtimeProgress = progress.start(RUNTIME_LABEL, 'precompiling the mode bundles');\n const runtimeBuild = buildSyncedRuntime(location.root, environment, homeDirectory, {\n state,\n directory,\n onCompositionsResolved: resolveCompositions,\n }).then((synced) => {\n runtimeProgress(`${String(Object.keys(synced.bundles).length)} mode bundles`);\n return synced;\n });\n void runtimeBuild.catch(rejectCompositions);\n const webBuild = (async () => {\n await runtimeBuild;\n const webProgress = progress.start(WEB_LABEL, 'bundling the web cockpit plugins');\n const web = await syncWebBundle({\n repoRoot: location.root,\n resolvedEntries: state.resolved,\n environment,\n outputDirectory: path.join(directory, 'web-bundle'),\n onNotice: (message) => progress.line(WEB_LABEL, message),\n });\n if (web.status === 'failed') throw new Error(`Cockpit bundle failed: ${web.reason}`);\n webProgress(web.status === 'bundled' ? `cockpit bundled with plugins: ${web.pluginIds.join(', ')}` : web.reason);\n return web;\n })();\n const serverBuild = (async () => {\n const compositions = await compositionsReady;\n await runtimeBuild;\n const apiProgress = progress.start(API_LABEL, 'compiling the server bundle');\n const apiDirectory = path.join(directory, 'api');\n const fingerprint = crypto\n .createHash('sha256')\n .update(JSON.stringify([...new Set(compositions.map((composition) => composition.fingerprint))]))\n .digest('hex');\n const server = await syncServerBundle({\n repositoryRoot: location.root,\n generation,\n fingerprint,\n compositions,\n outputDirectory: apiDirectory,\n cacheDirectory: path.join(directory, 'cache'),\n sharedCacheDirectory: location.sharedCacheDirectory,\n });\n apiProgress(`${server.descriptor.entries.length} server facet(s) compiled`);\n for (const gap of server.contractGaps) progress.line(API_LABEL, `API contract incomplete: ${gap}`);\n return { server, fingerprint, apiDirectory };\n })();\n const [runtimeResult, webResult, serverResult] = await Promise.allSettled([runtimeBuild, webBuild, serverBuild]);\n if (runtimeResult.status === 'rejected') throw runtimeResult.reason;\n if (webResult.status === 'rejected') throw webResult.reason;\n if (serverResult.status === 'rejected') throw serverResult.reason;\n const synced = runtimeResult.value;\n const web = webResult.value;\n const { server, fingerprint, apiDirectory } = serverResult.value;\n const descriptorPath = path.join(apiDirectory, DOOM_SERVER_BUNDLE_FILE);\n const finalState: SyncState = {\n ...synced.state,\n serverBundle: {\n descriptorPath,\n fingerprint,\n compilerManifests: server.compilerManifests,\n sourcesHash: computeServerSourcesHash(synced.state.resolved),\n },\n };\n const statePath = await writeSyncState(\n location.root,\n finalState,\n homeDirectory,\n path.join(directory, 'state.json'),\n );\n const projectSettingsPath =\n (commandOptions.settingsMode ?? 'persisted') === 'persisted'\n ? writeProjectPiSettings(location.root, homeDirectory)\n : undefined;\n const result: SyncResult = {\n statePath,\n ...(projectSettingsPath ? { projectSettingsPath } : {}),\n selection,\n mcpServers: synced.state.baseline.mcpConfigPath ? readMcpServerNames(synced.state.baseline.mcpConfigPath) : [],\n skillCount: context.resources.skillCount,\n agentCount: context.resources.agentCount,\n };\n publishSyncRegistration(\n location.root,\n {\n version: SYNC_REGISTRATION_VERSION,\n root: location.root,\n identity: location.identity,\n generation,\n generationRoot: directory,\n statePath,\n stateSha256: syncStateSha256(statePath),\n webDirectory: web.status === 'bundled' ? web.assetsDir : null,\n apiDirectory,\n serverBundle: { path: descriptorPath, fingerprint, sha256: syncStateSha256(descriptorPath) },\n package: packageRegistrationFor(),\n },\n homeDirectory,\n );\n // ponytail: retain generations until host-owned drain evidence can prove no session uses them.\n // Directory age and an open-file check cannot establish that a lazy import is finished.\n return result;\n } catch (error) {\n await fs.promises.rm(directory, { recursive: true, force: true });\n throw error;\n }\n}\n\n/** Compatibility API. Executables call the command function directly. */\nexport class SyncCommand {\n readonly name = SYNC_COMMAND;\n private readonly settingsMode: SyncSettingsMode;\n private readonly homeDirectory: string | undefined;\n private readonly lockHeld: boolean;\n\n constructor(options: SyncCommandOptions = {}) {\n this.settingsMode = options.settingsMode ?? 'persisted';\n this.homeDirectory = options.homeDirectory;\n this.lockHeld = options.lockHeld ?? false;\n }\n\n matches(args: string[]): boolean {\n return args[0] === this.name;\n }\n\n async execute(\n args: string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n output: SyncOutput = process.stdout,\n ): Promise<number> {\n return synchronize(args, environment, currentDirectory, output, {\n settingsMode: this.settingsMode,\n homeDirectory: this.homeDirectory,\n lockHeld: this.lockHeld,\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["loadDoomConfigLenient"],"sources":["../../../../src/cli/commands/sync/index.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\nimport { loadDomains } from '@agimon-ai/doompi-config/domains';\nimport { filterHookDisabledLayers, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport { loadMajorModesConfig, loadMajorModesConfigLenient } from '@agimon-ai/doompi-config/majorModes';\nimport type { ConfigDiagnostic } from '@agimon-ai/doompi-config/types';\nimport {\n mergePiSettings,\n piAgentDirectory,\n piThemeDirectory,\n readPiSettings,\n serializePiSettings,\n} from '@agimon-ai/doompi-core/runtime-pi-settings';\nimport { DOOM_SERVER_BUNDLE_FILE } from '@agimon-ai/doompi-core/server-facet';\nimport {\n acquireSyncLocationLock,\n resolveSyncLocation,\n syncGenerationDirectory,\n} from '@agimon-ai/doompi-core/sync-location';\nimport {\n DOOMPI_API_VERSION,\n publishSyncRegistration,\n readSyncRegistration,\n SYNC_REGISTRATION_VERSION,\n syncStateSha256,\n type SyncPackageRegistration,\n} from '@agimon-ai/doompi-core/sync-registration';\nimport { DEFAULT_THEME, DEFAULT_THEME_NAME } from '@agimon-ai/doompi-ui/theme';\n\nimport { buildSyncedRuntime } from '../../../builders/cli';\nimport { readBootstrapStatus } from '../../../builders/cli/bootstrapLocator';\nimport {\n createLayerResolvers,\n type ExtensionComposition,\n PERSONA_ENTRY,\n resolveExtensionComposition,\n} from '../../../builders/cli/extensionAssembler';\nimport { buildHarnessContext } from '../../../builders/cli/harnessContext';\nimport {\n doomPiPackageRoot,\n piExtensionAliasIsCurrent,\n writePiExtensionAlias,\n} from '../../../builders/cli/piExtensionAlias';\nimport { piExtensionDispatcherIsUpgradeable } from '../../../builders/cli/piExtensionDispatcher';\nimport {\n DUPLICATE_REGISTRATION_DRIFT,\n projectRegistersDoom,\n writeProjectPiSettings,\n} from '../../../builders/cli/projectSettings';\nimport { syncServerBundle } from '../../../builders/server';\nimport { syncWebBundle } from '../../../builders/web';\nimport { HARNESS_STATE_POINTER, loadHarnessState } from '../../../composition/harnessState';\nimport { ensureLayerPackages, missingLayerPackageSpecifiers } from '../../../composition/layerPackageInstaller';\nimport { loadDoomConfigLenient } from '../../../composition/projectTrust';\nimport { resolveDoomConfigurationRoot } from '../../../composition/repository';\nimport { readSyncDrift } from '../../../composition/syncDrift';\nimport {\n computeInputsHash,\n computeWebSourcesHash,\n computeServerSourcesHash,\n readLocatedSyncState,\n readMcpServerNames,\n recordResolvedEntries,\n SYNC_STATE_VERSION,\n type SyncSelection,\n type SyncState,\n syncStateRootMatches,\n writeSyncState,\n} from '../../../composition/syncState';\nimport type { HarnessOptions } from '../../../composition/types/harness';\nimport { DOOMPI_DOMAINS_ENV, DOOMPI_MAJOR_MODE_ENV, DOOMPI_PROFILE_ENV } from '../../matrixOptions';\nimport { parseHarnessArgs } from '../../options';\nimport { SyncProgress, type SyncProgressOutput } from './presenter';\n\n/**\n * `doom-pi sync`: resolve the matrix once and write it where plain Pi finds it.\n *\n * The doom-emacs split. Everything that needs a real Node process (module\n * resolution, staging skills and agents, generating the MCP config) happens\n * here, and the doom-pi extension then only reads what this produced. The\n * launcher is untouched and keeps resolving the same matrix per run.\n */\n\nconst SYNC_COMMAND = 'sync';\nconst CHECK_OPTION = '--check';\n/** Republishes even when nothing drifted, for a generation suspected of being damaged. */\nconst FORCE_OPTION = '--force';\nconst GLOBAL_OPTION = '--global';\nconst HARNESS_ROOT_ENV = 'DOOMPI_ROOT';\nconst PERSONA_FILE_ENV = 'DOOMPI_PERSONA_FILE';\nconst HOOK_EMITTER = path.join('tools', 'harness', 'emit-hooks.mjs');\nconst NONE = '(none)';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst SYNC_LABEL = 'sync';\nconst RUNTIME_LABEL = 'runtime';\nconst WEB_LABEL = 'web';\nconst API_LABEL = 'api';\n\nexport interface SyncRoots {\n globalOnly: boolean;\n globalRoot: string;\n sourceRoot: string;\n targetRoot: string;\n}\n\n/** Resolves the configuration source and publication destination for one sync. */\nexport function resolveSyncRoots(\n args: readonly string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n homeDirectory = environment.HOME ?? os.homedir(),\n): SyncRoots {\n const globalRoot = globalDoomConfigDirectory(homeDirectory);\n const inheritedRoot = environment[HARNESS_ROOT_ENV];\n const sourceRoot = inheritedRoot\n ? path.resolve(inheritedRoot)\n : resolveDoomConfigurationRoot(currentDirectory, homeDirectory);\n const globalOnly = args.includes(GLOBAL_OPTION);\n return { globalOnly, globalRoot, sourceRoot, targetRoot: globalOnly ? globalRoot : sourceRoot };\n}\n\nconst GLOBAL_SCOPE_INHERITED_KEYS = [\n HARNESS_ROOT_ENV,\n HARNESS_STATE_POINTER,\n DOOMPI_MAJOR_MODE_ENV,\n DOOMPI_DOMAINS_ENV,\n DOOMPI_PROFILE_ENV,\n] as const;\n\n/** Removes workspace-only state before a global runtime is resolved. */\nexport function environmentForSyncScope(environment: NodeJS.ProcessEnv, globalOnly: boolean): NodeJS.ProcessEnv {\n if (!globalOnly) return environment;\n const scoped = { ...environment };\n for (const key of GLOBAL_SCOPE_INHERITED_KEYS) delete scoped[key];\n return scoped;\n}\n\n/**\n * Harness variables worth recording, by prefix or exact name.\n *\n * An allowlist rather than the whole environment: the state file is a snapshot\n * of resolved configuration, and dumping `process.env` into it would write\n * every credential the sync happened to run with onto disk.\n */\nconst RECORDED_PREFIXES = ['DOOMPI_'];\nconst RECORDED_KEYS = ['CLAUDE_PROJECT_DIR', 'CODEX_REPO_ROOT', 'ORIGINAL_REPO_PATH', 'MCP_UI_VIEWER'];\n/**\n * Launcher-only values a synced session must not inherit.\n *\n * The child extension list is recomposed on every load, and the subagent binary\n * points at `pi.sh`, which a session started as plain `pi` should not shell out\n * to: Doom Team resolves Pi's own CLI when the variable is absent.\n */\nconst EXCLUDED_KEYS = new Set([\n 'DOOMPI_CHILD_EXTENSIONS',\n 'DOOMPI_COMPOSED',\n 'DOOMPI_MUTE',\n 'DOOMPI_TEMP_DIR',\n // A pointer to the syncing process's own state file. Recording it would hand\n // every later session a path to a state that died with this one.\n HARNESS_STATE_POINTER,\n 'PI_SUBAGENT_PI_BINARY',\n]);\n\ntype SyncOutput = SyncProgressOutput;\n\nexport type SyncSettingsMode = 'persisted' | 'embedded';\n\nexport interface SyncCommandOptions {\n settingsMode?: SyncSettingsMode;\n /** Test/embedding override; normal CLI execution uses the process home. */\n homeDirectory?: string;\n /** Internal pipeline seam when the caller owns the worktree lock. */\n lockHeld?: boolean;\n}\n\nexport interface SyncResult {\n statePath: string;\n /** Omitted when DPI supplies the integration as a process-local overlay. */\n settingsPath?: string;\n /** Set only when the repository still carried its own DoomPi registration. */\n projectSettingsPath?: string;\n selection: SyncSelection;\n mcpServers: string[];\n skillCount: number;\n agentCount: number;\n}\n\nexport function recordedEnvironment(environment: NodeJS.ProcessEnv): Record<string, string> {\n const recorded: Record<string, string> = {};\n for (const [key, value] of Object.entries(environment)) {\n if (value === undefined || EXCLUDED_KEYS.has(key)) continue;\n if (RECORDED_KEYS.includes(key) || RECORDED_PREFIXES.some((prefix) => key.startsWith(prefix))) {\n recorded[key] = value;\n }\n }\n return recorded;\n}\n\n/**\n * Reports the config keys sync chose to ignore.\n *\n * Never fatal. A key nobody recognises is usually a config written for another\n * version of a layer, and refusing to build over it is worse than proceeding\n * without it. The strict check lives in `doompi doctor`.\n */\nfunction writeConfigDiagnostics(diagnostics: readonly ConfigDiagnostic[], output: SyncOutput): void {\n if (diagnostics.length === 0) return;\n const lines = diagnostics.map((entry) => ` ${entry.filePath}: ${entry.path}`).join('\\n');\n output.write(\n `config: ignored ${String(diagnostics.length)} unsupported key(s); run doompi doctor for the strict check\\n${lines}\\n`,\n );\n}\n/**\n * Layers the repository's declared selection under the usual resolution.\n *\n * `.doom/config.yaml` holds what the repository selects by default, the way\n * init.el does for doom-emacs. Seeding the environment the parser reads keeps\n * the precedence the launcher already documents: an explicit flag wins, then an\n * exported variable, then the declared default.\n */\nexport function selectionEnvironment(\n repoRoot: string,\n environment: NodeJS.ProcessEnv,\n homeDirectory?: string,\n): NodeJS.ProcessEnv {\n const { selection } = loadDoomConfigLenient(repoRoot, homeDirectory).config;\n if (!selection) return environment;\n return {\n ...environment,\n ...(selection.majorMode && !environment[DOOMPI_MAJOR_MODE_ENV]\n ? { [DOOMPI_MAJOR_MODE_ENV]: selection.majorMode }\n : {}),\n ...(selection.profile && !environment[DOOMPI_PROFILE_ENV] ? { [DOOMPI_PROFILE_ENV]: selection.profile } : {}),\n ...(selection.domains && environment[DOOMPI_DOMAINS_ENV] === undefined\n ? { [DOOMPI_DOMAINS_ENV]: selection.domains.join(',') }\n : {}),\n };\n}\n\nexport function toSelection(\n options: Pick<HarnessOptions, 'majorMode' | 'domains' | 'profile' | 'preset'>,\n): SyncSelection {\n return {\n majorMode: options.majorMode,\n domains: options.domains,\n profile: options.profile,\n preset: options.preset,\n };\n}\n\nexport function selectionCompositionFingerprint(\n repoRoot: string,\n options: Pick<HarnessOptions, 'agents' | 'hooks' | 'majorMode' | 'mcp' | 'preset'>,\n homeDirectory: string = os.homedir(),\n): string {\n const majorModesConfig = loadMajorModesConfig(repoRoot, homeDirectory);\n const resolvers = createLayerResolvers(repoRoot);\n return resolveExtensionComposition({\n agents: options.agents,\n autoStop: false,\n mute: false,\n preset: options.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorMode: options.majorMode,\n layers: filterHookDisabledLayers(\n majorModesConfig,\n resolveLayers(majorModesConfig, options.majorMode),\n options.hooks,\n ),\n majorModesConfig,\n resolvers,\n }).fingerprint;\n}\n\n/** Settings, dispatcher and theme differences an init would fix, independent of sync state. */\nfunction piIntegrationDrift(agentDirectory: string): string[] {\n const drift: string[] = [];\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const settings = readPiSettings(agentDirectory);\n const merged = mergePiSettings(settings, agentDirectory, { themePath, themeName: DEFAULT_THEME_NAME });\n if (serializePiSettings(merged) !== serializePiSettings(settings)) {\n drift.push('Pi user settings are out of date; run doompi init');\n }\n if (!piExtensionAliasIsCurrent(agentDirectory)) drift.push('Pi user dispatcher is out of date; run doompi init');\n const expectedTheme = `${JSON.stringify(DEFAULT_THEME, null, 2)}\\n`;\n if (!fs.existsSync(themePath) || fs.readFileSync(themePath, 'utf8') !== expectedTheme) {\n drift.push('Pi user theme is out of date; run doompi init');\n }\n return drift;\n}\n\n/** Differences between what a sync would produce and what is on disk. */\nexport function collectDrift(\n repoRoot: string,\n selection: SyncSelection,\n state: SyncState | undefined,\n environment: NodeJS.ProcessEnv = process.env,\n settingsMode: SyncSettingsMode = 'persisted',\n expectedCompositionFingerprint?: string,\n): string[] {\n if (!state) return ['no sync state: run doompi sync'];\n const drift: string[] = [];\n if (syncRegistrationNeedsApiMigration(repoRoot, environment.HOME ?? os.homedir())) {\n drift.push('DoomPi registration needs API migration');\n }\n if (!syncStateRootMatches(repoRoot, state.root)) drift.push('sync state belongs to a different repository');\n const recorded = state.selection;\n if (\n recorded.majorMode !== selection.majorMode ||\n recorded.profile !== selection.profile ||\n recorded.preset !== selection.preset ||\n recorded.domains.join(',') !== selection.domains.join(',')\n ) {\n drift.push('selection changed since the last sync');\n }\n // Hashed against the recorded selection, not the requested one, so a\n // selection change is reported once rather than as two findings.\n if (computeInputsHash(repoRoot, recorded, environment.HOME ?? os.homedir()) !== state.inputsHash) {\n drift.push('.doom configuration changed');\n }\n // Re-resolving is what catches a dependency upgrade moving a package, which\n // the inputs hash deliberately does not read.\n if (\n JSON.stringify(\n recordResolvedEntries(\n loadMajorModesConfig(repoRoot, environment.HOME ?? os.homedir()),\n createLayerResolvers(repoRoot),\n ),\n ) !== JSON.stringify(state.resolved)\n ) {\n drift.push('resolved extension paths changed');\n }\n if (expectedCompositionFingerprint && state.compositionFingerprint !== expectedCompositionFingerprint) {\n drift.push('extension composition changed');\n }\n try {\n if (!readBootstrapStatus(repoRoot, undefined, environment.HOME ?? os.homedir()).fresh) {\n drift.push('precompiled runtime is missing or stale');\n }\n } catch {\n drift.push('precompiled runtime is missing or stale');\n }\n\n if (settingsMode === 'persisted') {\n const agentDirectory = piAgentDirectory(environment);\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n drift.push(...piIntegrationDrift(agentDirectory));\n if (projectRegistersDoom(repoRoot)) drift.push(DUPLICATE_REGISTRATION_DRIFT);\n if (state.baseline.themePath !== themePath || state.baseline.themeName !== DEFAULT_THEME_NAME) {\n drift.push('synced theme location is out of date');\n }\n }\n if (\n readSyncDrift({ repoRoot, homeDirectory: environment.HOME ?? os.homedir() }).reasons.includes('server-bundle-stale')\n ) {\n drift.push('server bundle is missing or stale');\n }\n return drift;\n}\n\n/** Regenerates the hook files the other frontends read before any harness code runs. */\nfunction emitFrontendHooks(repoRoot: string, output: SyncOutput): void {\n const emitter = path.join(repoRoot, HOOK_EMITTER);\n if (!fs.existsSync(emitter)) return;\n const result = spawnSync(process.execPath, [emitter, '--write'], { cwd: repoRoot, encoding: 'utf8' });\n if (result.status === 0) {\n output.write('hooks: regenerated for Claude Code and Codex\\n');\n return;\n }\n output.write(`hooks: emit-hooks failed (${result.stderr?.trim() || `exit ${String(result.status)}`})\\n`);\n}\n\nexport function formatSyncResult(result: SyncResult, runner = 'pi'): string {\n const { selection } = result;\n return [\n `mode: ${selection.majorMode}`,\n `domains: ${selection.domains.join(', ') || NONE}`,\n `profile: ${selection.profile ?? NONE}`,\n `skills: ${result.skillCount}`,\n `agents: ${result.agentCount}`,\n `mcp: ${result.mcpServers.join(', ') || NONE}`,\n `state: ${result.statePath}`,\n ...(result.settingsPath ? [`settings: ${result.settingsPath}`] : []),\n ...(result.projectSettingsPath\n ? [`project: removed duplicate registration from ${result.projectSettingsPath}`]\n : []),\n '',\n `Run ${runner} from the repository root to use it.`,\n '',\n ].join('\\n');\n}\n\n/**\n * The DoomPi that produced this generation, which is the one that can load it.\n *\n * Always the executing package, never another copy the repository happens to\n * install. A generation is not portable between two installations: the bundles\n * are compiled from the building package's own extension entries, the recorded\n * compiler inputs are its files, and the state names its bootstrap entry. Naming\n * a second copy here publishes a registration whose package disagrees with the\n * state it points at, and Pi's dispatcher then loads a harness that rejects the\n * bootstrap as stale on every session, with no sync able to fix it.\n *\n * A repository that wants its own copy to own its sessions runs sync with that\n * copy's CLI, which makes it the executing package.\n */\nfunction packageRegistrationFor(): SyncPackageRegistration {\n const root = fs.realpathSync(doomPiPackageRoot());\n const manifestPath = path.join(root, 'package.json');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as {\n version?: unknown;\n doompiApiVersion?: unknown;\n pi?: { extensions?: unknown };\n };\n const version = manifest.version;\n const apiVersion = manifest.doompiApiVersion;\n const extensions = manifest.pi?.extensions;\n const extension = Array.isArray(extensions) ? extensions.find((value) => typeof value === 'string') : undefined;\n if (\n typeof version !== 'string' ||\n typeof apiVersion !== 'number' ||\n !Number.isSafeInteger(apiVersion) ||\n apiVersion < 1 ||\n apiVersion !== DOOMPI_API_VERSION ||\n typeof extension !== 'string'\n ) {\n throw new Error(`Installed DoomPi package at ${root} has no supported API-versioned Pi extension entry`);\n }\n return {\n root,\n version,\n apiVersion: apiVersion as number,\n manifestPath,\n entry: fs.realpathSync(path.resolve(root, extension)),\n };\n}\n\n/** Returns true when a valid legacy registration must be republished with API metadata. */\nexport function syncRegistrationNeedsApiMigration(repoRoot: string, homeDirectory: string): boolean {\n try {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n return (\n registration !== undefined &&\n (registration.version !== SYNC_REGISTRATION_VERSION || registration.package.apiVersion === undefined)\n );\n } catch {\n return false;\n }\n}\n\n/** Resolves the matrix, stages it into home-scoped worktree storage, and publishes one generation. */\nexport async function synchronize(\n args: string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n output: SyncOutput = process.stdout,\n commandOptions: SyncCommandOptions = {},\n): Promise<number> {\n const check = args.includes(CHECK_OPTION);\n const force = args.includes(FORCE_OPTION);\n const rest = args.slice(1).filter((argument) => ![CHECK_OPTION, FORCE_OPTION, GLOBAL_OPTION].includes(argument));\n const homeDirectory = commandOptions.homeDirectory ?? environment.HOME ?? os.homedir();\n const roots = resolveSyncRoots(args, environment, currentDirectory, homeDirectory);\n const { globalOnly, globalRoot, targetRoot: repoRoot } = roots;\n const scopedEnvironment = environmentForSyncScope(environment, globalOnly);\n if (globalOnly && !check) fs.mkdirSync(globalRoot, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n // Sync tolerates keys it does not recognise so a config written against a\n // different version cannot break a build. `doompi doctor` reports them.\n const modes = loadMajorModesConfigLenient(repoRoot, homeDirectory);\n const configDiagnostics = [...loadDoomConfigLenient(repoRoot, homeDirectory).diagnostics, ...modes.diagnostics];\n const defaultMajorMode = modes.config.defaultMajorMode;\n const defaultDomains = loadDomains(repoRoot, homeDirectory).defaultDomains;\n const parsed = parseHarnessArgs(\n rest,\n selectionEnvironment(repoRoot, scopedEnvironment, homeDirectory),\n globalOnly ? globalRoot : currentDirectory,\n defaultMajorMode,\n defaultDomains,\n );\n const selection = toSelection(parsed.options);\n const agentDirectory = piAgentDirectory(scopedEnvironment, homeDirectory);\n if ((commandOptions.settingsMode ?? 'persisted') === 'persisted' && !check) {\n if (piExtensionDispatcherIsUpgradeable(agentDirectory)) {\n writePiExtensionAlias(agentDirectory);\n output.write('repair: refreshed Pi user dispatcher\\n');\n }\n const drift = piIntegrationDrift(agentDirectory);\n if (drift.length > 0) {\n throw new Error(`DoomPi Pi integration is not ready:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}`);\n }\n }\n writeConfigDiagnostics(configDiagnostics, output);\n if (check) {\n const majorModesConfig = modes.config;\n const missingPackages = missingLayerPackageSpecifiers(\n majorModesConfig,\n Object.keys(majorModesConfig.layers),\n createLayerResolvers(repoRoot),\n );\n if (missingPackages.length > 0) {\n output.write(\n `doompi sync is out of date:\\n${missingPackages\n .map((specifier) => ` configured package is not installed: ${specifier}`)\n .join('\\n')}\\n`,\n );\n return 1;\n }\n let located: ReturnType<typeof readLocatedSyncState>;\n try {\n located = readLocatedSyncState(repoRoot, homeDirectory);\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n output.write(`doompi sync is out of date:\\n ${detail}\\n`);\n return 1;\n }\n const expectedCompositionFingerprint = selectionCompositionFingerprint(repoRoot, parsed.options, homeDirectory);\n const drift = collectDrift(\n repoRoot,\n selection,\n located?.state,\n scopedEnvironment,\n commandOptions.settingsMode ?? 'persisted',\n expectedCompositionFingerprint,\n );\n if (drift.length === 0) {\n output.write('doompi sync is up to date\\n');\n return 0;\n }\n output.write(`doompi sync is out of date:\\n${drift.map((entry) => ` ${entry}`).join('\\n')}\\n`);\n return 1;\n }\n\n // Publishing an identical generation is not a no-op: it moves the\n // registration, so every attached cockpit reloads and the previous\n // generation becomes garbage. Same inputs, same published result.\n const driftOptions = {\n repoRoot,\n homeDirectory,\n requireWebBundle: Boolean(environment.DOOMPI_WEB_PACKAGE_ROOT),\n };\n const registrationNeedsMigration = syncRegistrationNeedsApiMigration(repoRoot, homeDirectory);\n if (!force && !registrationNeedsMigration && readSyncDrift(driftOptions).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n\n const progress = new SyncProgress(output);\n const releaseLock = commandOptions.lockHeld\n ? undefined\n : await acquireSyncLocationLock(resolveSyncLocation(repoRoot, homeDirectory));\n let result: SyncResult;\n try {\n // A concurrent publisher may have resolved the drift while this command\n // waited for the lock. Avoid moving the registration for no change.\n const registrationNeedsMigration = syncRegistrationNeedsApiMigration(repoRoot, homeDirectory);\n if (!force && !registrationNeedsMigration && readSyncDrift(driftOptions).fresh) {\n output.write('doompi sync is already up to date\\n');\n return 0;\n }\n result = await stageSync(repoRoot, parsed.options, scopedEnvironment, homeDirectory, progress, commandOptions);\n } finally {\n await releaseLock?.();\n }\n emitFrontendHooks(repoRoot, output);\n output.write(formatSyncResult(result, (commandOptions.settingsMode ?? 'persisted') === 'embedded' ? 'dpi' : 'pi'));\n return 0;\n}\n\nasync function stageSync(\n repoRoot: string,\n options: Omit<HarnessOptions, 'repoRoot'>,\n environment: NodeJS.ProcessEnv,\n homeDirectory: string,\n progress: SyncProgress,\n commandOptions: SyncCommandOptions = {},\n): Promise<SyncResult> {\n const location = resolveSyncLocation(repoRoot, homeDirectory);\n const generation = `${Date.now().toString(36)}-${crypto.randomUUID()}`;\n const directory = syncGenerationDirectory(location, generation);\n await fs.promises.mkdir(location.generationsDirectory, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });\n // The leaf is created without `recursive`, so an existing path is an error\n // rather than something to adopt: the cockpit signs and serves whatever the\n // published generation holds, and sync must only ever publish bytes it\n // wrote itself into a directory it just created.\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE });\n\n try {\n const staged = progress.start(SYNC_LABEL, 'resolving the matrix and staging resources');\n const context = await buildHarnessContext({\n ...options,\n repoRoot: location.root,\n homeDirectory,\n cwd: location.root,\n resourceDirectory: directory,\n });\n await ensureLayerPackages({\n repoRoot: location.root,\n config: context.majorModesConfig,\n layers: Object.keys(context.majorModesConfig.layers),\n environment,\n });\n staged(`${String(context.resources.skillCount)} skills, ${String(context.resources.agentCount)} agents`);\n const selection = toSelection(options);\n const resolvers = createLayerResolvers(location.root);\n const resolved = recordResolvedEntries(context.majorModesConfig, resolvers);\n const compositionFingerprint = selectionCompositionFingerprint(location.root, options, homeDirectory);\n const agentDirectory = piAgentDirectory(environment, homeDirectory);\n const persistedThemePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n const themePath =\n (commandOptions.settingsMode ?? 'persisted') === 'persisted' ? persistedThemePath : context.defaultThemePath;\n const state: SyncState = {\n version: SYNC_STATE_VERSION,\n root: location.root,\n identity: location.identity,\n inputsHash: computeInputsHash(location.root, selection, homeDirectory),\n webSourcesHash: computeWebSourcesHash(resolved),\n compositionFingerprint,\n selection,\n env: recordedEnvironment(context.environment),\n fileState: {\n profileEnvironment: loadHarnessState(context.environment).state.profileEnvironment,\n pluginHooks: context.resources.pluginHooks,\n mcpProjection: context.resources.mcpProjection,\n },\n resolved,\n baseline: {\n mcpConfigPath: context.resources.mcpConfigPath,\n personaFile: context.environment[PERSONA_FILE_ENV],\n themePath,\n themeName: DEFAULT_THEME_NAME,\n },\n };\n\n // Runtime compilation writes the package dist files consumed by both the web\n // and server bundlers. Finish it first so a package clean cannot race either\n // consumer, then run the independent web and server builds together.\n let resolveCompositions!: (compositions: readonly ExtensionComposition[]) => void;\n let rejectCompositions!: (reason?: unknown) => void;\n const compositionsReady = new Promise<readonly ExtensionComposition[]>((resolve, reject) => {\n resolveCompositions = resolve;\n rejectCompositions = reject;\n });\n const runtimeProgress = progress.start(RUNTIME_LABEL, 'precompiling the mode bundles');\n const runtimeBuild = buildSyncedRuntime(location.root, environment, homeDirectory, {\n state,\n directory,\n onCompositionsResolved: resolveCompositions,\n }).then((synced) => {\n runtimeProgress(`${String(Object.keys(synced.bundles).length)} mode bundles`);\n return synced;\n });\n void runtimeBuild.catch(rejectCompositions);\n const webBuild = (async () => {\n await runtimeBuild;\n const webProgress = progress.start(WEB_LABEL, 'bundling the web cockpit plugins');\n const web = await syncWebBundle({\n repoRoot: location.root,\n resolvedEntries: state.resolved,\n environment,\n outputDirectory: path.join(directory, 'web-bundle'),\n onNotice: (message) => progress.line(WEB_LABEL, message),\n });\n if (web.status === 'failed') throw new Error(`Cockpit bundle failed: ${web.reason}`);\n webProgress(web.status === 'bundled' ? `cockpit bundled with plugins: ${web.pluginIds.join(', ')}` : web.reason);\n return web;\n })();\n const serverBuild = (async () => {\n const compositions = await compositionsReady;\n await runtimeBuild;\n const apiProgress = progress.start(API_LABEL, 'compiling the server bundle');\n const apiDirectory = path.join(directory, 'api');\n const fingerprint = crypto\n .createHash('sha256')\n .update(JSON.stringify([...new Set(compositions.map((composition) => composition.fingerprint))]))\n .digest('hex');\n const server = await syncServerBundle({\n repositoryRoot: location.root,\n generation,\n fingerprint,\n compositions,\n outputDirectory: apiDirectory,\n cacheDirectory: path.join(directory, 'cache'),\n sharedCacheDirectory: location.sharedCacheDirectory,\n });\n apiProgress(`${server.descriptor.entries.length} server facet(s) compiled`);\n for (const gap of server.contractGaps) progress.line(API_LABEL, `API contract incomplete: ${gap}`);\n return { server, fingerprint, apiDirectory };\n })();\n const [runtimeResult, webResult, serverResult] = await Promise.allSettled([runtimeBuild, webBuild, serverBuild]);\n if (runtimeResult.status === 'rejected') throw runtimeResult.reason;\n if (webResult.status === 'rejected') throw webResult.reason;\n if (serverResult.status === 'rejected') throw serverResult.reason;\n const synced = runtimeResult.value;\n const web = webResult.value;\n const { server, fingerprint, apiDirectory } = serverResult.value;\n const descriptorPath = path.join(apiDirectory, DOOM_SERVER_BUNDLE_FILE);\n const finalState: SyncState = {\n ...synced.state,\n serverBundle: {\n descriptorPath,\n fingerprint,\n compilerManifests: server.compilerManifests,\n sourcesHash: computeServerSourcesHash(synced.state.resolved),\n },\n };\n const statePath = await writeSyncState(\n location.root,\n finalState,\n homeDirectory,\n path.join(directory, 'state.json'),\n );\n const projectSettingsPath =\n (commandOptions.settingsMode ?? 'persisted') === 'persisted'\n ? writeProjectPiSettings(location.root, homeDirectory)\n : undefined;\n const result: SyncResult = {\n statePath,\n ...(projectSettingsPath ? { projectSettingsPath } : {}),\n selection,\n mcpServers: synced.state.baseline.mcpConfigPath ? readMcpServerNames(synced.state.baseline.mcpConfigPath) : [],\n skillCount: context.resources.skillCount,\n agentCount: context.resources.agentCount,\n };\n publishSyncRegistration(\n location.root,\n {\n version: SYNC_REGISTRATION_VERSION,\n root: location.root,\n identity: location.identity,\n generation,\n generationRoot: directory,\n statePath,\n stateSha256: syncStateSha256(statePath),\n webDirectory: web.status === 'bundled' ? web.assetsDir : null,\n apiDirectory,\n serverBundle: { path: descriptorPath, fingerprint, sha256: syncStateSha256(descriptorPath) },\n package: packageRegistrationFor(),\n },\n homeDirectory,\n );\n // ponytail: retain generations until host-owned drain evidence can prove no session uses them.\n // Directory age and an open-file check cannot establish that a lazy import is finished.\n return result;\n } catch (error) {\n await fs.promises.rm(directory, { recursive: true, force: true });\n throw error;\n }\n}\n\n/** Compatibility API. Executables call the command function directly. */\nexport class SyncCommand {\n readonly name = SYNC_COMMAND;\n private readonly settingsMode: SyncSettingsMode;\n private readonly homeDirectory: string | undefined;\n private readonly lockHeld: boolean;\n\n constructor(options: SyncCommandOptions = {}) {\n this.settingsMode = options.settingsMode ?? 'persisted';\n this.homeDirectory = options.homeDirectory;\n this.lockHeld = options.lockHeld ?? false;\n }\n\n matches(args: string[]): boolean {\n return args[0] === this.name;\n }\n\n async execute(\n args: string[],\n environment: NodeJS.ProcessEnv = process.env,\n currentDirectory = process.cwd(),\n output: SyncOutput = process.stdout,\n ): Promise<number> {\n return synchronize(args, environment, currentDirectory, output, {\n settingsMode: this.settingsMode,\n homeDirectory: this.homeDirectory,\n lockHeld: this.lockHeld,\n });\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi",
3
- "version": "0.0.1-alpha.77",
3
+ "version": "0.0.1-alpha.79",
4
4
  "description": "Opinionated, composable Pi distribution for scoped agent tools, skills, MCP servers, and developer workflows.",
5
5
  "keywords": [
6
6
  "ai",
@@ -277,19 +277,19 @@
277
277
  "registry": "https://registry.npmjs.org/"
278
278
  },
279
279
  "dependencies": {
280
- "@agimon-ai/doompi-autostop": "0.0.1-alpha.52",
281
- "@agimon-ai/doompi-cache": "0.0.1-alpha.41",
282
- "@agimon-ai/doompi-config": "0.0.1-alpha.74",
283
- "@agimon-ai/doompi-core": "0.0.1-alpha.75",
284
- "@agimon-ai/doompi-domain": "0.0.1-alpha.53",
285
- "@agimon-ai/doompi-major-mode": "0.0.1-alpha.53",
286
- "@agimon-ai/doompi-minor-mode": "0.0.1-alpha.75",
287
- "@agimon-ai/doompi-notification": "0.0.1-alpha.52",
288
- "@agimon-ai/doompi-profile": "0.0.1-alpha.53",
289
- "@agimon-ai/doompi-skill": "0.0.1-alpha.53",
290
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.72",
291
- "@agimon-ai/doompi-ui": "0.0.1-alpha.75",
292
- "@agimon-ai/doompi-web-security": "0.0.1-alpha.35",
280
+ "@agimon-ai/doompi-autostop": "0.0.1-alpha.53",
281
+ "@agimon-ai/doompi-cache": "0.0.1-alpha.42",
282
+ "@agimon-ai/doompi-config": "0.0.1-alpha.75",
283
+ "@agimon-ai/doompi-core": "0.0.1-alpha.76",
284
+ "@agimon-ai/doompi-domain": "0.0.1-alpha.54",
285
+ "@agimon-ai/doompi-major-mode": "0.0.1-alpha.54",
286
+ "@agimon-ai/doompi-minor-mode": "0.0.1-alpha.76",
287
+ "@agimon-ai/doompi-notification": "0.0.1-alpha.53",
288
+ "@agimon-ai/doompi-profile": "0.0.1-alpha.54",
289
+ "@agimon-ai/doompi-skill": "0.0.1-alpha.54",
290
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.73",
291
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.76",
292
+ "@agimon-ai/doompi-web-security": "0.0.1-alpha.36",
293
293
  "@deepseek-ai/cordis": "4.0.2",
294
294
  "@earendil-works/chord": "0.85.1",
295
295
  "@earendil-works/pi-agent-core": "0.85.1",
@@ -314,8 +314,8 @@
314
314
  "yaml": "2.9.0"
315
315
  },
316
316
  "devDependencies": {
317
- "@agimon-ai/doompi-runner": "0.0.1-alpha.75",
318
- "@agimon-ai/vibe-lint-plugin-doom-cli": "0.0.1-alpha.6",
317
+ "@agimon-ai/doompi-runner": "0.0.1-alpha.76",
318
+ "@agimon-ai/vibe-lint-plugin-doom-cli": "0.0.1-alpha.8",
319
319
  "@earendil-works/pi-ai": "0.85.1",
320
320
  "@earendil-works/pi-client": "0.85.1",
321
321
  "@earendil-works/pi-tui": "0.85.1",