{"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":";;;;;AAcA,MAAM,mBAAmB;AACzB,MAAM,mBAAmB;AACzB,MAAM,yBAAyB;AAC/B,MAAM,oBAAoB;AAE1B,SAAS,qBAA6B;CACpC,OAAO,GAAG,KAAK,UACb;EACE,MAAM;EACN,SAAS;EACT,MAAM;EACN,kBAAA;EACA,IAAI,EAAE,YAAY,CAAC,KAAK,kBAAkB,EAAE;CAC9C,GACA,MACA,CACF,EAAE;AACJ;AAEA,SAAS,mBAA2B;CAClC,OAAO;;;;;;+BAMsB,OAAO,yBAAyB,EAAE;sCAC3B,OAAO,gCAAgC,EAAE;sBACzD,OAAO,kBAAkB,EAAE;uBAC1B,KAAK,UAAU,iBAAiB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmGzD;;AAGA,SAAgB,0BAA0B,aAA6B;CACrE,OAAO,KAAK,KAAK,aAAa,GAAG,kBAAkB,MAAM,GAAG,CAAC;AAC/D;AAEA,SAAS,uBAAuB,WAAuC;CACrE,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,GAAG,aAAa,KAAK,KAAK,WAAW,gBAAgB,GAAG,MAAM,CAAC;EAIzF,OAAO,OAAO,SAAS,qBAAqB,OAAO,cAAc,OAAO,gBAAgB,IACnF,OAAO,mBACR,KAAA;CACN,QAAQ;EACN;CACF;AACF;AAEA,SAAS,gBAAgB,WAA4B;CACnD,OAAO,uBAAuB,SAAS,MAAA;AACzC;;AAGA,SAAgB,+BAA+B,aAA8B;CAC3E,MAAM,YAAY,0BAA0B,WAAW;CACvD,MAAM,OAAO,GAAG,UAAU,WAAW,EAAE,gBAAgB,MAAM,CAAC;CAC9D,IAAI,CAAC,MAAM,YAAY,KAAK,KAAK,eAAe,KAAK,CAAC,gBAAgB,SAAS,GAAG,OAAO;CACzF,IAAI;EACF,OACE,GAAG,aAAa,KAAK,KAAK,WAAW,gBAAgB,GAAG,MAAM,MAAM,mBAAmB,KACvF,GAAG,aAAa,KAAK,KAAK,WAAW,gBAAgB,GAAG,MAAM,MAAM,iBAAiB;CAEzF,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,mCAAmC,aAA8B;CAC/E,MAAM,YAAY,0BAA0B,WAAW;CACvD,MAAM,OAAO,GAAG,UAAU,WAAW,EAAE,gBAAgB,MAAM,CAAC;CAC9D,IAAI,CAAC,MAAM,YAAY,KAAK,KAAK,eAAe,GAAG,OAAO;CAC1D,OAAO,gBAAgB,SAAS,KAAK,CAAC,+BAA+B,WAAW;AAClF;AAEA,SAAS,oBAAoB,UAA2B;CACtD,IAAI;EACF,OAAO,aAAa,GAAG,aAAa,QAAQ,CAAC,MAAM;CACrD,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,2BAA2B,aAA6B;CACtE,MAAM,YAAY,0BAA0B,WAAW;CACvD,MAAM,UAAU,GAAG,UAAU,WAAW,EAAE,gBAAgB,MAAM,CAAC;CACjE,IAAI,SAAS,eAAe,GAAG;EAC7B,IAAI,CAAC,oBAAoB,SAAS,GAChC,MAAM,IAAI,MAAM,oDAAoD,WAAW;EAEjF,GAAG,OAAO,WAAW,EAAE,OAAO,KAAK,CAAC;CACtC,OAAO,IAAI,YAAY,CAAC,QAAQ,YAAY,KAAK,CAAC,gBAAgB,SAAS,IACzE,MAAM,IAAI,MAAM,oDAAoD,WAAW;CAGjF,MAAM,QAAQ,KAAK,QAAQ,SAAS;CACpC,MAAM,YAAY,GAAG,UAAU,OAAO,EAAE,gBAAgB,MAAM,CAAC;CAC/D,IAAI,aAAa,CAAC,UAAU,YAAY,GACtC,MAAM,IAAI,MAAM,iDAAiD,OAAO;CAE1E,GAAG,UAAU,WAAW;EAAE,MAAM;EAAwB,WAAW;CAAK,CAAC;CACzE,GAAG,UAAU,WAAW,sBAAsB;CAC9C,gBAAgB,KAAK,KAAK,WAAW,gBAAgB,GAAG,mBAAmB,CAAC;CAC5E,gBAAgB,KAAK,KAAK,WAAW,gBAAgB,GAAG,iBAAiB,CAAC;CAC1E,GAAG,UAAU,KAAK,KAAK,WAAW,gBAAgB,GAAG,iBAAiB;CACtE,GAAG,UAAU,KAAK,KAAK,WAAW,gBAAgB,GAAG,iBAAiB;CACtE,OAAO;AACT"}