@agimon-ai/doompi 0.0.1-alpha.33 → 0.0.1-alpha.35

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.
Files changed (42) hide show
  1. package/README.md +7 -5
  2. package/dist/src/adapters/layerPackageInstaller.cjs +2 -1
  3. package/dist/src/adapters/layerPackageInstaller.cjs.map +1 -1
  4. package/dist/src/adapters/layerPackageInstaller.mjs +2 -1
  5. package/dist/src/adapters/layerPackageInstaller.mjs.map +1 -1
  6. package/dist/src/commands/cli/help.cjs +2 -1
  7. package/dist/src/commands/cli/help.cjs.map +1 -1
  8. package/dist/src/commands/cli/help.mjs +2 -1
  9. package/dist/src/commands/cli/help.mjs.map +1 -1
  10. package/dist/src/commands/syncCommand.cjs +5 -5
  11. package/dist/src/commands/syncCommand.cjs.map +1 -1
  12. package/dist/src/commands/syncCommand.d.cts +2 -1
  13. package/dist/src/commands/syncCommand.d.cts.map +1 -1
  14. package/dist/src/commands/syncCommand.d.mts +2 -1
  15. package/dist/src/commands/syncCommand.d.mts.map +1 -1
  16. package/dist/src/commands/syncCommand.mjs +4 -4
  17. package/dist/src/commands/syncCommand.mjs.map +1 -1
  18. package/dist/src/commands/syncPipeline.cjs +1 -1
  19. package/dist/src/commands/syncPipeline.cjs.map +1 -1
  20. package/dist/src/commands/syncPipeline.mjs +1 -1
  21. package/dist/src/commands/syncPipeline.mjs.map +1 -1
  22. package/dist/src/commands/syncPresenter.cjs +2 -0
  23. package/dist/src/commands/syncPresenter.cjs.map +1 -0
  24. package/dist/src/commands/syncPresenter.d.cts +17 -0
  25. package/dist/src/commands/syncPresenter.d.cts.map +1 -0
  26. package/dist/src/commands/syncPresenter.d.mts +17 -0
  27. package/dist/src/commands/syncPresenter.d.mts.map +1 -0
  28. package/dist/src/commands/syncPresenter.mjs +2 -0
  29. package/dist/src/commands/syncPresenter.mjs.map +1 -0
  30. package/dist/src/services/extensionAssembler.cjs +1 -1
  31. package/dist/src/services/extensionAssembler.cjs.map +1 -1
  32. package/dist/src/services/extensionAssembler.d.cts +1 -1
  33. package/dist/src/services/extensionAssembler.d.cts.map +1 -1
  34. package/dist/src/services/extensionAssembler.d.mts +1 -1
  35. package/dist/src/services/extensionAssembler.d.mts.map +1 -1
  36. package/dist/src/services/extensionAssembler.mjs +1 -1
  37. package/dist/src/services/extensionAssembler.mjs.map +1 -1
  38. package/package.json +13 -13
  39. package/dist/pi-cache-optimizer-DcFaM4WB.mjs +0 -47
  40. package/dist/pi-cache-optimizer-DcFaM4WB.mjs.map +0 -1
  41. package/dist/src/extensions/entries/cacheOptimizer.mjs +0 -2
  42. package/dist/src/extensions/entries/cacheOptimizer.mjs.map +0 -1
package/README.md CHANGED
@@ -452,8 +452,8 @@ you want to restart one.
452
452
  #### Workflow mode
453
453
 
454
454
  [`@agimon-ai/doompi-workflow`][pkg-doompi-workflow] runs GitHub Actions-style job graphs with
455
- dependencies, timeouts, artifacts, and a separate DoomPi session for each step. Use `SPC w w` to
456
- launch, `SPC w l` to list the repository's workflows, `SPC w r` to inspect this session's runs,
455
+ dependencies, timeouts, artifacts, and a separate DoomPi session for each step. Use `SPC w l` to
456
+ browse the repository's workflows and launch one with `r`, `SPC w r` to inspect this session's runs,
457
457
  `SPC w c` to recover a failed run, and `SPC w e` to give the agent workflow tools or take them back. It is for work that needs hard job boundaries and explicit
458
458
  handoffs rather than one long conversation.
459
459
 
@@ -506,9 +506,11 @@ baseline. The fixed host core remains active independently of this setting, and
506
506
  keep their existing behavior.
507
507
 
508
508
  Launch installs missing required packages from the defaults and selected layers. `doompi sync`
509
- installs missing required packages from the defaults and every declared layer, then builds the
510
- synchronized matrix. `doompi sync --check` reports missing packages without modifying the
511
- repository. Optional packages and local paths are not installed automatically. Removing an
509
+ installs missing required packages from the defaults and every declared layer, moves every
510
+ package in Pi's managed `.pi/npm` store to its newest published version, then builds the
511
+ synchronized matrix. A package whose published version cannot be read, because the machine is
512
+ offline or the registry refuses the request, keeps its installed version and is named in the
513
+ sync output. `doompi sync --check` reports missing packages without modifying the repository. Optional packages and local paths are not installed automatically. Removing an
512
514
  entry stops activating it but leaves Pi's package cache available for later reuse.
513
515
 
514
516
  Packages in `default` and named layers may be bare strings, or mappings when the package accepts
@@ -1,2 +1,3 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./modules/moduleResolution.cjs"),n=require("../services/extensionAssembler.cjs"),r=require("./piSettings.cjs");let i=require("node:path");i=e.__toESM(i,1);let a=require("node:fs");a=e.__toESM(a,1);let o=require("@agimon-ai/doompi-config/majorModes"),s=require("@earendil-works/pi-coding-agent");const c=i.default.join(`.pi`,`npm`);function l(e){let t=e.indexOf(`@`,+!!e.startsWith(`@`));return t===-1?e:e.slice(0,t)}const u={"@hono/node-server@2.0.0 - 2.0.9":`^2.0.10`,"@opentelemetry/core@2.0.0 - 2.7.999":`2.8.0`,"brace-expansion@5.0.0 - 5.0.8":`^5.0.9`,"hono@4.0.0 - 4.12.33":`^4.12.34`,"js-yaml@4.0.0 - 4.3.0":`^4.3.1`,"js-yaml@5.0.0 - 5.2.1":`^5.2.2`,"liquidjs@10.0.0 - 10.27.0":`^10.27.1`,"protobufjs@8.0.0 - 8.7.1":`^8.7.2`},d=new Set(Object.keys(u).map(l));function f(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function p(e){return f(e)?{...e}:{}}function m(e){let t=p(e);for(let[e,n]of Object.entries(t))d.has(l(e))?delete t[e]:f(n)&&(t[e]=m(n));return t}function h(e){return i.default.join(e,c,`package.json`)}function g(e,t){let n=new Set,r=[...e.default?[e.default]:[],...t.map(t=>{let n=e.layers[t];if(!n)throw Error(`Unknown layer: ${t}`);return n})];for(let e of r)for(let t of e.packages??[]){let e=typeof t==`string`?t:t.name;(typeof t==`string`||t.optional!==!0)&&!(0,o.isLocalPackageSpecifier)(e)&&n.add(e)}return[...n]}function _(e,t){let n=e.optionalPackageEntries?.(t);if(n!==void 0)return n;let r=e.optionalPackageEntry(t);return r?[r]:void 0}function v(e,t){let n=h(e),r=a.default.existsSync(n)?a.default.readFileSync(n,`utf8`):void 0,o;if(r===void 0)o={name:`pi-extensions`,private:!0};else{let e=JSON.parse(r);if(!f(e))throw Error(`Pi managed package manifest at ${n} must be a JSON object.`);o=e}let s=p(o.dependencies);for(let e of t){let t=e.slice(4);Object.hasOwn(s,t)||(s[t]=`*`)}o.dependencies=s,o.overrides={...m(o.overrides),...u};let c=p(o.pnpm);c.overrides={...m(c.overrides),...u},o.pnpm=c;let l=`${JSON.stringify(o,null,2)}\n`;return l!==r&&(a.default.mkdirSync(i.default.dirname(n),{recursive:!0}),a.default.writeFileSync(n,l,`utf8`),!0)}function y(e,t,n){return g(e,t).filter(e=>_(n,e)===void 0)}function b(e){return[...new Set(e.map(e=>`npm:${t.splitPackageSpecifier(e).name}`))]}function x(e,t){let n=r.piAgentDirectory(t),i=s.SettingsManager.create(e,n,{projectTrusted:!0});return new s.DefaultPackageManager({cwd:e,agentDir:n,settingsManager:i})}async function S(e,t={}){let r=e.environment??process.env,o=t.resolvers??n.createLayerResolvers(e.repoRoot),s=g(e.config,e.layers),c=s.filter(e=>_(o,e)===void 0),l=b(c),u=b(s);if(u.length===0)return[];let d=i.default.dirname(h(e.repoRoot));if(c.length===0&&!a.default.existsSync(d))return[];let f=v(e.repoRoot,u);if(c.length===0&&!f)return[];let p=t.packageManager??x(e.repoRoot,r);try{f?await p.install(u[0],{local:!0}):await p.resolveExtensionSources(l,{local:!0})}catch(e){throw Error(`Failed to reconcile DoomPi layer package(s): ${(f?u:l).join(`, `)}`,{cause:e})}let m=c.filter(e=>!_(o,e)?.length);if(m.length>0)throw Error(`Installed DoomPi layer package(s) did not expose the configured Pi extension: ${m.join(`, `)}`);return l}exports.SAFE_TRANSITIVE_OVERRIDES=u,exports.ensureLayerPackages=S,exports.ensureManagedPackageManifest=v,exports.missingLayerPackageSpecifiers=y;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./modules/moduleResolution.cjs"),n=require("../services/extensionAssembler.cjs"),r=require("./piSettings.cjs");let i=require("node:path");i=e.__toESM(i,1);let a=require("node:fs");a=e.__toESM(a,1);let o=require("@agimon-ai/doompi-config/majorModes"),s=require("node:child_process"),c=require("node:util"),l=require("@earendil-works/pi-coding-agent");const u=`npm:`,d=i.default.join(`.pi`,`npm`),f=i.default.join(d,`node_modules`),p=`package.json`,m=(0,c.promisify)(s.execFile);function h(e){let t=e.indexOf(`@`,+!!e.startsWith(`@`));return t===-1?e:e.slice(0,t)}const g={"@hono/node-server@2.0.0 - 2.0.9":`^2.0.10`,"@opentelemetry/core@2.0.0 - 2.7.999":`2.8.0`,"brace-expansion@5.0.0 - 5.0.8":`^5.0.9`,"hono@4.0.0 - 4.12.33":`^4.12.34`,"js-yaml@4.0.0 - 4.3.0":`^4.3.1`,"js-yaml@5.0.0 - 5.2.1":`^5.2.2`,"liquidjs@10.0.0 - 10.27.0":`^10.27.1`,"protobufjs@8.0.0 - 8.7.1":`^8.7.2`},_=new Set(Object.keys(g).map(h));function v(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function y(e){return v(e)?{...e}:{}}function b(e){let t=y(e);for(let[e,n]of Object.entries(t))_.has(h(e))?delete t[e]:v(n)&&(t[e]=b(n));return t}function x(e){return i.default.join(e,d,p)}function S(e,t){let n=new Set,r=[...e.default?[e.default]:[],...t.map(t=>{let n=e.layers[t];if(!n)throw Error(`Unknown layer: ${t}`);return n})];for(let e of r)for(let t of e.packages??[]){let e=typeof t==`string`?t:t.name;(typeof t==`string`||t.optional!==!0)&&!(0,o.isLocalPackageSpecifier)(e)&&n.add(e)}return[...n]}function C(e,t){let n=e.optionalPackageEntries?.(t);if(n!==void 0)return n;let r=e.optionalPackageEntry(t);return r?[r]:void 0}function w(e,t,n=new Map){let r=x(e),o=a.default.existsSync(r)?a.default.readFileSync(r,`utf8`):void 0,s;if(o===void 0)s={name:`pi-extensions`,private:!0};else{let e=JSON.parse(o);if(!v(e))throw Error(`Pi managed package manifest at ${r} must be a JSON object.`);s=e}let c=y(s.dependencies);for(let e of t){let t=E(e),r=n.get(t);r===void 0?Object.hasOwn(c,t)||(c[t]=`*`):c[t]=r}s.dependencies=c,s.overrides={...b(s.overrides),...g};let l=y(s.pnpm);l.overrides={...b(l.overrides),...g},s.pnpm=l;let u=`${JSON.stringify(s,null,2)}\n`;return u!==o&&(a.default.mkdirSync(i.default.dirname(r),{recursive:!0}),a.default.writeFileSync(r,u,`utf8`),!0)}function T(e,t,n){return S(e,t).filter(e=>C(n,e)===void 0)}function E(e){return e.startsWith(u)?e.slice(4):e}function D(e){return[...new Set(e.map(e=>`${u}${t.splitPackageSpecifier(e).name}`))]}function O(e,t){return`${e} ${t}${e===1?``:`s`}`}function k(e,t){let n=i.default.join(e,f,t,p);if(a.default.existsSync(n))try{let e=JSON.parse(a.default.readFileSync(n,`utf8`));return v(e)&&typeof e.version==`string`?e.version:void 0}catch{return}}function A(e,t){let n=r.piAgentDirectory(t),i=l.SettingsManager.create(e,n,{projectTrusted:!0});return new l.DefaultPackageManager({cwd:e,agentDir:n,settingsManager:i})}function j(e,t){let[n,...i]=l.SettingsManager.create(e,r.piAgentDirectory(t),{projectTrusted:!0}).getNpmCommand()??[],a=n??`npm`,o=n?i:[];return async t=>{let{stdout:n}=await m(a,[...o,`view`,t,`version`,`--json`],{cwd:e,timeout:2e4,encoding:`utf8`}),r=JSON.parse(n.trim()||`null`);if(typeof r!=`string`)throw Error(`${a} view reported no published version`);return r}}async function M(e,t,n,r){let i=t.map(t=>({name:E(t),from:k(e,E(t))})).filter(e=>e.from!==void 0),a=await Promise.all(i.map(async e=>{try{return{...e,to:await n(e.name)}}catch(t){return{...e,failure:(t instanceof Error?t.message:String(t)).split(`
2
+ `)[0]}}})),o=[],s=[];for(let e of a){if(e.to===void 0){s.push(e.name),r?.(`kept ${e.name} ${e.from}: ${e.failure??`no published version`}`);continue}e.to!==e.from&&(o.push({name:e.name,from:e.from,to:e.to}),r?.(`${e.name} ${e.from} -> ${e.to}`))}return{updated:o,unchecked:s}}async function N(e,t={}){let r=e.environment??process.env,o=t.resolvers??n.createLayerResolvers(e.repoRoot),s=S(e.config,e.layers),c=s.filter(e=>C(o,e)===void 0),l=D(c),u=D(s);if(u.length===0)return{installed:[],updated:[],unchecked:[]};let d=i.default.dirname(x(e.repoRoot));if(c.length===0&&!a.default.existsSync(d))return{installed:[],updated:[],unchecked:[]};let{updated:f,unchecked:p}=e.refresh?await M(e.repoRoot,u,t.publishedVersion??j(e.repoRoot,r),e.onProgress):{updated:[],unchecked:[]},m=w(e.repoRoot,u,new Map(f.map(e=>[e.name,e.to])));if(c.length===0&&!m)return{installed:[],updated:[],unchecked:p};let h=t.packageManager??A(e.repoRoot,r);e.onProgress?.(`installing ${f.length>0?O(f.length,`update`):c.length>0?O(c.length,`missing package`):`the package manifest`}`);try{m?await h.install(u[0],{local:!0}):await h.resolveExtensionSources(l,{local:!0})}catch(e){throw Error(`Failed to reconcile DoomPi layer package(s): ${(m?u:l).join(`, `)}`,{cause:e})}let g=c.filter(e=>!C(o,e)?.length);if(g.length>0)throw Error(`Installed DoomPi layer package(s) did not expose the configured Pi extension: ${g.join(`, `)}`);return{installed:l,updated:f,unchecked:p}}exports.SAFE_TRANSITIVE_OVERRIDES=g,exports.ensureLayerPackages=N,exports.ensureManagedPackageManifest=w,exports.missingLayerPackageSpecifiers=T;
2
3
  //# sourceMappingURL=layerPackageInstaller.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"layerPackageInstaller.cjs","names":["path","isLocalPackageSpecifier","fs","splitPackageSpecifier","piAgentDirectory","SettingsManager","DefaultPackageManager","createLayerResolvers"],"sources":["../../../src/adapters/layerPackageInstaller.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { isLocalPackageSpecifier, type MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { DefaultPackageManager, type PackageManager, SettingsManager } from '@earendil-works/pi-coding-agent';\nimport { createLayerResolvers, type ExtensionLayerResolvers } from '../services/extensionAssembler.ts';\nimport { splitPackageSpecifier } from './modules/moduleResolution.ts';\nimport { piAgentDirectory } from './piSettings.ts';\n\nconst NPM_SOURCE_PREFIX = 'npm:';\nconst PI_MANAGED_NPM_DIRECTORY = path.join('.pi', 'npm');\nconst PI_MANAGED_PACKAGE_NAME = 'pi-extensions';\nconst UNPINNED_PACKAGE_RANGE = '*';\n\nfunction overridePackageName(selector: string): string {\n const separatorIndex = selector.indexOf('@', selector.startsWith('@') ? 1 : 0);\n return separatorIndex === -1 ? selector : selector.slice(0, separatorIndex);\n}\n\n/**\n * Same-major patches for transitive versions pinned by the current foundation packages.\n * npm only honors overrides from the install root, so published extensions cannot carry\n * these fixes in their own manifests.\n */\nexport const SAFE_TRANSITIVE_OVERRIDES = {\n '@hono/node-server@2.0.0 - 2.0.9': '^2.0.10',\n '@opentelemetry/core@2.0.0 - 2.7.999': '2.8.0',\n 'brace-expansion@5.0.0 - 5.0.8': '^5.0.9',\n 'hono@4.0.0 - 4.12.33': '^4.12.34',\n 'js-yaml@4.0.0 - 4.3.0': '^4.3.1',\n 'js-yaml@5.0.0 - 5.2.1': '^5.2.2',\n 'liquidjs@10.0.0 - 10.27.0': '^10.27.1',\n 'protobufjs@8.0.0 - 8.7.1': '^8.7.2',\n} as const satisfies Readonly<Record<string, string>>;\n\nconst SAFE_TRANSITIVE_PACKAGE_NAMES = new Set(Object.keys(SAFE_TRANSITIVE_OVERRIDES).map(overridePackageName));\n\ntype LayerPackageManager = Pick<PackageManager, 'install' | 'resolveExtensionSources'>;\n\nexport interface EnsureLayerPackagesOptions {\n repoRoot: string;\n config: MajorModesConfig;\n layers: readonly string[];\n environment?: NodeJS.ProcessEnv;\n}\n\nexport interface LayerPackageInstallerDependencies {\n packageManager?: LayerPackageManager;\n resolvers?: ExtensionLayerResolvers;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction recordValue(value: unknown): Record<string, unknown> {\n return isRecord(value) ? { ...value } : {};\n}\n\nfunction withoutConflictingTransitiveOverrides(value: unknown): Record<string, unknown> {\n const overrides = recordValue(value);\n for (const [selector, replacement] of Object.entries(overrides)) {\n if (SAFE_TRANSITIVE_PACKAGE_NAMES.has(overridePackageName(selector))) {\n delete overrides[selector];\n } else if (isRecord(replacement)) {\n overrides[selector] = withoutConflictingTransitiveOverrides(replacement);\n }\n }\n return overrides;\n}\n\nfunction managedPackageManifestPath(repoRoot: string): string {\n return path.join(repoRoot, PI_MANAGED_NPM_DIRECTORY, 'package.json');\n}\n\nfunction configuredPackageSpecifiers(config: MajorModesConfig, layers: readonly string[]): string[] {\n const specifiers = new Set<string>();\n const selectedDefinitions = [\n ...(config.default ? [config.default] : []),\n ...layers.map((layerName) => {\n const layer = config.layers[layerName];\n if (!layer) throw new Error(`Unknown layer: ${layerName}`);\n return layer;\n }),\n ];\n for (const layer of selectedDefinitions) {\n for (const configured of layer.packages ?? []) {\n const specifier = typeof configured === 'string' ? configured : configured.name;\n const optional = typeof configured !== 'string' && configured.optional === true;\n if (!optional && !isLocalPackageSpecifier(specifier)) specifiers.add(specifier);\n }\n }\n return [...specifiers];\n}\n\nfunction resolvePackageEntries(resolvers: ExtensionLayerResolvers, specifier: string): string[] | undefined {\n const entries = resolvers.optionalPackageEntries?.(specifier);\n if (entries !== undefined) return entries;\n const entry = resolvers.optionalPackageEntry(specifier);\n return entry ? [entry] : undefined;\n}\n\n/** Adds the complete package set and patched transitive policy to Pi's managed install root. */\nexport function ensureManagedPackageManifest(repoRoot: string, sources: readonly string[]): boolean {\n const manifestPath = managedPackageManifestPath(repoRoot);\n const current = fs.existsSync(manifestPath) ? fs.readFileSync(manifestPath, 'utf8') : undefined;\n let manifest: Record<string, unknown>;\n if (current === undefined) {\n manifest = { name: PI_MANAGED_PACKAGE_NAME, private: true };\n } else {\n const parsed: unknown = JSON.parse(current);\n if (!isRecord(parsed)) throw new Error(`Pi managed package manifest at ${manifestPath} must be a JSON object.`);\n manifest = parsed;\n }\n\n const dependencies = recordValue(manifest.dependencies);\n for (const source of sources) {\n const name = source.slice(NPM_SOURCE_PREFIX.length);\n if (!Object.hasOwn(dependencies, name)) dependencies[name] = UNPINNED_PACKAGE_RANGE;\n }\n manifest.dependencies = dependencies;\n // npm gives nested and overlapping consumer rules precedence in cases that can retain\n // vulnerable versions, so DoomPi owns these package-specific selectors recursively.\n manifest.overrides = {\n ...withoutConflictingTransitiveOverrides(manifest.overrides),\n ...SAFE_TRANSITIVE_OVERRIDES,\n };\n const pnpm = recordValue(manifest.pnpm);\n pnpm.overrides = {\n ...withoutConflictingTransitiveOverrides(pnpm.overrides),\n ...SAFE_TRANSITIVE_OVERRIDES,\n };\n manifest.pnpm = pnpm;\n\n const serialized = `${JSON.stringify(manifest, null, 2)}\\n`;\n if (serialized === current) return false;\n fs.mkdirSync(path.dirname(manifestPath), { recursive: true });\n fs.writeFileSync(manifestPath, serialized, 'utf8');\n return true;\n}\n\n/** Finds required npm packages selected by defaults and active layers that DoomPi cannot resolve. */\nexport function missingLayerPackageSpecifiers(\n config: MajorModesConfig,\n layers: readonly string[],\n resolvers: ExtensionLayerResolvers,\n): string[] {\n return configuredPackageSpecifiers(config, layers).filter(\n (specifier) => resolvePackageEntries(resolvers, specifier) === undefined,\n );\n}\n\nfunction npmSources(specifiers: readonly string[]): string[] {\n return [...new Set(specifiers.map((specifier) => `${NPM_SOURCE_PREFIX}${splitPackageSpecifier(specifier).name}`))];\n}\n\nfunction createPackageManager(repoRoot: string, environment: NodeJS.ProcessEnv): LayerPackageManager {\n const agentDirectory = piAgentDirectory(environment);\n const settingsManager = SettingsManager.create(repoRoot, agentDirectory, { projectTrusted: true });\n return new DefaultPackageManager({ cwd: repoRoot, agentDir: agentDirectory, settingsManager });\n}\n\n/** Installs missing selected layer packages into Pi's project-local `.pi/npm` store and verifies the retry. */\nexport async function ensureLayerPackages(\n options: EnsureLayerPackagesOptions,\n dependencies: LayerPackageInstallerDependencies = {},\n): Promise<string[]> {\n const environment = options.environment ?? process.env;\n const resolvers = dependencies.resolvers ?? createLayerResolvers(options.repoRoot);\n const configured = configuredPackageSpecifiers(options.config, options.layers);\n const missing = configured.filter((specifier) => resolvePackageEntries(resolvers, specifier) === undefined);\n const missingSources = npmSources(missing);\n const sources = npmSources(configured);\n if (sources.length === 0) return [];\n\n const managedDirectory = path.dirname(managedPackageManifestPath(options.repoRoot));\n if (missing.length === 0 && !fs.existsSync(managedDirectory)) return [];\n const manifestChanged = ensureManagedPackageManifest(options.repoRoot, sources);\n if (missing.length === 0 && !manifestChanged) return [];\n\n const packageManager = dependencies.packageManager ?? createPackageManager(options.repoRoot, environment);\n try {\n if (manifestChanged) {\n // Installing one source reconciles the complete dependency set already written\n // to package.json, avoiding one npm install and audit report per DoomPi package.\n await packageManager.install(sources[0]!, { local: true });\n } else {\n await packageManager.resolveExtensionSources(missingSources, { local: true });\n }\n } catch (error) {\n const attempted = manifestChanged ? sources : missingSources;\n throw new Error(`Failed to reconcile DoomPi layer package(s): ${attempted.join(', ')}`, { cause: error });\n }\n\n const unresolved = missing.filter((specifier) => !resolvePackageEntries(resolvers, specifier)?.length);\n if (unresolved.length > 0) {\n throw new Error(\n `Installed DoomPi layer package(s) did not expose the configured Pi extension: ${unresolved.join(', ')}`,\n );\n }\n return missingSources;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"layerPackageInstaller.cjs","names":["path","promisify","execFile","isLocalPackageSpecifier","fs","splitPackageSpecifier","piAgentDirectory","SettingsManager","DefaultPackageManager","createLayerResolvers"],"sources":["../../../src/adapters/layerPackageInstaller.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\nimport { isLocalPackageSpecifier, type MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { DefaultPackageManager, type PackageManager, SettingsManager } from '@earendil-works/pi-coding-agent';\nimport { createLayerResolvers, type ExtensionLayerResolvers } from '../services/extensionAssembler.ts';\nimport { splitPackageSpecifier } from './modules/moduleResolution.ts';\nimport { piAgentDirectory } from './piSettings.ts';\n\nconst NPM_SOURCE_PREFIX = 'npm:';\nconst PI_MANAGED_NPM_DIRECTORY = path.join('.pi', 'npm');\nconst PI_MANAGED_MODULES_DIRECTORY = path.join(PI_MANAGED_NPM_DIRECTORY, 'node_modules');\nconst PI_MANAGED_PACKAGE_NAME = 'pi-extensions';\nconst UNPINNED_PACKAGE_RANGE = '*';\nconst PACKAGE_MANIFEST = 'package.json';\nconst DEFAULT_NPM_COMMAND = 'npm';\nconst REGISTRY_TIMEOUT_MS = 20_000;\n\nconst execFileAsync = promisify(execFile);\n\nfunction overridePackageName(selector: string): string {\n const separatorIndex = selector.indexOf('@', selector.startsWith('@') ? 1 : 0);\n return separatorIndex === -1 ? selector : selector.slice(0, separatorIndex);\n}\n\n/**\n * Same-major patches for transitive versions pinned by the current foundation packages.\n * npm only honors overrides from the install root, so published extensions cannot carry\n * these fixes in their own manifests.\n */\nexport const SAFE_TRANSITIVE_OVERRIDES = {\n '@hono/node-server@2.0.0 - 2.0.9': '^2.0.10',\n '@opentelemetry/core@2.0.0 - 2.7.999': '2.8.0',\n 'brace-expansion@5.0.0 - 5.0.8': '^5.0.9',\n 'hono@4.0.0 - 4.12.33': '^4.12.34',\n 'js-yaml@4.0.0 - 4.3.0': '^4.3.1',\n 'js-yaml@5.0.0 - 5.2.1': '^5.2.2',\n 'liquidjs@10.0.0 - 10.27.0': '^10.27.1',\n 'protobufjs@8.0.0 - 8.7.1': '^8.7.2',\n} as const satisfies Readonly<Record<string, string>>;\n\nconst SAFE_TRANSITIVE_PACKAGE_NAMES = new Set(Object.keys(SAFE_TRANSITIVE_OVERRIDES).map(overridePackageName));\n\ntype LayerPackageManager = Pick<PackageManager, 'install' | 'resolveExtensionSources'>;\n\nexport interface EnsureLayerPackagesOptions {\n repoRoot: string;\n config: MajorModesConfig;\n layers: readonly string[];\n environment?: NodeJS.ProcessEnv;\n /**\n * Move every managed package to its newest published version.\n *\n * Launch installs what a selection needs and nothing more, so a session never\n * pays for a registry round trip. Sync is the explicit maintenance command and\n * is the only caller that asks for this.\n */\n refresh?: boolean;\n /** Receives one human-readable line per package decision. */\n onProgress?: (message: string) => void;\n}\n\nexport interface LayerPackageInstallerDependencies {\n packageManager?: LayerPackageManager;\n resolvers?: ExtensionLayerResolvers;\n /** Registry lookup seam; the default reads the newest version with Pi's npm client. */\n publishedVersion?: (name: string) => Promise<string>;\n}\n\n/** One managed package moving from the installed version to the newest published one. */\nexport interface LayerPackageUpdate {\n name: string;\n from: string;\n to: string;\n}\n\nexport interface LayerPackageResult {\n /** Sources installed because a configured extension could not be resolved. */\n installed: string[];\n /** Packages this run moved to a newer published version. */\n updated: LayerPackageUpdate[];\n /** Packages left at the installed version because the registry could not be read. */\n unchecked: string[];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction recordValue(value: unknown): Record<string, unknown> {\n return isRecord(value) ? { ...value } : {};\n}\n\nfunction withoutConflictingTransitiveOverrides(value: unknown): Record<string, unknown> {\n const overrides = recordValue(value);\n for (const [selector, replacement] of Object.entries(overrides)) {\n if (SAFE_TRANSITIVE_PACKAGE_NAMES.has(overridePackageName(selector))) {\n delete overrides[selector];\n } else if (isRecord(replacement)) {\n overrides[selector] = withoutConflictingTransitiveOverrides(replacement);\n }\n }\n return overrides;\n}\n\nfunction managedPackageManifestPath(repoRoot: string): string {\n return path.join(repoRoot, PI_MANAGED_NPM_DIRECTORY, PACKAGE_MANIFEST);\n}\n\nfunction configuredPackageSpecifiers(config: MajorModesConfig, layers: readonly string[]): string[] {\n const specifiers = new Set<string>();\n const selectedDefinitions = [\n ...(config.default ? [config.default] : []),\n ...layers.map((layerName) => {\n const layer = config.layers[layerName];\n if (!layer) throw new Error(`Unknown layer: ${layerName}`);\n return layer;\n }),\n ];\n for (const layer of selectedDefinitions) {\n for (const configured of layer.packages ?? []) {\n const specifier = typeof configured === 'string' ? configured : configured.name;\n const optional = typeof configured !== 'string' && configured.optional === true;\n if (!optional && !isLocalPackageSpecifier(specifier)) specifiers.add(specifier);\n }\n }\n return [...specifiers];\n}\n\nfunction resolvePackageEntries(resolvers: ExtensionLayerResolvers, specifier: string): string[] | undefined {\n const entries = resolvers.optionalPackageEntries?.(specifier);\n if (entries !== undefined) return entries;\n const entry = resolvers.optionalPackageEntry(specifier);\n return entry ? [entry] : undefined;\n}\n\n/** Adds the complete package set and patched transitive policy to Pi's managed install root. */\nexport function ensureManagedPackageManifest(\n repoRoot: string,\n sources: readonly string[],\n targets: ReadonlyMap<string, string> = new Map(),\n): boolean {\n const manifestPath = managedPackageManifestPath(repoRoot);\n const current = fs.existsSync(manifestPath) ? fs.readFileSync(manifestPath, 'utf8') : undefined;\n let manifest: Record<string, unknown>;\n if (current === undefined) {\n manifest = { name: PI_MANAGED_PACKAGE_NAME, private: true };\n } else {\n const parsed: unknown = JSON.parse(current);\n if (!isRecord(parsed)) throw new Error(`Pi managed package manifest at ${manifestPath} must be a JSON object.`);\n manifest = parsed;\n }\n\n const dependencies = recordValue(manifest.dependencies);\n for (const source of sources) {\n const name = packageName(source);\n const target = targets.get(name);\n // An exact target is what makes an update reach the store: npm keeps a\n // locked version whenever the recorded range still admits it, and the range\n // it writes back after an install always admits the version it just wrote.\n if (target !== undefined) dependencies[name] = target;\n else if (!Object.hasOwn(dependencies, name)) dependencies[name] = UNPINNED_PACKAGE_RANGE;\n }\n manifest.dependencies = dependencies;\n // npm gives nested and overlapping consumer rules precedence in cases that can retain\n // vulnerable versions, so DoomPi owns these package-specific selectors recursively.\n manifest.overrides = {\n ...withoutConflictingTransitiveOverrides(manifest.overrides),\n ...SAFE_TRANSITIVE_OVERRIDES,\n };\n const pnpm = recordValue(manifest.pnpm);\n pnpm.overrides = {\n ...withoutConflictingTransitiveOverrides(pnpm.overrides),\n ...SAFE_TRANSITIVE_OVERRIDES,\n };\n manifest.pnpm = pnpm;\n\n const serialized = `${JSON.stringify(manifest, null, 2)}\\n`;\n if (serialized === current) return false;\n fs.mkdirSync(path.dirname(manifestPath), { recursive: true });\n fs.writeFileSync(manifestPath, serialized, 'utf8');\n return true;\n}\n\n/** Finds required npm packages selected by defaults and active layers that DoomPi cannot resolve. */\nexport function missingLayerPackageSpecifiers(\n config: MajorModesConfig,\n layers: readonly string[],\n resolvers: ExtensionLayerResolvers,\n): string[] {\n return configuredPackageSpecifiers(config, layers).filter(\n (specifier) => resolvePackageEntries(resolvers, specifier) === undefined,\n );\n}\n\nfunction packageName(source: string): string {\n return source.startsWith(NPM_SOURCE_PREFIX) ? source.slice(NPM_SOURCE_PREFIX.length) : source;\n}\n\nfunction npmSources(specifiers: readonly string[]): string[] {\n return [...new Set(specifiers.map((specifier) => `${NPM_SOURCE_PREFIX}${splitPackageSpecifier(specifier).name}`))];\n}\n\nfunction pluralize(count: number, noun: string): string {\n return `${count} ${noun}${count === 1 ? '' : 's'}`;\n}\n\n/** Version of a package inside Pi's managed store, absent when sync does not own it. */\nfunction managedPackageVersion(repoRoot: string, name: string): string | undefined {\n const manifestPath = path.join(repoRoot, PI_MANAGED_MODULES_DIRECTORY, name, PACKAGE_MANIFEST);\n if (!fs.existsSync(manifestPath)) return undefined;\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n return isRecord(parsed) && typeof parsed.version === 'string' ? parsed.version : undefined;\n } catch {\n // An unreadable manifest carries the same meaning as an absent one: nothing\n // usable is installed, so the caller treats the package as uninstalled.\n return undefined;\n }\n}\n\nfunction createPackageManager(repoRoot: string, environment: NodeJS.ProcessEnv): LayerPackageManager {\n const agentDirectory = piAgentDirectory(environment);\n const settingsManager = SettingsManager.create(repoRoot, agentDirectory, { projectTrusted: true });\n return new DefaultPackageManager({ cwd: repoRoot, agentDir: agentDirectory, settingsManager });\n}\n\n/** Reads newest published versions with the same client Pi installs them with. */\nfunction createVersionReader(repoRoot: string, environment: NodeJS.ProcessEnv): (name: string) => Promise<string> {\n const configured = SettingsManager.create(repoRoot, piAgentDirectory(environment), {\n projectTrusted: true,\n }).getNpmCommand();\n const [command, ...args] = configured ?? [];\n const client = command ?? DEFAULT_NPM_COMMAND;\n const clientArguments = command ? args : [];\n return async (name) => {\n const { stdout } = await execFileAsync(client, [...clientArguments, 'view', name, 'version', '--json'], {\n cwd: repoRoot,\n timeout: REGISTRY_TIMEOUT_MS,\n encoding: 'utf8',\n });\n const parsed: unknown = JSON.parse(stdout.trim() || 'null');\n if (typeof parsed !== 'string') throw new Error(`${client} view reported no published version`);\n return parsed;\n };\n}\n\ninterface CheckedPackage {\n name: string;\n from: string;\n to?: string;\n failure?: string;\n}\n\n/**\n * Compares every package sync installed against the registry.\n *\n * Only the managed store is consulted: a package that resolves from the\n * repository's own modules is not sync's to move, and a package that is not\n * installed yet is already covered by the missing-package path.\n */\nasync function resolveUpdates(\n repoRoot: string,\n sources: readonly string[],\n publishedVersion: (name: string) => Promise<string>,\n onProgress: ((message: string) => void) | undefined,\n): Promise<Pick<LayerPackageResult, 'updated' | 'unchecked'>> {\n const managed = sources\n .map((source) => ({ name: packageName(source), from: managedPackageVersion(repoRoot, packageName(source)) }))\n .filter((entry): entry is { name: string; from: string } => entry.from !== undefined);\n const checked: CheckedPackage[] = await Promise.all(\n managed.map(async (entry) => {\n try {\n return { ...entry, to: await publishedVersion(entry.name) };\n } catch (error) {\n return { ...entry, failure: (error instanceof Error ? error.message : String(error)).split('\\n')[0] };\n }\n }),\n );\n\n const updated: LayerPackageUpdate[] = [];\n const unchecked: string[] = [];\n for (const entry of checked) {\n if (entry.to === undefined) {\n unchecked.push(entry.name);\n onProgress?.(`kept ${entry.name} ${entry.from}: ${entry.failure ?? 'no published version'}`);\n continue;\n }\n if (entry.to === entry.from) continue;\n updated.push({ name: entry.name, from: entry.from, to: entry.to });\n onProgress?.(`${entry.name} ${entry.from} -> ${entry.to}`);\n }\n return { updated, unchecked };\n}\n\n/** Installs selected layer packages into Pi's project-local `.pi/npm` store and verifies the retry. */\nexport async function ensureLayerPackages(\n options: EnsureLayerPackagesOptions,\n dependencies: LayerPackageInstallerDependencies = {},\n): Promise<LayerPackageResult> {\n const environment = options.environment ?? process.env;\n const resolvers = dependencies.resolvers ?? createLayerResolvers(options.repoRoot);\n const configured = configuredPackageSpecifiers(options.config, options.layers);\n const missing = configured.filter((specifier) => resolvePackageEntries(resolvers, specifier) === undefined);\n const missingSources = npmSources(missing);\n const sources = npmSources(configured);\n if (sources.length === 0) return { installed: [], updated: [], unchecked: [] };\n\n const managedDirectory = path.dirname(managedPackageManifestPath(options.repoRoot));\n if (missing.length === 0 && !fs.existsSync(managedDirectory)) return { installed: [], updated: [], unchecked: [] };\n\n const { updated, unchecked } = options.refresh\n ? await resolveUpdates(\n options.repoRoot,\n sources,\n dependencies.publishedVersion ?? createVersionReader(options.repoRoot, environment),\n options.onProgress,\n )\n : { updated: [], unchecked: [] };\n const manifestChanged = ensureManagedPackageManifest(\n options.repoRoot,\n sources,\n new Map(updated.map((update) => [update.name, update.to])),\n );\n if (missing.length === 0 && !manifestChanged) return { installed: [], updated: [], unchecked };\n\n const packageManager = dependencies.packageManager ?? createPackageManager(options.repoRoot, environment);\n options.onProgress?.(\n `installing ${\n updated.length > 0\n ? pluralize(updated.length, 'update')\n : missing.length > 0\n ? pluralize(missing.length, 'missing package')\n : 'the package manifest'\n }`,\n );\n try {\n if (manifestChanged) {\n // Installing one source reconciles the complete dependency set already written\n // to package.json, avoiding one npm install and audit report per DoomPi package.\n await packageManager.install(sources[0]!, { local: true });\n } else {\n await packageManager.resolveExtensionSources(missingSources, { local: true });\n }\n } catch (error) {\n const attempted = manifestChanged ? sources : missingSources;\n throw new Error(`Failed to reconcile DoomPi layer package(s): ${attempted.join(', ')}`, { cause: error });\n }\n\n const unresolved = missing.filter((specifier) => !resolvePackageEntries(resolvers, specifier)?.length);\n if (unresolved.length > 0) {\n throw new Error(\n `Installed DoomPi layer package(s) did not expose the configured Pi extension: ${unresolved.join(', ')}`,\n );\n }\n return { installed: missingSources, updated, unchecked };\n}\n"],"mappings":"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"}
@@ -1,2 +1,3 @@
1
- import{splitPackageSpecifier as e}from"./modules/moduleResolution.mjs";import{createLayerResolvers as t}from"../services/extensionAssembler.mjs";import{piAgentDirectory as n}from"./piSettings.mjs";import r from"node:path";import i from"node:fs";import{isLocalPackageSpecifier as a}from"@agimon-ai/doompi-config/majorModes";import{DefaultPackageManager as o,SettingsManager as s}from"@earendil-works/pi-coding-agent";const c=r.join(`.pi`,`npm`);function l(e){let t=e.indexOf(`@`,+!!e.startsWith(`@`));return t===-1?e:e.slice(0,t)}const u={"@hono/node-server@2.0.0 - 2.0.9":`^2.0.10`,"@opentelemetry/core@2.0.0 - 2.7.999":`2.8.0`,"brace-expansion@5.0.0 - 5.0.8":`^5.0.9`,"hono@4.0.0 - 4.12.33":`^4.12.34`,"js-yaml@4.0.0 - 4.3.0":`^4.3.1`,"js-yaml@5.0.0 - 5.2.1":`^5.2.2`,"liquidjs@10.0.0 - 10.27.0":`^10.27.1`,"protobufjs@8.0.0 - 8.7.1":`^8.7.2`},d=new Set(Object.keys(u).map(l));function f(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function p(e){return f(e)?{...e}:{}}function m(e){let t=p(e);for(let[e,n]of Object.entries(t))d.has(l(e))?delete t[e]:f(n)&&(t[e]=m(n));return t}function h(e){return r.join(e,c,`package.json`)}function g(e,t){let n=new Set,r=[...e.default?[e.default]:[],...t.map(t=>{let n=e.layers[t];if(!n)throw Error(`Unknown layer: ${t}`);return n})];for(let e of r)for(let t of e.packages??[]){let e=typeof t==`string`?t:t.name;(typeof t==`string`||t.optional!==!0)&&!a(e)&&n.add(e)}return[...n]}function _(e,t){let n=e.optionalPackageEntries?.(t);if(n!==void 0)return n;let r=e.optionalPackageEntry(t);return r?[r]:void 0}function v(e,t){let n=h(e),a=i.existsSync(n)?i.readFileSync(n,`utf8`):void 0,o;if(a===void 0)o={name:`pi-extensions`,private:!0};else{let e=JSON.parse(a);if(!f(e))throw Error(`Pi managed package manifest at ${n} must be a JSON object.`);o=e}let s=p(o.dependencies);for(let e of t){let t=e.slice(4);Object.hasOwn(s,t)||(s[t]=`*`)}o.dependencies=s,o.overrides={...m(o.overrides),...u};let c=p(o.pnpm);c.overrides={...m(c.overrides),...u},o.pnpm=c;let l=`${JSON.stringify(o,null,2)}\n`;return l!==a&&(i.mkdirSync(r.dirname(n),{recursive:!0}),i.writeFileSync(n,l,`utf8`),!0)}function y(e,t,n){return g(e,t).filter(e=>_(n,e)===void 0)}function b(t){return[...new Set(t.map(t=>`npm:${e(t).name}`))]}function x(e,t){let r=n(t),i=s.create(e,r,{projectTrusted:!0});return new o({cwd:e,agentDir:r,settingsManager:i})}async function S(e,n={}){let a=e.environment??process.env,o=n.resolvers??t(e.repoRoot),s=g(e.config,e.layers),c=s.filter(e=>_(o,e)===void 0),l=b(c),u=b(s);if(u.length===0)return[];let d=r.dirname(h(e.repoRoot));if(c.length===0&&!i.existsSync(d))return[];let f=v(e.repoRoot,u);if(c.length===0&&!f)return[];let p=n.packageManager??x(e.repoRoot,a);try{f?await p.install(u[0],{local:!0}):await p.resolveExtensionSources(l,{local:!0})}catch(e){throw Error(`Failed to reconcile DoomPi layer package(s): ${(f?u:l).join(`, `)}`,{cause:e})}let m=c.filter(e=>!_(o,e)?.length);if(m.length>0)throw Error(`Installed DoomPi layer package(s) did not expose the configured Pi extension: ${m.join(`, `)}`);return l}export{u as SAFE_TRANSITIVE_OVERRIDES,S as ensureLayerPackages,v as ensureManagedPackageManifest,y as missingLayerPackageSpecifiers};
1
+ import{splitPackageSpecifier as e}from"./modules/moduleResolution.mjs";import{createLayerResolvers as t}from"../services/extensionAssembler.mjs";import{piAgentDirectory as n}from"./piSettings.mjs";import r from"node:path";import i from"node:fs";import{isLocalPackageSpecifier as a}from"@agimon-ai/doompi-config/majorModes";import{execFile as o}from"node:child_process";import{promisify as s}from"node:util";import{DefaultPackageManager as c,SettingsManager as l}from"@earendil-works/pi-coding-agent";const u=`npm:`,d=r.join(`.pi`,`npm`),f=r.join(d,`node_modules`),p=`package.json`,m=s(o);function h(e){let t=e.indexOf(`@`,+!!e.startsWith(`@`));return t===-1?e:e.slice(0,t)}const g={"@hono/node-server@2.0.0 - 2.0.9":`^2.0.10`,"@opentelemetry/core@2.0.0 - 2.7.999":`2.8.0`,"brace-expansion@5.0.0 - 5.0.8":`^5.0.9`,"hono@4.0.0 - 4.12.33":`^4.12.34`,"js-yaml@4.0.0 - 4.3.0":`^4.3.1`,"js-yaml@5.0.0 - 5.2.1":`^5.2.2`,"liquidjs@10.0.0 - 10.27.0":`^10.27.1`,"protobufjs@8.0.0 - 8.7.1":`^8.7.2`},_=new Set(Object.keys(g).map(h));function v(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function y(e){return v(e)?{...e}:{}}function b(e){let t=y(e);for(let[e,n]of Object.entries(t))_.has(h(e))?delete t[e]:v(n)&&(t[e]=b(n));return t}function x(e){return r.join(e,d,p)}function S(e,t){let n=new Set,r=[...e.default?[e.default]:[],...t.map(t=>{let n=e.layers[t];if(!n)throw Error(`Unknown layer: ${t}`);return n})];for(let e of r)for(let t of e.packages??[]){let e=typeof t==`string`?t:t.name;(typeof t==`string`||t.optional!==!0)&&!a(e)&&n.add(e)}return[...n]}function C(e,t){let n=e.optionalPackageEntries?.(t);if(n!==void 0)return n;let r=e.optionalPackageEntry(t);return r?[r]:void 0}function w(e,t,n=new Map){let a=x(e),o=i.existsSync(a)?i.readFileSync(a,`utf8`):void 0,s;if(o===void 0)s={name:`pi-extensions`,private:!0};else{let e=JSON.parse(o);if(!v(e))throw Error(`Pi managed package manifest at ${a} must be a JSON object.`);s=e}let c=y(s.dependencies);for(let e of t){let t=E(e),r=n.get(t);r===void 0?Object.hasOwn(c,t)||(c[t]=`*`):c[t]=r}s.dependencies=c,s.overrides={...b(s.overrides),...g};let l=y(s.pnpm);l.overrides={...b(l.overrides),...g},s.pnpm=l;let u=`${JSON.stringify(s,null,2)}\n`;return u!==o&&(i.mkdirSync(r.dirname(a),{recursive:!0}),i.writeFileSync(a,u,`utf8`),!0)}function T(e,t,n){return S(e,t).filter(e=>C(n,e)===void 0)}function E(e){return e.startsWith(u)?e.slice(4):e}function D(t){return[...new Set(t.map(t=>`${u}${e(t).name}`))]}function O(e,t){return`${e} ${t}${e===1?``:`s`}`}function k(e,t){let n=r.join(e,f,t,p);if(i.existsSync(n))try{let e=JSON.parse(i.readFileSync(n,`utf8`));return v(e)&&typeof e.version==`string`?e.version:void 0}catch{return}}function A(e,t){let r=n(t),i=l.create(e,r,{projectTrusted:!0});return new c({cwd:e,agentDir:r,settingsManager:i})}function j(e,t){let[r,...i]=l.create(e,n(t),{projectTrusted:!0}).getNpmCommand()??[],a=r??`npm`,o=r?i:[];return async t=>{let{stdout:n}=await m(a,[...o,`view`,t,`version`,`--json`],{cwd:e,timeout:2e4,encoding:`utf8`}),r=JSON.parse(n.trim()||`null`);if(typeof r!=`string`)throw Error(`${a} view reported no published version`);return r}}async function M(e,t,n,r){let i=t.map(t=>({name:E(t),from:k(e,E(t))})).filter(e=>e.from!==void 0),a=await Promise.all(i.map(async e=>{try{return{...e,to:await n(e.name)}}catch(t){return{...e,failure:(t instanceof Error?t.message:String(t)).split(`
2
+ `)[0]}}})),o=[],s=[];for(let e of a){if(e.to===void 0){s.push(e.name),r?.(`kept ${e.name} ${e.from}: ${e.failure??`no published version`}`);continue}e.to!==e.from&&(o.push({name:e.name,from:e.from,to:e.to}),r?.(`${e.name} ${e.from} -> ${e.to}`))}return{updated:o,unchecked:s}}async function N(e,n={}){let a=e.environment??process.env,o=n.resolvers??t(e.repoRoot),s=S(e.config,e.layers),c=s.filter(e=>C(o,e)===void 0),l=D(c),u=D(s);if(u.length===0)return{installed:[],updated:[],unchecked:[]};let d=r.dirname(x(e.repoRoot));if(c.length===0&&!i.existsSync(d))return{installed:[],updated:[],unchecked:[]};let{updated:f,unchecked:p}=e.refresh?await M(e.repoRoot,u,n.publishedVersion??j(e.repoRoot,a),e.onProgress):{updated:[],unchecked:[]},m=w(e.repoRoot,u,new Map(f.map(e=>[e.name,e.to])));if(c.length===0&&!m)return{installed:[],updated:[],unchecked:p};let h=n.packageManager??A(e.repoRoot,a);e.onProgress?.(`installing ${f.length>0?O(f.length,`update`):c.length>0?O(c.length,`missing package`):`the package manifest`}`);try{m?await h.install(u[0],{local:!0}):await h.resolveExtensionSources(l,{local:!0})}catch(e){throw Error(`Failed to reconcile DoomPi layer package(s): ${(m?u:l).join(`, `)}`,{cause:e})}let g=c.filter(e=>!C(o,e)?.length);if(g.length>0)throw Error(`Installed DoomPi layer package(s) did not expose the configured Pi extension: ${g.join(`, `)}`);return{installed:l,updated:f,unchecked:p}}export{g as SAFE_TRANSITIVE_OVERRIDES,N as ensureLayerPackages,w as ensureManagedPackageManifest,T as missingLayerPackageSpecifiers};
2
3
  //# sourceMappingURL=layerPackageInstaller.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"layerPackageInstaller.mjs","names":[],"sources":["../../../src/adapters/layerPackageInstaller.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { isLocalPackageSpecifier, type MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { DefaultPackageManager, type PackageManager, SettingsManager } from '@earendil-works/pi-coding-agent';\nimport { createLayerResolvers, type ExtensionLayerResolvers } from '../services/extensionAssembler.ts';\nimport { splitPackageSpecifier } from './modules/moduleResolution.ts';\nimport { piAgentDirectory } from './piSettings.ts';\n\nconst NPM_SOURCE_PREFIX = 'npm:';\nconst PI_MANAGED_NPM_DIRECTORY = path.join('.pi', 'npm');\nconst PI_MANAGED_PACKAGE_NAME = 'pi-extensions';\nconst UNPINNED_PACKAGE_RANGE = '*';\n\nfunction overridePackageName(selector: string): string {\n const separatorIndex = selector.indexOf('@', selector.startsWith('@') ? 1 : 0);\n return separatorIndex === -1 ? selector : selector.slice(0, separatorIndex);\n}\n\n/**\n * Same-major patches for transitive versions pinned by the current foundation packages.\n * npm only honors overrides from the install root, so published extensions cannot carry\n * these fixes in their own manifests.\n */\nexport const SAFE_TRANSITIVE_OVERRIDES = {\n '@hono/node-server@2.0.0 - 2.0.9': '^2.0.10',\n '@opentelemetry/core@2.0.0 - 2.7.999': '2.8.0',\n 'brace-expansion@5.0.0 - 5.0.8': '^5.0.9',\n 'hono@4.0.0 - 4.12.33': '^4.12.34',\n 'js-yaml@4.0.0 - 4.3.0': '^4.3.1',\n 'js-yaml@5.0.0 - 5.2.1': '^5.2.2',\n 'liquidjs@10.0.0 - 10.27.0': '^10.27.1',\n 'protobufjs@8.0.0 - 8.7.1': '^8.7.2',\n} as const satisfies Readonly<Record<string, string>>;\n\nconst SAFE_TRANSITIVE_PACKAGE_NAMES = new Set(Object.keys(SAFE_TRANSITIVE_OVERRIDES).map(overridePackageName));\n\ntype LayerPackageManager = Pick<PackageManager, 'install' | 'resolveExtensionSources'>;\n\nexport interface EnsureLayerPackagesOptions {\n repoRoot: string;\n config: MajorModesConfig;\n layers: readonly string[];\n environment?: NodeJS.ProcessEnv;\n}\n\nexport interface LayerPackageInstallerDependencies {\n packageManager?: LayerPackageManager;\n resolvers?: ExtensionLayerResolvers;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction recordValue(value: unknown): Record<string, unknown> {\n return isRecord(value) ? { ...value } : {};\n}\n\nfunction withoutConflictingTransitiveOverrides(value: unknown): Record<string, unknown> {\n const overrides = recordValue(value);\n for (const [selector, replacement] of Object.entries(overrides)) {\n if (SAFE_TRANSITIVE_PACKAGE_NAMES.has(overridePackageName(selector))) {\n delete overrides[selector];\n } else if (isRecord(replacement)) {\n overrides[selector] = withoutConflictingTransitiveOverrides(replacement);\n }\n }\n return overrides;\n}\n\nfunction managedPackageManifestPath(repoRoot: string): string {\n return path.join(repoRoot, PI_MANAGED_NPM_DIRECTORY, 'package.json');\n}\n\nfunction configuredPackageSpecifiers(config: MajorModesConfig, layers: readonly string[]): string[] {\n const specifiers = new Set<string>();\n const selectedDefinitions = [\n ...(config.default ? [config.default] : []),\n ...layers.map((layerName) => {\n const layer = config.layers[layerName];\n if (!layer) throw new Error(`Unknown layer: ${layerName}`);\n return layer;\n }),\n ];\n for (const layer of selectedDefinitions) {\n for (const configured of layer.packages ?? []) {\n const specifier = typeof configured === 'string' ? configured : configured.name;\n const optional = typeof configured !== 'string' && configured.optional === true;\n if (!optional && !isLocalPackageSpecifier(specifier)) specifiers.add(specifier);\n }\n }\n return [...specifiers];\n}\n\nfunction resolvePackageEntries(resolvers: ExtensionLayerResolvers, specifier: string): string[] | undefined {\n const entries = resolvers.optionalPackageEntries?.(specifier);\n if (entries !== undefined) return entries;\n const entry = resolvers.optionalPackageEntry(specifier);\n return entry ? [entry] : undefined;\n}\n\n/** Adds the complete package set and patched transitive policy to Pi's managed install root. */\nexport function ensureManagedPackageManifest(repoRoot: string, sources: readonly string[]): boolean {\n const manifestPath = managedPackageManifestPath(repoRoot);\n const current = fs.existsSync(manifestPath) ? fs.readFileSync(manifestPath, 'utf8') : undefined;\n let manifest: Record<string, unknown>;\n if (current === undefined) {\n manifest = { name: PI_MANAGED_PACKAGE_NAME, private: true };\n } else {\n const parsed: unknown = JSON.parse(current);\n if (!isRecord(parsed)) throw new Error(`Pi managed package manifest at ${manifestPath} must be a JSON object.`);\n manifest = parsed;\n }\n\n const dependencies = recordValue(manifest.dependencies);\n for (const source of sources) {\n const name = source.slice(NPM_SOURCE_PREFIX.length);\n if (!Object.hasOwn(dependencies, name)) dependencies[name] = UNPINNED_PACKAGE_RANGE;\n }\n manifest.dependencies = dependencies;\n // npm gives nested and overlapping consumer rules precedence in cases that can retain\n // vulnerable versions, so DoomPi owns these package-specific selectors recursively.\n manifest.overrides = {\n ...withoutConflictingTransitiveOverrides(manifest.overrides),\n ...SAFE_TRANSITIVE_OVERRIDES,\n };\n const pnpm = recordValue(manifest.pnpm);\n pnpm.overrides = {\n ...withoutConflictingTransitiveOverrides(pnpm.overrides),\n ...SAFE_TRANSITIVE_OVERRIDES,\n };\n manifest.pnpm = pnpm;\n\n const serialized = `${JSON.stringify(manifest, null, 2)}\\n`;\n if (serialized === current) return false;\n fs.mkdirSync(path.dirname(manifestPath), { recursive: true });\n fs.writeFileSync(manifestPath, serialized, 'utf8');\n return true;\n}\n\n/** Finds required npm packages selected by defaults and active layers that DoomPi cannot resolve. */\nexport function missingLayerPackageSpecifiers(\n config: MajorModesConfig,\n layers: readonly string[],\n resolvers: ExtensionLayerResolvers,\n): string[] {\n return configuredPackageSpecifiers(config, layers).filter(\n (specifier) => resolvePackageEntries(resolvers, specifier) === undefined,\n );\n}\n\nfunction npmSources(specifiers: readonly string[]): string[] {\n return [...new Set(specifiers.map((specifier) => `${NPM_SOURCE_PREFIX}${splitPackageSpecifier(specifier).name}`))];\n}\n\nfunction createPackageManager(repoRoot: string, environment: NodeJS.ProcessEnv): LayerPackageManager {\n const agentDirectory = piAgentDirectory(environment);\n const settingsManager = SettingsManager.create(repoRoot, agentDirectory, { projectTrusted: true });\n return new DefaultPackageManager({ cwd: repoRoot, agentDir: agentDirectory, settingsManager });\n}\n\n/** Installs missing selected layer packages into Pi's project-local `.pi/npm` store and verifies the retry. */\nexport async function ensureLayerPackages(\n options: EnsureLayerPackagesOptions,\n dependencies: LayerPackageInstallerDependencies = {},\n): Promise<string[]> {\n const environment = options.environment ?? process.env;\n const resolvers = dependencies.resolvers ?? createLayerResolvers(options.repoRoot);\n const configured = configuredPackageSpecifiers(options.config, options.layers);\n const missing = configured.filter((specifier) => resolvePackageEntries(resolvers, specifier) === undefined);\n const missingSources = npmSources(missing);\n const sources = npmSources(configured);\n if (sources.length === 0) return [];\n\n const managedDirectory = path.dirname(managedPackageManifestPath(options.repoRoot));\n if (missing.length === 0 && !fs.existsSync(managedDirectory)) return [];\n const manifestChanged = ensureManagedPackageManifest(options.repoRoot, sources);\n if (missing.length === 0 && !manifestChanged) return [];\n\n const packageManager = dependencies.packageManager ?? createPackageManager(options.repoRoot, environment);\n try {\n if (manifestChanged) {\n // Installing one source reconciles the complete dependency set already written\n // to package.json, avoiding one npm install and audit report per DoomPi package.\n await packageManager.install(sources[0]!, { local: true });\n } else {\n await packageManager.resolveExtensionSources(missingSources, { local: true });\n }\n } catch (error) {\n const attempted = manifestChanged ? sources : missingSources;\n throw new Error(`Failed to reconcile DoomPi layer package(s): ${attempted.join(', ')}`, { cause: error });\n }\n\n const unresolved = missing.filter((specifier) => !resolvePackageEntries(resolvers, specifier)?.length);\n if (unresolved.length > 0) {\n throw new Error(\n `Installed DoomPi layer package(s) did not expose the configured Pi extension: ${unresolved.join(', ')}`,\n );\n }\n return missingSources;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"layerPackageInstaller.mjs","names":[],"sources":["../../../src/adapters/layerPackageInstaller.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\nimport { isLocalPackageSpecifier, type MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { DefaultPackageManager, type PackageManager, SettingsManager } from '@earendil-works/pi-coding-agent';\nimport { createLayerResolvers, type ExtensionLayerResolvers } from '../services/extensionAssembler.ts';\nimport { splitPackageSpecifier } from './modules/moduleResolution.ts';\nimport { piAgentDirectory } from './piSettings.ts';\n\nconst NPM_SOURCE_PREFIX = 'npm:';\nconst PI_MANAGED_NPM_DIRECTORY = path.join('.pi', 'npm');\nconst PI_MANAGED_MODULES_DIRECTORY = path.join(PI_MANAGED_NPM_DIRECTORY, 'node_modules');\nconst PI_MANAGED_PACKAGE_NAME = 'pi-extensions';\nconst UNPINNED_PACKAGE_RANGE = '*';\nconst PACKAGE_MANIFEST = 'package.json';\nconst DEFAULT_NPM_COMMAND = 'npm';\nconst REGISTRY_TIMEOUT_MS = 20_000;\n\nconst execFileAsync = promisify(execFile);\n\nfunction overridePackageName(selector: string): string {\n const separatorIndex = selector.indexOf('@', selector.startsWith('@') ? 1 : 0);\n return separatorIndex === -1 ? selector : selector.slice(0, separatorIndex);\n}\n\n/**\n * Same-major patches for transitive versions pinned by the current foundation packages.\n * npm only honors overrides from the install root, so published extensions cannot carry\n * these fixes in their own manifests.\n */\nexport const SAFE_TRANSITIVE_OVERRIDES = {\n '@hono/node-server@2.0.0 - 2.0.9': '^2.0.10',\n '@opentelemetry/core@2.0.0 - 2.7.999': '2.8.0',\n 'brace-expansion@5.0.0 - 5.0.8': '^5.0.9',\n 'hono@4.0.0 - 4.12.33': '^4.12.34',\n 'js-yaml@4.0.0 - 4.3.0': '^4.3.1',\n 'js-yaml@5.0.0 - 5.2.1': '^5.2.2',\n 'liquidjs@10.0.0 - 10.27.0': '^10.27.1',\n 'protobufjs@8.0.0 - 8.7.1': '^8.7.2',\n} as const satisfies Readonly<Record<string, string>>;\n\nconst SAFE_TRANSITIVE_PACKAGE_NAMES = new Set(Object.keys(SAFE_TRANSITIVE_OVERRIDES).map(overridePackageName));\n\ntype LayerPackageManager = Pick<PackageManager, 'install' | 'resolveExtensionSources'>;\n\nexport interface EnsureLayerPackagesOptions {\n repoRoot: string;\n config: MajorModesConfig;\n layers: readonly string[];\n environment?: NodeJS.ProcessEnv;\n /**\n * Move every managed package to its newest published version.\n *\n * Launch installs what a selection needs and nothing more, so a session never\n * pays for a registry round trip. Sync is the explicit maintenance command and\n * is the only caller that asks for this.\n */\n refresh?: boolean;\n /** Receives one human-readable line per package decision. */\n onProgress?: (message: string) => void;\n}\n\nexport interface LayerPackageInstallerDependencies {\n packageManager?: LayerPackageManager;\n resolvers?: ExtensionLayerResolvers;\n /** Registry lookup seam; the default reads the newest version with Pi's npm client. */\n publishedVersion?: (name: string) => Promise<string>;\n}\n\n/** One managed package moving from the installed version to the newest published one. */\nexport interface LayerPackageUpdate {\n name: string;\n from: string;\n to: string;\n}\n\nexport interface LayerPackageResult {\n /** Sources installed because a configured extension could not be resolved. */\n installed: string[];\n /** Packages this run moved to a newer published version. */\n updated: LayerPackageUpdate[];\n /** Packages left at the installed version because the registry could not be read. */\n unchecked: string[];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction recordValue(value: unknown): Record<string, unknown> {\n return isRecord(value) ? { ...value } : {};\n}\n\nfunction withoutConflictingTransitiveOverrides(value: unknown): Record<string, unknown> {\n const overrides = recordValue(value);\n for (const [selector, replacement] of Object.entries(overrides)) {\n if (SAFE_TRANSITIVE_PACKAGE_NAMES.has(overridePackageName(selector))) {\n delete overrides[selector];\n } else if (isRecord(replacement)) {\n overrides[selector] = withoutConflictingTransitiveOverrides(replacement);\n }\n }\n return overrides;\n}\n\nfunction managedPackageManifestPath(repoRoot: string): string {\n return path.join(repoRoot, PI_MANAGED_NPM_DIRECTORY, PACKAGE_MANIFEST);\n}\n\nfunction configuredPackageSpecifiers(config: MajorModesConfig, layers: readonly string[]): string[] {\n const specifiers = new Set<string>();\n const selectedDefinitions = [\n ...(config.default ? [config.default] : []),\n ...layers.map((layerName) => {\n const layer = config.layers[layerName];\n if (!layer) throw new Error(`Unknown layer: ${layerName}`);\n return layer;\n }),\n ];\n for (const layer of selectedDefinitions) {\n for (const configured of layer.packages ?? []) {\n const specifier = typeof configured === 'string' ? configured : configured.name;\n const optional = typeof configured !== 'string' && configured.optional === true;\n if (!optional && !isLocalPackageSpecifier(specifier)) specifiers.add(specifier);\n }\n }\n return [...specifiers];\n}\n\nfunction resolvePackageEntries(resolvers: ExtensionLayerResolvers, specifier: string): string[] | undefined {\n const entries = resolvers.optionalPackageEntries?.(specifier);\n if (entries !== undefined) return entries;\n const entry = resolvers.optionalPackageEntry(specifier);\n return entry ? [entry] : undefined;\n}\n\n/** Adds the complete package set and patched transitive policy to Pi's managed install root. */\nexport function ensureManagedPackageManifest(\n repoRoot: string,\n sources: readonly string[],\n targets: ReadonlyMap<string, string> = new Map(),\n): boolean {\n const manifestPath = managedPackageManifestPath(repoRoot);\n const current = fs.existsSync(manifestPath) ? fs.readFileSync(manifestPath, 'utf8') : undefined;\n let manifest: Record<string, unknown>;\n if (current === undefined) {\n manifest = { name: PI_MANAGED_PACKAGE_NAME, private: true };\n } else {\n const parsed: unknown = JSON.parse(current);\n if (!isRecord(parsed)) throw new Error(`Pi managed package manifest at ${manifestPath} must be a JSON object.`);\n manifest = parsed;\n }\n\n const dependencies = recordValue(manifest.dependencies);\n for (const source of sources) {\n const name = packageName(source);\n const target = targets.get(name);\n // An exact target is what makes an update reach the store: npm keeps a\n // locked version whenever the recorded range still admits it, and the range\n // it writes back after an install always admits the version it just wrote.\n if (target !== undefined) dependencies[name] = target;\n else if (!Object.hasOwn(dependencies, name)) dependencies[name] = UNPINNED_PACKAGE_RANGE;\n }\n manifest.dependencies = dependencies;\n // npm gives nested and overlapping consumer rules precedence in cases that can retain\n // vulnerable versions, so DoomPi owns these package-specific selectors recursively.\n manifest.overrides = {\n ...withoutConflictingTransitiveOverrides(manifest.overrides),\n ...SAFE_TRANSITIVE_OVERRIDES,\n };\n const pnpm = recordValue(manifest.pnpm);\n pnpm.overrides = {\n ...withoutConflictingTransitiveOverrides(pnpm.overrides),\n ...SAFE_TRANSITIVE_OVERRIDES,\n };\n manifest.pnpm = pnpm;\n\n const serialized = `${JSON.stringify(manifest, null, 2)}\\n`;\n if (serialized === current) return false;\n fs.mkdirSync(path.dirname(manifestPath), { recursive: true });\n fs.writeFileSync(manifestPath, serialized, 'utf8');\n return true;\n}\n\n/** Finds required npm packages selected by defaults and active layers that DoomPi cannot resolve. */\nexport function missingLayerPackageSpecifiers(\n config: MajorModesConfig,\n layers: readonly string[],\n resolvers: ExtensionLayerResolvers,\n): string[] {\n return configuredPackageSpecifiers(config, layers).filter(\n (specifier) => resolvePackageEntries(resolvers, specifier) === undefined,\n );\n}\n\nfunction packageName(source: string): string {\n return source.startsWith(NPM_SOURCE_PREFIX) ? source.slice(NPM_SOURCE_PREFIX.length) : source;\n}\n\nfunction npmSources(specifiers: readonly string[]): string[] {\n return [...new Set(specifiers.map((specifier) => `${NPM_SOURCE_PREFIX}${splitPackageSpecifier(specifier).name}`))];\n}\n\nfunction pluralize(count: number, noun: string): string {\n return `${count} ${noun}${count === 1 ? '' : 's'}`;\n}\n\n/** Version of a package inside Pi's managed store, absent when sync does not own it. */\nfunction managedPackageVersion(repoRoot: string, name: string): string | undefined {\n const manifestPath = path.join(repoRoot, PI_MANAGED_MODULES_DIRECTORY, name, PACKAGE_MANIFEST);\n if (!fs.existsSync(manifestPath)) return undefined;\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n return isRecord(parsed) && typeof parsed.version === 'string' ? parsed.version : undefined;\n } catch {\n // An unreadable manifest carries the same meaning as an absent one: nothing\n // usable is installed, so the caller treats the package as uninstalled.\n return undefined;\n }\n}\n\nfunction createPackageManager(repoRoot: string, environment: NodeJS.ProcessEnv): LayerPackageManager {\n const agentDirectory = piAgentDirectory(environment);\n const settingsManager = SettingsManager.create(repoRoot, agentDirectory, { projectTrusted: true });\n return new DefaultPackageManager({ cwd: repoRoot, agentDir: agentDirectory, settingsManager });\n}\n\n/** Reads newest published versions with the same client Pi installs them with. */\nfunction createVersionReader(repoRoot: string, environment: NodeJS.ProcessEnv): (name: string) => Promise<string> {\n const configured = SettingsManager.create(repoRoot, piAgentDirectory(environment), {\n projectTrusted: true,\n }).getNpmCommand();\n const [command, ...args] = configured ?? [];\n const client = command ?? DEFAULT_NPM_COMMAND;\n const clientArguments = command ? args : [];\n return async (name) => {\n const { stdout } = await execFileAsync(client, [...clientArguments, 'view', name, 'version', '--json'], {\n cwd: repoRoot,\n timeout: REGISTRY_TIMEOUT_MS,\n encoding: 'utf8',\n });\n const parsed: unknown = JSON.parse(stdout.trim() || 'null');\n if (typeof parsed !== 'string') throw new Error(`${client} view reported no published version`);\n return parsed;\n };\n}\n\ninterface CheckedPackage {\n name: string;\n from: string;\n to?: string;\n failure?: string;\n}\n\n/**\n * Compares every package sync installed against the registry.\n *\n * Only the managed store is consulted: a package that resolves from the\n * repository's own modules is not sync's to move, and a package that is not\n * installed yet is already covered by the missing-package path.\n */\nasync function resolveUpdates(\n repoRoot: string,\n sources: readonly string[],\n publishedVersion: (name: string) => Promise<string>,\n onProgress: ((message: string) => void) | undefined,\n): Promise<Pick<LayerPackageResult, 'updated' | 'unchecked'>> {\n const managed = sources\n .map((source) => ({ name: packageName(source), from: managedPackageVersion(repoRoot, packageName(source)) }))\n .filter((entry): entry is { name: string; from: string } => entry.from !== undefined);\n const checked: CheckedPackage[] = await Promise.all(\n managed.map(async (entry) => {\n try {\n return { ...entry, to: await publishedVersion(entry.name) };\n } catch (error) {\n return { ...entry, failure: (error instanceof Error ? error.message : String(error)).split('\\n')[0] };\n }\n }),\n );\n\n const updated: LayerPackageUpdate[] = [];\n const unchecked: string[] = [];\n for (const entry of checked) {\n if (entry.to === undefined) {\n unchecked.push(entry.name);\n onProgress?.(`kept ${entry.name} ${entry.from}: ${entry.failure ?? 'no published version'}`);\n continue;\n }\n if (entry.to === entry.from) continue;\n updated.push({ name: entry.name, from: entry.from, to: entry.to });\n onProgress?.(`${entry.name} ${entry.from} -> ${entry.to}`);\n }\n return { updated, unchecked };\n}\n\n/** Installs selected layer packages into Pi's project-local `.pi/npm` store and verifies the retry. */\nexport async function ensureLayerPackages(\n options: EnsureLayerPackagesOptions,\n dependencies: LayerPackageInstallerDependencies = {},\n): Promise<LayerPackageResult> {\n const environment = options.environment ?? process.env;\n const resolvers = dependencies.resolvers ?? createLayerResolvers(options.repoRoot);\n const configured = configuredPackageSpecifiers(options.config, options.layers);\n const missing = configured.filter((specifier) => resolvePackageEntries(resolvers, specifier) === undefined);\n const missingSources = npmSources(missing);\n const sources = npmSources(configured);\n if (sources.length === 0) return { installed: [], updated: [], unchecked: [] };\n\n const managedDirectory = path.dirname(managedPackageManifestPath(options.repoRoot));\n if (missing.length === 0 && !fs.existsSync(managedDirectory)) return { installed: [], updated: [], unchecked: [] };\n\n const { updated, unchecked } = options.refresh\n ? await resolveUpdates(\n options.repoRoot,\n sources,\n dependencies.publishedVersion ?? createVersionReader(options.repoRoot, environment),\n options.onProgress,\n )\n : { updated: [], unchecked: [] };\n const manifestChanged = ensureManagedPackageManifest(\n options.repoRoot,\n sources,\n new Map(updated.map((update) => [update.name, update.to])),\n );\n if (missing.length === 0 && !manifestChanged) return { installed: [], updated: [], unchecked };\n\n const packageManager = dependencies.packageManager ?? createPackageManager(options.repoRoot, environment);\n options.onProgress?.(\n `installing ${\n updated.length > 0\n ? pluralize(updated.length, 'update')\n : missing.length > 0\n ? pluralize(missing.length, 'missing package')\n : 'the package manifest'\n }`,\n );\n try {\n if (manifestChanged) {\n // Installing one source reconciles the complete dependency set already written\n // to package.json, avoiding one npm install and audit report per DoomPi package.\n await packageManager.install(sources[0]!, { local: true });\n } else {\n await packageManager.resolveExtensionSources(missingSources, { local: true });\n }\n } catch (error) {\n const attempted = manifestChanged ? sources : missingSources;\n throw new Error(`Failed to reconcile DoomPi layer package(s): ${attempted.join(', ')}`, { cause: error });\n }\n\n const unresolved = missing.filter((specifier) => !resolvePackageEntries(resolvers, specifier)?.length);\n if (unresolved.length > 0) {\n throw new Error(\n `Installed DoomPi layer package(s) did not expose the configured Pi extension: ${unresolved.join(', ')}`,\n );\n }\n return { installed: missingSources, updated, unchecked };\n}\n"],"mappings":"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"}
@@ -7,7 +7,8 @@ Usage: doompi [harness options] [Pi options] [prompt]
7
7
 
8
8
  Initialization:
9
9
  doompi init Fill missing files in ~/.pi/.doom
10
- doompi sync Build, resolve, and register DoomPi in Pi user settings
10
+ doompi sync Update packages, build, resolve, and register DoomPi
11
+ in Pi user settings
11
12
  doompi sync --check Exit non-zero when the synced config is out of date
12
13
  or report legacy repository-local state to migrate
13
14
 
@@ -1 +1 @@
1
- {"version":3,"file":"help.cjs","names":["require","createRequire"],"sources":["../../../../src/commands/cli/help.ts"],"sourcesContent":["import { createRequire } from 'node:module';\n\n// createRequire rather than a JSON import: this file runs both from src under\n// Node's strip-only TypeScript mode (how pi.sh starts it) and from dist. The\n// package self-reference resolves the same published manifest from either tree.\nconst require = createRequire(import.meta.url);\nconst PACKAGE_MANIFEST = '@agimon-ai/doompi/package.json';\n\nexport const HARNESS_VERSION = (require(PACKAGE_MANIFEST) as { version: string }).version;\n\nexport function printHelp(): void {\n process.stdout.write(`doompi\n\nUsage: doompi [harness options] [Pi options] [prompt]\n doompi init\n doompi sync [matrix options] [--check]\n doompi compat <codex|claude|antigravity> [matrix options] [provider arguments]\n\nInitialization:\n doompi init Fill missing files in ~/.pi/.doom\n doompi sync Build, resolve, and register DoomPi in Pi user settings\n doompi sync --check Exit non-zero when the synced config is out of date\n or report legacy repository-local state to migrate\n\nSync artifacts live under ~/.pi/.doom/sync in a repository- and worktree-specific\nnamespace. Linked worktrees isolate state, dist, manifests, resources, and run\nfiles while sharing immutable repository build objects. A legacy <repo>/.pi/doom\nstate is read only when no global state exists; run doompi sync to migrate it.\nDoomPi never removes old or orphaned generated directories automatically.\n\nAfter a sync, run pi directly. The synced session accepts --major-mode,\n--domains, --profile, and --mute, and /mode, /domains, and /profile switch\nwithout a restart. Selection defaults come from the selection block in\n.doom/config.yaml.\n\nCompatibility matrix options:\n --profile <name> Persona and env from .doom/profiles.yaml\n --domains <names> Comma-separated content domains\n --major-mode <name> Named major mode from .doom/modes.yaml\n -- Pass following matrix-named flags to the provider\n\nAll other compatibility arguments pass to the provider unchanged.\n\nHarness options:\n --major-mode <name> Named major mode from .doom/modes.yaml\n (default: copilot). Not --mode: Pi owns that one.\n --profile <name> Persona and env from .doom/profiles.yaml\n --domains <names> Comma-separated domains (content: plugins, skills)\n --no-domains Load no content domains\n --explain Print the resolved matrix and exit\n --emit-mcp <dir> Write the resolved MCP config to a directory and exit\n --plugin-dir <path> Load an explicit plugin directory, repeatable\n --add-dir <path> Add an accessible directory, repeatable\n --preset <name> default, kimi, or ollama\n --cwd <path> Run Pi in this directory\n --automation Use print mode, JSON output, and project approval\n --output-format vibe-lint Read a vibe-lint request from stdin and emit one JSON response\n --mute Disable the notification extension for this run\n --auto-stop Exit interactive Pi when the agent settles\n --allow-protected-writes Allow writes to protected repository paths\n --hooks, --no-hooks Enable or disable repository and plugin hooks\n --mcp, --no-mcp Enable or disable MCP configuration\n --agents, --no-agents Enable or disable spawnable agents\n -h, --help Show harness help\n -v, --version Show harness version\n\nInteractive commands:\n /loop Open the loop launcher picker\n /loops List active loops and stop future scheduling\n /workflow [on|off] Toggle workflow tools for the agent\n /effort <level> Set and persist the thinking effort\n /plan [on|off|status] Toggle repository read-only plan mode\n /mode Pick a named major mode\n /domains [names] Show the selected domains or pick a different set\n /profile List or switch persona and environment profiles\n\nAll remaining options are passed to Pi.\n`);\n}\n"],"mappings":"AAKA,MAGa,GAHPA,uBAAUC,CAAAA,CAAAA,cAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,IAGgBD,CAAAA,CAAQ,gCAAgB,CAAC,CAAyB,QAElF,SAAgB,GAAkB,CAChC,QAAQ,OAAO,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkEtB,CACD"}
1
+ {"version":3,"file":"help.cjs","names":["require","createRequire"],"sources":["../../../../src/commands/cli/help.ts"],"sourcesContent":["import { createRequire } from 'node:module';\n\n// createRequire rather than a JSON import: this file runs both from src under\n// Node's strip-only TypeScript mode (how pi.sh starts it) and from dist. The\n// package self-reference resolves the same published manifest from either tree.\nconst require = createRequire(import.meta.url);\nconst PACKAGE_MANIFEST = '@agimon-ai/doompi/package.json';\n\nexport const HARNESS_VERSION = (require(PACKAGE_MANIFEST) as { version: string }).version;\n\nexport function printHelp(): void {\n process.stdout.write(`doompi\n\nUsage: doompi [harness options] [Pi options] [prompt]\n doompi init\n doompi sync [matrix options] [--check]\n doompi compat <codex|claude|antigravity> [matrix options] [provider arguments]\n\nInitialization:\n doompi init Fill missing files in ~/.pi/.doom\n doompi sync Update packages, build, resolve, and register DoomPi\n in Pi user settings\n doompi sync --check Exit non-zero when the synced config is out of date\n or report legacy repository-local state to migrate\n\nSync artifacts live under ~/.pi/.doom/sync in a repository- and worktree-specific\nnamespace. Linked worktrees isolate state, dist, manifests, resources, and run\nfiles while sharing immutable repository build objects. A legacy <repo>/.pi/doom\nstate is read only when no global state exists; run doompi sync to migrate it.\nDoomPi never removes old or orphaned generated directories automatically.\n\nAfter a sync, run pi directly. The synced session accepts --major-mode,\n--domains, --profile, and --mute, and /mode, /domains, and /profile switch\nwithout a restart. Selection defaults come from the selection block in\n.doom/config.yaml.\n\nCompatibility matrix options:\n --profile <name> Persona and env from .doom/profiles.yaml\n --domains <names> Comma-separated content domains\n --major-mode <name> Named major mode from .doom/modes.yaml\n -- Pass following matrix-named flags to the provider\n\nAll other compatibility arguments pass to the provider unchanged.\n\nHarness options:\n --major-mode <name> Named major mode from .doom/modes.yaml\n (default: copilot). Not --mode: Pi owns that one.\n --profile <name> Persona and env from .doom/profiles.yaml\n --domains <names> Comma-separated domains (content: plugins, skills)\n --no-domains Load no content domains\n --explain Print the resolved matrix and exit\n --emit-mcp <dir> Write the resolved MCP config to a directory and exit\n --plugin-dir <path> Load an explicit plugin directory, repeatable\n --add-dir <path> Add an accessible directory, repeatable\n --preset <name> default, kimi, or ollama\n --cwd <path> Run Pi in this directory\n --automation Use print mode, JSON output, and project approval\n --output-format vibe-lint Read a vibe-lint request from stdin and emit one JSON response\n --mute Disable the notification extension for this run\n --auto-stop Exit interactive Pi when the agent settles\n --allow-protected-writes Allow writes to protected repository paths\n --hooks, --no-hooks Enable or disable repository and plugin hooks\n --mcp, --no-mcp Enable or disable MCP configuration\n --agents, --no-agents Enable or disable spawnable agents\n -h, --help Show harness help\n -v, --version Show harness version\n\nInteractive commands:\n /loop Open the loop launcher picker\n /loops List active loops and stop future scheduling\n /workflow [on|off] Toggle workflow tools for the agent\n /effort <level> Set and persist the thinking effort\n /plan [on|off|status] Toggle repository read-only plan mode\n /mode Pick a named major mode\n /domains [names] Show the selected domains or pick a different set\n /profile List or switch persona and environment profiles\n\nAll remaining options are passed to Pi.\n`);\n}\n"],"mappings":"AAKA,MAGa,GAHPA,uBAAUC,CAAAA,CAAAA,cAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,IAGgBD,CAAAA,CAAQ,gCAAgB,CAAC,CAAyB,QAElF,SAAgB,GAAkB,CAChC,QAAQ,OAAO,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmEtB,CACD"}
@@ -7,7 +7,8 @@ Usage: doompi [harness options] [Pi options] [prompt]
7
7
 
8
8
  Initialization:
9
9
  doompi init Fill missing files in ~/.pi/.doom
10
- doompi sync Build, resolve, and register DoomPi in Pi user settings
10
+ doompi sync Update packages, build, resolve, and register DoomPi
11
+ in Pi user settings
11
12
  doompi sync --check Exit non-zero when the synced config is out of date
12
13
  or report legacy repository-local state to migrate
13
14
 
@@ -1 +1 @@
1
- {"version":3,"file":"help.mjs","names":[],"sources":["../../../../src/commands/cli/help.ts"],"sourcesContent":["import { createRequire } from 'node:module';\n\n// createRequire rather than a JSON import: this file runs both from src under\n// Node's strip-only TypeScript mode (how pi.sh starts it) and from dist. The\n// package self-reference resolves the same published manifest from either tree.\nconst require = createRequire(import.meta.url);\nconst PACKAGE_MANIFEST = '@agimon-ai/doompi/package.json';\n\nexport const HARNESS_VERSION = (require(PACKAGE_MANIFEST) as { version: string }).version;\n\nexport function printHelp(): void {\n process.stdout.write(`doompi\n\nUsage: doompi [harness options] [Pi options] [prompt]\n doompi init\n doompi sync [matrix options] [--check]\n doompi compat <codex|claude|antigravity> [matrix options] [provider arguments]\n\nInitialization:\n doompi init Fill missing files in ~/.pi/.doom\n doompi sync Build, resolve, and register DoomPi in Pi user settings\n doompi sync --check Exit non-zero when the synced config is out of date\n or report legacy repository-local state to migrate\n\nSync artifacts live under ~/.pi/.doom/sync in a repository- and worktree-specific\nnamespace. Linked worktrees isolate state, dist, manifests, resources, and run\nfiles while sharing immutable repository build objects. A legacy <repo>/.pi/doom\nstate is read only when no global state exists; run doompi sync to migrate it.\nDoomPi never removes old or orphaned generated directories automatically.\n\nAfter a sync, run pi directly. The synced session accepts --major-mode,\n--domains, --profile, and --mute, and /mode, /domains, and /profile switch\nwithout a restart. Selection defaults come from the selection block in\n.doom/config.yaml.\n\nCompatibility matrix options:\n --profile <name> Persona and env from .doom/profiles.yaml\n --domains <names> Comma-separated content domains\n --major-mode <name> Named major mode from .doom/modes.yaml\n -- Pass following matrix-named flags to the provider\n\nAll other compatibility arguments pass to the provider unchanged.\n\nHarness options:\n --major-mode <name> Named major mode from .doom/modes.yaml\n (default: copilot). Not --mode: Pi owns that one.\n --profile <name> Persona and env from .doom/profiles.yaml\n --domains <names> Comma-separated domains (content: plugins, skills)\n --no-domains Load no content domains\n --explain Print the resolved matrix and exit\n --emit-mcp <dir> Write the resolved MCP config to a directory and exit\n --plugin-dir <path> Load an explicit plugin directory, repeatable\n --add-dir <path> Add an accessible directory, repeatable\n --preset <name> default, kimi, or ollama\n --cwd <path> Run Pi in this directory\n --automation Use print mode, JSON output, and project approval\n --output-format vibe-lint Read a vibe-lint request from stdin and emit one JSON response\n --mute Disable the notification extension for this run\n --auto-stop Exit interactive Pi when the agent settles\n --allow-protected-writes Allow writes to protected repository paths\n --hooks, --no-hooks Enable or disable repository and plugin hooks\n --mcp, --no-mcp Enable or disable MCP configuration\n --agents, --no-agents Enable or disable spawnable agents\n -h, --help Show harness help\n -v, --version Show harness version\n\nInteractive commands:\n /loop Open the loop launcher picker\n /loops List active loops and stop future scheduling\n /workflow [on|off] Toggle workflow tools for the agent\n /effort <level> Set and persist the thinking effort\n /plan [on|off|status] Toggle repository read-only plan mode\n /mode Pick a named major mode\n /domains [names] Show the selected domains or pick a different set\n /profile List or switch persona and environment profiles\n\nAll remaining options are passed to Pi.\n`);\n}\n"],"mappings":"4CAKA,MAGa,EAHG,EAAc,YAAY,GAGV,CAAA,CAAQ,gCAAgB,CAAC,CAAyB,QAElF,SAAgB,GAAkB,CAChC,QAAQ,OAAO,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkEtB,CACD"}
1
+ {"version":3,"file":"help.mjs","names":[],"sources":["../../../../src/commands/cli/help.ts"],"sourcesContent":["import { createRequire } from 'node:module';\n\n// createRequire rather than a JSON import: this file runs both from src under\n// Node's strip-only TypeScript mode (how pi.sh starts it) and from dist. The\n// package self-reference resolves the same published manifest from either tree.\nconst require = createRequire(import.meta.url);\nconst PACKAGE_MANIFEST = '@agimon-ai/doompi/package.json';\n\nexport const HARNESS_VERSION = (require(PACKAGE_MANIFEST) as { version: string }).version;\n\nexport function printHelp(): void {\n process.stdout.write(`doompi\n\nUsage: doompi [harness options] [Pi options] [prompt]\n doompi init\n doompi sync [matrix options] [--check]\n doompi compat <codex|claude|antigravity> [matrix options] [provider arguments]\n\nInitialization:\n doompi init Fill missing files in ~/.pi/.doom\n doompi sync Update packages, build, resolve, and register DoomPi\n in Pi user settings\n doompi sync --check Exit non-zero when the synced config is out of date\n or report legacy repository-local state to migrate\n\nSync artifacts live under ~/.pi/.doom/sync in a repository- and worktree-specific\nnamespace. Linked worktrees isolate state, dist, manifests, resources, and run\nfiles while sharing immutable repository build objects. A legacy <repo>/.pi/doom\nstate is read only when no global state exists; run doompi sync to migrate it.\nDoomPi never removes old or orphaned generated directories automatically.\n\nAfter a sync, run pi directly. The synced session accepts --major-mode,\n--domains, --profile, and --mute, and /mode, /domains, and /profile switch\nwithout a restart. Selection defaults come from the selection block in\n.doom/config.yaml.\n\nCompatibility matrix options:\n --profile <name> Persona and env from .doom/profiles.yaml\n --domains <names> Comma-separated content domains\n --major-mode <name> Named major mode from .doom/modes.yaml\n -- Pass following matrix-named flags to the provider\n\nAll other compatibility arguments pass to the provider unchanged.\n\nHarness options:\n --major-mode <name> Named major mode from .doom/modes.yaml\n (default: copilot). Not --mode: Pi owns that one.\n --profile <name> Persona and env from .doom/profiles.yaml\n --domains <names> Comma-separated domains (content: plugins, skills)\n --no-domains Load no content domains\n --explain Print the resolved matrix and exit\n --emit-mcp <dir> Write the resolved MCP config to a directory and exit\n --plugin-dir <path> Load an explicit plugin directory, repeatable\n --add-dir <path> Add an accessible directory, repeatable\n --preset <name> default, kimi, or ollama\n --cwd <path> Run Pi in this directory\n --automation Use print mode, JSON output, and project approval\n --output-format vibe-lint Read a vibe-lint request from stdin and emit one JSON response\n --mute Disable the notification extension for this run\n --auto-stop Exit interactive Pi when the agent settles\n --allow-protected-writes Allow writes to protected repository paths\n --hooks, --no-hooks Enable or disable repository and plugin hooks\n --mcp, --no-mcp Enable or disable MCP configuration\n --agents, --no-agents Enable or disable spawnable agents\n -h, --help Show harness help\n -v, --version Show harness version\n\nInteractive commands:\n /loop Open the loop launcher picker\n /loops List active loops and stop future scheduling\n /workflow [on|off] Toggle workflow tools for the agent\n /effort <level> Set and persist the thinking effort\n /plan [on|off|status] Toggle repository read-only plan mode\n /mode Pick a named major mode\n /domains [names] Show the selected domains or pick a different set\n /profile List or switch persona and environment profiles\n\nAll remaining options are passed to Pi.\n`);\n}\n"],"mappings":"4CAKA,MAGa,EAHG,EAAc,YAAY,GAGV,CAAA,CAAQ,gCAAgB,CAAC,CAAyB,QAElF,SAAgB,GAAkB,CAChC,QAAQ,OAAO,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmEtB,CACD"}
@@ -1,7 +1,7 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../adapters/repository/repository.cjs"),n=require("../adapters/syncLocation.cjs"),r=require("../services/extensionAssembler.cjs");require("../adapters/syncStateContract.cjs");const i=require("../adapters/syncState.cjs"),a=require("../adapters/bootstrapLocator.cjs"),o=require("./cli/matrixOptions.cjs"),s=require("./cli/options.cjs");require("../adapters/config/harnessState.cjs");const c=require("../adapters/piExtensionAlias.cjs"),l=require("../adapters/piSettings.cjs"),u=require("../services/config/projectTrust.cjs"),d=require("../adapters/harnessContext.cjs"),f=require("../adapters/layerPackageInstaller.cjs"),p=require("../adapters/projectPiSettings.cjs"),m=require("../adapters/syncedRuntimeBuilder.cjs");let h=require("node:path");h=e.__toESM(h,1);let g=require("node:fs");g=e.__toESM(g,1);let _=require("node:os");_=e.__toESM(_,1);let v=require("@agimon-ai/doompi-config/domains"),y=require("@agimon-ai/doompi-config/majorModes"),b=require("node:child_process"),x=require("@agimon-ai/doompi-ui/theme"),S=require("@agimon-ai/doompi-config/harnessStore");const C=`--check`,w=h.default.join(`tools`,`harness`,`emit-hooks.mjs`),T=`(none)`,E=[`DOOMPI_`],D=[`CLAUDE_PROJECT_DIR`,`CODEX_REPO_ROOT`,`ORIGINAL_REPO_PATH`,`MCP_UI_VIEWER`],O=new Set([`DOOMPI_CHILD_EXTENSIONS`,`DOOMPI_COMPOSED`,`DOOMPI_MUTE`,`DOOMPI_TEMP_DIR`,S.HARNESS_STATE_POINTER,`PI_SUBAGENT_PI_BINARY`]);function k(e){let t={};for(let[n,r]of Object.entries(e))r===void 0||O.has(n)||(D.includes(n)||E.some(e=>n.startsWith(e)))&&(t[n]=r);return t}function A(e,t){let{selection:n}=u.loadDoomConfig(e);return n?{...t,...n.majorMode&&!t.DOOMPI_MAJOR_MODE?{[o.DOOMPI_MAJOR_MODE_ENV]:n.majorMode}:{},...n.profile&&!t.DOOMPI_PROFILE?{[o.DOOMPI_PROFILE_ENV]:n.profile}:{},...n.domains&&t.DOOMPI_DOMAINS===void 0?{[o.DOOMPI_DOMAINS_ENV]:n.domains.join(`,`)}:{}}:t}function j(e){return{majorMode:e.majorMode,domains:e.domains,profile:e.profile,preset:e.preset}}function M(e,t){let n=(0,y.loadMajorModesConfig)(e),i=r.createLayerResolvers(e);return r.resolveExtensionComposition({agents:t.agents,autoStop:!1,mute:!1,preset:t.preset,personaEntry:i.packageEntry(r.PERSONA_ENTRY),majorMode:t.majorMode,layers:(0,y.filterHookDisabledLayers)(n,(0,y.resolveLayers)(n,t.majorMode),t.hooks),majorModesConfig:n,resolvers:i}).fingerprint}function N(e,t,n,o=process.env,s=`persisted`,u){if(!n)return[`no sync state: run doompi sync`];let d=[];i.syncStateRootMatches(e,n.root)||d.push(`sync state belongs to a different repository`);let f=n.selection;(f.majorMode!==t.majorMode||f.profile!==t.profile||f.preset!==t.preset||f.domains.join(`,`)!==t.domains.join(`,`))&&d.push(`selection changed since the last sync`),i.computeInputsHash(e,f,o.HOME??_.default.homedir())!==n.inputsHash&&d.push(`.doom configuration changed`),JSON.stringify(i.recordResolvedEntries((0,y.loadMajorModesConfig)(e),r.createLayerResolvers(e)))!==JSON.stringify(n.resolved)&&d.push(`resolved extension paths changed`),u&&n.compositionFingerprint!==u&&d.push(`extension composition changed`);try{a.readBootstrapStatus(e,void 0,o.HOME??_.default.homedir()).fresh||d.push(`precompiled runtime is missing or stale`)}catch{d.push(`precompiled runtime is missing or stale`)}let m=l.piAgentDirectory(o),v=h.default.join(l.piThemeDirectory(m),`${x.DEFAULT_THEME_NAME}.json`);if(s===`persisted`){let t=l.readPiSettings(m),n=l.mergePiSettings(t,m,{themePath:v,themeName:x.DEFAULT_THEME_NAME});l.serializePiSettings(n)!==l.serializePiSettings(t)&&d.push(`Pi user settings are out of date`),p.projectRegistersDoom(e)&&d.push(p.DUPLICATE_REGISTRATION_DRIFT)}c.piExtensionAliasIsCurrent(m)||d.push(`Pi user extension alias is out of date`);let b=`${JSON.stringify(x.DEFAULT_THEME,null,2)}\n`;return(!g.default.existsSync(v)||g.default.readFileSync(v,`utf8`)!==b)&&d.push(`Pi user theme is out of date`),(n.baseline.themePath!==v||n.baseline.themeName!==x.DEFAULT_THEME_NAME)&&d.push(`synced theme location is out of date`),d}function P(e,t){let n=h.default.join(e,w);if(!g.default.existsSync(n))return;let r=(0,b.spawnSync)(process.execPath,[n,`--write`],{cwd:e,encoding:`utf8`});if(r.status===0){t.write(`hooks: regenerated for Claude Code and Codex
2
- `);return}t.write(`hooks: emit-hooks failed (${r.stderr?.trim()||`exit ${String(r.status)}`})\n`)}function F(e,t=`pi`){let{selection:n}=e;return[`mode: ${n.majorMode}`,`domains: ${n.domains.join(`, `)||T}`,`profile: ${n.profile??T}`,`skills: ${e.skillCount}`,`agents: ${e.agentCount}`,`mcp: ${e.mcpServers.join(`, `)||T}`,`state: ${e.statePath}`,...e.settingsPath?[`settings: ${e.settingsPath}`]:[],...e.projectSettingsPath?[`project: removed duplicate registration from ${e.projectSettingsPath}`]:[],``,`Run ${t} from the repository root to use it.`,``].join(`
3
- `)}var I=class{name=`sync`;settingsMode;homeDirectory;constructor(e={}){this.settingsMode=e.settingsMode??`persisted`,this.homeDirectory=e.homeDirectory}matches(e){return e[0]===this.name}async execute(e,a=process.env,o=process.cwd(),c=process.stdout){let l=e.includes(C),u=e.slice(1).filter(e=>e!==C),d=a.DOOMPI_ROOT,p=d?h.default.resolve(d):t.findRepositoryRoot(o),m=(0,y.loadMajorModesConfig)(p).defaultMajorMode,g=(0,v.loadDomains)(p).defaultDomains,b=s.parseHarnessArgs(u,A(p,a),o,m,g),x=j(b.options),S=this.homeDirectory??a.HOME??_.default.homedir();if(l){let e=(0,y.loadMajorModesConfig)(p),t=f.missingLayerPackageSpecifiers(e,Object.keys(e.layers),r.createLayerResolvers(p));if(t.length>0)return c.write(`doompi sync is out of date:\n${t.map(e=>` configured package is not installed: ${e}`).join(`
4
- `)}\n`),1;let n;try{n=i.readLocatedSyncState(p,S)}catch(e){let t=e instanceof Error?e.message:String(e);return c.write(`doompi sync is out of date:\n ${t}\n`),1}let o=M(p,b.options),s=N(p,x,n?.state,a,this.settingsMode,o);return n?.layout===`legacy`&&s.unshift(`legacy repository-local sync state requires migration`),s.length===0?(c.write(`doompi sync is up to date
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../adapters/repository/repository.cjs"),n=require("../adapters/syncLocation.cjs"),r=require("../services/extensionAssembler.cjs");require("../adapters/syncStateContract.cjs");const i=require("../adapters/syncState.cjs"),a=require("../adapters/bootstrapLocator.cjs"),o=require("./cli/matrixOptions.cjs"),s=require("./cli/options.cjs");require("../adapters/config/harnessState.cjs");const c=require("../adapters/piExtensionAlias.cjs"),l=require("../adapters/piSettings.cjs"),u=require("../services/config/projectTrust.cjs"),d=require("../adapters/harnessContext.cjs"),f=require("../adapters/layerPackageInstaller.cjs"),p=require("../adapters/projectPiSettings.cjs"),m=require("../adapters/syncedRuntimeBuilder.cjs"),h=require("./syncPresenter.cjs");let g=require("node:path");g=e.__toESM(g,1);let _=require("node:fs");_=e.__toESM(_,1);let v=require("node:os");v=e.__toESM(v,1);let y=require("@agimon-ai/doompi-config/domains"),b=require("@agimon-ai/doompi-config/majorModes"),x=require("node:child_process"),S=require("@agimon-ai/doompi-ui/theme"),C=require("@agimon-ai/doompi-config/harnessStore");const w=`--check`,T=g.default.join(`tools`,`harness`,`emit-hooks.mjs`),E=`(none)`,D=[`DOOMPI_`],O=[`CLAUDE_PROJECT_DIR`,`CODEX_REPO_ROOT`,`ORIGINAL_REPO_PATH`,`MCP_UI_VIEWER`],k=new Set([`DOOMPI_CHILD_EXTENSIONS`,`DOOMPI_COMPOSED`,`DOOMPI_MUTE`,`DOOMPI_TEMP_DIR`,C.HARNESS_STATE_POINTER,`PI_SUBAGENT_PI_BINARY`]);function A(e){let t={};for(let[n,r]of Object.entries(e))r===void 0||k.has(n)||(O.includes(n)||D.some(e=>n.startsWith(e)))&&(t[n]=r);return t}function j(e,t){let{selection:n}=u.loadDoomConfig(e);return n?{...t,...n.majorMode&&!t.DOOMPI_MAJOR_MODE?{[o.DOOMPI_MAJOR_MODE_ENV]:n.majorMode}:{},...n.profile&&!t.DOOMPI_PROFILE?{[o.DOOMPI_PROFILE_ENV]:n.profile}:{},...n.domains&&t.DOOMPI_DOMAINS===void 0?{[o.DOOMPI_DOMAINS_ENV]:n.domains.join(`,`)}:{}}:t}function M(e){return{majorMode:e.majorMode,domains:e.domains,profile:e.profile,preset:e.preset}}function N(e,t){let n=(0,b.loadMajorModesConfig)(e),i=r.createLayerResolvers(e);return r.resolveExtensionComposition({agents:t.agents,autoStop:!1,mute:!1,preset:t.preset,personaEntry:i.packageEntry(r.PERSONA_ENTRY),majorMode:t.majorMode,layers:(0,b.filterHookDisabledLayers)(n,(0,b.resolveLayers)(n,t.majorMode),t.hooks),majorModesConfig:n,resolvers:i}).fingerprint}function P(e,t,n,o=process.env,s=`persisted`,u){if(!n)return[`no sync state: run doompi sync`];let d=[];i.syncStateRootMatches(e,n.root)||d.push(`sync state belongs to a different repository`);let f=n.selection;(f.majorMode!==t.majorMode||f.profile!==t.profile||f.preset!==t.preset||f.domains.join(`,`)!==t.domains.join(`,`))&&d.push(`selection changed since the last sync`),i.computeInputsHash(e,f,o.HOME??v.default.homedir())!==n.inputsHash&&d.push(`.doom configuration changed`),JSON.stringify(i.recordResolvedEntries((0,b.loadMajorModesConfig)(e),r.createLayerResolvers(e)))!==JSON.stringify(n.resolved)&&d.push(`resolved extension paths changed`),u&&n.compositionFingerprint!==u&&d.push(`extension composition changed`);try{a.readBootstrapStatus(e,void 0,o.HOME??v.default.homedir()).fresh||d.push(`precompiled runtime is missing or stale`)}catch{d.push(`precompiled runtime is missing or stale`)}let m=l.piAgentDirectory(o),h=g.default.join(l.piThemeDirectory(m),`${S.DEFAULT_THEME_NAME}.json`);if(s===`persisted`){let t=l.readPiSettings(m),n=l.mergePiSettings(t,m,{themePath:h,themeName:S.DEFAULT_THEME_NAME});l.serializePiSettings(n)!==l.serializePiSettings(t)&&d.push(`Pi user settings are out of date`),p.projectRegistersDoom(e)&&d.push(p.DUPLICATE_REGISTRATION_DRIFT)}c.piExtensionAliasIsCurrent(m)||d.push(`Pi user extension alias is out of date`);let y=`${JSON.stringify(S.DEFAULT_THEME,null,2)}\n`;return(!_.default.existsSync(h)||_.default.readFileSync(h,`utf8`)!==y)&&d.push(`Pi user theme is out of date`),(n.baseline.themePath!==h||n.baseline.themeName!==S.DEFAULT_THEME_NAME)&&d.push(`synced theme location is out of date`),d}function F(e,t){let n=g.default.join(e,T);if(!_.default.existsSync(n))return;let r=(0,x.spawnSync)(process.execPath,[n,`--write`],{cwd:e,encoding:`utf8`});if(r.status===0){t.write(`hooks: regenerated for Claude Code and Codex
2
+ `);return}t.write(`hooks: emit-hooks failed (${r.stderr?.trim()||`exit ${String(r.status)}`})\n`)}function I(e,t=`pi`){let{selection:n}=e;return[`mode: ${n.majorMode}`,`domains: ${n.domains.join(`, `)||E}`,`profile: ${n.profile??E}`,`skills: ${e.skillCount}`,`agents: ${e.agentCount}`,`mcp: ${e.mcpServers.join(`, `)||E}`,`state: ${e.statePath}`,...e.settingsPath?[`settings: ${e.settingsPath}`]:[],...e.projectSettingsPath?[`project: removed duplicate registration from ${e.projectSettingsPath}`]:[],``,`Run ${t} from the repository root to use it.`,``].join(`
3
+ `)}var L=class{name=`sync`;settingsMode;homeDirectory;constructor(e={}){this.settingsMode=e.settingsMode??`persisted`,this.homeDirectory=e.homeDirectory}matches(e){return e[0]===this.name}async execute(e,a=process.env,o=process.cwd(),c=process.stdout){let l=e.includes(w),u=e.slice(1).filter(e=>e!==w),d=a.DOOMPI_ROOT,p=d?g.default.resolve(d):t.findRepositoryRoot(o),m=(0,b.loadMajorModesConfig)(p).defaultMajorMode,_=(0,y.loadDomains)(p).defaultDomains,x=s.parseHarnessArgs(u,j(p,a),o,m,_),S=M(x.options),C=this.homeDirectory??a.HOME??v.default.homedir();if(l){let e=(0,b.loadMajorModesConfig)(p),t=f.missingLayerPackageSpecifiers(e,Object.keys(e.layers),r.createLayerResolvers(p));if(t.length>0)return c.write(`doompi sync is out of date:\n${t.map(e=>` configured package is not installed: ${e}`).join(`
4
+ `)}\n`),1;let n;try{n=i.readLocatedSyncState(p,C)}catch(e){let t=e instanceof Error?e.message:String(e);return c.write(`doompi sync is out of date:\n ${t}\n`),1}let o=N(p,x.options),s=P(p,S,n?.state,a,this.settingsMode,o);return n?.layout===`legacy`&&s.unshift(`legacy repository-local sync state requires migration`),s.length===0?(c.write(`doompi sync is up to date
5
5
  `),0):(c.write(`doompi sync is out of date:\n${s.map(e=>` ${e}`).join(`
6
- `)}\n`),1)}let w=await n.acquireSyncLocationLock(n.resolveSyncLocation(p,S)),T;try{T=await this.stage(p,b.options,a,S)}finally{await w()}return P(p,c),c.write(F(T,this.settingsMode===`embedded`?`dpi`:`pi`)),0}async stage(e,t,a,o){let s=n.resolveSyncLocation(e,o),u=s.directory,_=new Set([`run`,`cache`,`dist`,`.sync.lock`]);for(let e of g.default.existsSync(u)?g.default.readdirSync(u):[])_.has(e)||g.default.rmSync(h.default.join(u,e),{recursive:!0,force:!0});let v=await d.buildHarnessContext({...t,repoRoot:e,homeDirectory:o,cwd:e,resourceDirectory:u});await f.ensureLayerPackages({repoRoot:e,config:v.majorModesConfig,layers:Object.keys(v.majorModesConfig.layers),environment:a});let y=j(t),b=r.createLayerResolvers(e),C=i.recordResolvedEntries(v.majorModesConfig,b),w=M(e,t),T=l.piAgentDirectory(a),E=l.piThemeDirectory(T);await g.default.promises.mkdir(E,{recursive:!0});let D=await(0,x.writeDefaultTheme)(E);h.default.resolve(v.defaultThemePath)!==h.default.resolve(D)&&await g.default.promises.rm(v.defaultThemePath,{force:!0});let O={version:13,root:e,identity:s.identity,inputsHash:i.computeInputsHash(e,y,o),compositionFingerprint:w,selection:y,env:k(v.environment),fileState:{profileEnvironment:(0,S.loadHarnessState)(v.environment).state.profileEnvironment,pluginHooks:v.resources.pluginHooks,mcpProjection:v.resources.mcpProjection},resolved:C,baseline:{mcpConfigPath:v.resources.mcpConfigPath,personaFile:v.environment.DOOMPI_PERSONA_FILE,themePath:D,themeName:x.DEFAULT_THEME_NAME}},A=await i.writeSyncState(e,O,o);await m.buildSyncedRuntime(e,a,o),c.writePiExtensionAlias(T);let N=this.settingsMode===`persisted`?l.writePiSettings(T,{themePath:O.baseline.themePath,themeName:O.baseline.themeName}):void 0,P=this.settingsMode===`persisted`?p.writeProjectPiSettings(e):void 0;return{statePath:A,...N?{settingsPath:N}:{},...P?{projectSettingsPath:P}:{},selection:y,mcpServers:O.baseline.mcpConfigPath?i.readMcpServerNames(O.baseline.mcpConfigPath):[],skillCount:v.resources.skillCount,agentCount:v.resources.agentCount}}};exports.SyncCommand=I,exports.collectDrift=N,exports.formatSyncResult=F,exports.recordedEnvironment=k,exports.selectionCompositionFingerprint=M,exports.selectionEnvironment=A,exports.toSelection=j;
6
+ `)}\n`),1)}let T=new h.SyncProgress(c),E=await n.acquireSyncLocationLock(n.resolveSyncLocation(p,C)),D;try{D=await this.stage(p,x.options,a,C,T)}finally{await E()}return F(p,c),c.write(I(D,this.settingsMode===`embedded`?`dpi`:`pi`)),0}async stage(e,t,a,o,s){let u=n.resolveSyncLocation(e,o),h=u.directory,v=new Set([`run`,`cache`,`dist`,`.sync.lock`]);for(let e of _.default.existsSync(h)?_.default.readdirSync(h):[])v.has(e)||_.default.rmSync(g.default.join(h,e),{recursive:!0,force:!0});let y=s.start(`sync`,`resolving the matrix and staging resources`),b=await d.buildHarnessContext({...t,repoRoot:e,homeDirectory:o,cwd:e,resourceDirectory:h});await f.ensureLayerPackages({repoRoot:e,config:b.majorModesConfig,layers:Object.keys(b.majorModesConfig.layers),environment:a}),y(`${String(b.resources.skillCount)} skills, ${String(b.resources.agentCount)} agents`);let x=M(t),w=r.createLayerResolvers(e),T=i.recordResolvedEntries(b.majorModesConfig,w),E=N(e,t),D=l.piAgentDirectory(a),O=l.piThemeDirectory(D);await _.default.promises.mkdir(O,{recursive:!0});let k=await(0,S.writeDefaultTheme)(O);g.default.resolve(b.defaultThemePath)!==g.default.resolve(k)&&await _.default.promises.rm(b.defaultThemePath,{force:!0});let j={version:13,root:e,identity:u.identity,inputsHash:i.computeInputsHash(e,x,o),compositionFingerprint:E,selection:x,env:A(b.environment),fileState:{profileEnvironment:(0,C.loadHarnessState)(b.environment).state.profileEnvironment,pluginHooks:b.resources.pluginHooks,mcpProjection:b.resources.mcpProjection},resolved:T,baseline:{mcpConfigPath:b.resources.mcpConfigPath,personaFile:b.environment.DOOMPI_PERSONA_FILE,themePath:k,themeName:S.DEFAULT_THEME_NAME}},P=await i.writeSyncState(e,j,o),F=s.start(`runtime`,`precompiling the mode bundles`),I=await m.buildSyncedRuntime(e,a,o);F(`${String(Object.keys(I.bundles).length)} mode bundles`),c.writePiExtensionAlias(D);let L=this.settingsMode===`persisted`?l.writePiSettings(D,{themePath:j.baseline.themePath,themeName:j.baseline.themeName}):void 0,R=this.settingsMode===`persisted`?p.writeProjectPiSettings(e):void 0;return{statePath:P,...L?{settingsPath:L}:{},...R?{projectSettingsPath:R}:{},selection:x,mcpServers:j.baseline.mcpConfigPath?i.readMcpServerNames(j.baseline.mcpConfigPath):[],skillCount:b.resources.skillCount,agentCount:b.resources.agentCount}}};exports.SyncCommand=L,exports.collectDrift=P,exports.formatSyncResult=I,exports.recordedEnvironment=A,exports.selectionCompositionFingerprint=N,exports.selectionEnvironment=j,exports.toSelection=M;
7
7
  //# sourceMappingURL=syncCommand.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"syncCommand.cjs","names":["path","HARNESS_STATE_POINTER","loadDoomConfig","DOOMPI_MAJOR_MODE_ENV","DOOMPI_PROFILE_ENV","DOOMPI_DOMAINS_ENV","loadMajorModesConfig","createLayerResolvers","resolveExtensionComposition","PERSONA_ENTRY","filterHookDisabledLayers","resolveLayers","syncStateRootMatches","computeInputsHash","os","recordResolvedEntries","readBootstrapStatus","piAgentDirectory","piThemeDirectory","DEFAULT_THEME_NAME","readPiSettings","mergePiSettings","serializePiSettings","projectRegistersDoom","DUPLICATE_REGISTRATION_DRIFT","piExtensionAliasIsCurrent","DEFAULT_THEME","fs","spawnSync","findRepositoryRoot","loadDomains","parseHarnessArgs","missingLayerPackageSpecifiers","readLocatedSyncState","acquireSyncLocationLock","resolveSyncLocation","buildHarnessContext","ensureLayerPackages","writeDefaultTheme","loadHarnessState","writeSyncState","buildSyncedRuntime","writePiSettings","writeProjectPiSettings","readMcpServerNames"],"sources":["../../../src/commands/syncCommand.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { filterHookDisabledLayers, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport { buildHarnessContext } from '../adapters/harnessContext.ts';\nimport { ensureLayerPackages, missingLayerPackageSpecifiers } from '../adapters/layerPackageInstaller.ts';\nimport { readBootstrapStatus } from '../adapters/bootstrapLocator.ts';\nimport { piExtensionAliasIsCurrent, writePiExtensionAlias } from '../adapters/piExtensionAlias.ts';\nimport {\n mergePiSettings,\n piAgentDirectory,\n piThemeDirectory,\n readPiSettings,\n serializePiSettings,\n writePiSettings,\n} from '../adapters/piSettings.ts';\nimport {\n DUPLICATE_REGISTRATION_DRIFT,\n projectRegistersDoom,\n writeProjectPiSettings,\n} from '../adapters/projectPiSettings.ts';\nimport { buildSyncedRuntime } from '../adapters/syncedRuntimeBuilder.ts';\nimport { acquireSyncLocationLock, resolveSyncLocation } from '../adapters/syncLocation.ts';\nimport {\n computeInputsHash,\n readLocatedSyncState,\n readMcpServerNames,\n recordResolvedEntries,\n SYNC_STATE_VERSION,\n type SyncSelection,\n type SyncState,\n syncStateRootMatches,\n writeSyncState,\n} from '../adapters/syncState.ts';\nimport { HARNESS_STATE_POINTER, loadHarnessState } from '../adapters/config/harnessState';\nimport { loadDomains } from '@agimon-ai/doompi-config/domains';\nimport { loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { DEFAULT_THEME, DEFAULT_THEME_NAME, writeDefaultTheme } from '@agimon-ai/doompi-ui/theme';\nimport { loadDoomConfig } from '../services/config/projectTrust';\nimport { createLayerResolvers, PERSONA_ENTRY, resolveExtensionComposition } from '../services/extensionAssembler.ts';\nimport type { HarnessOptions } from '../types/interfaces/harness';\nimport { findRepositoryRoot } from '../adapters/repository/repository';\nimport { DOOMPI_DOMAINS_ENV, DOOMPI_MAJOR_MODE_ENV, DOOMPI_PROFILE_ENV } from './cli/matrixOptions.ts';\nimport { parseHarnessArgs } from './cli/options.ts';\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';\nconst HARNESS_ROOT_ENV = 'DOOMPI_ROOT';\nconst PERSONA_FILE_ENV = 'DOOMPI_PERSONA_FILE';\nconst HOOK_EMITTER = path.join('tools', 'harness', 'emit-hooks.mjs');\nconst RUN_DIRECTORY = 'run';\nconst EXTENSION_CACHE_DIRECTORY = 'cache';\nconst MODE_DIST_DIRECTORY = 'dist';\nconst SYNC_LOCK_FILE = '.sync.lock';\nconst NONE = '(none)';\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 = Pick<NodeJS.WritableStream, 'write'>;\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}\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 * 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(repoRoot: string, environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const { selection } = loadDoomConfig(repoRoot);\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): string {\n const majorModesConfig = loadMajorModesConfig(repoRoot);\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/** 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 (!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(recordResolvedEntries(loadMajorModesConfig(repoRoot), createLayerResolvers(repoRoot))) !==\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 const agentDirectory = piAgentDirectory(environment);\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n if (settingsMode === 'persisted') {\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');\n }\n if (projectRegistersDoom(repoRoot)) drift.push(DUPLICATE_REGISTRATION_DRIFT);\n }\n if (!piExtensionAliasIsCurrent(agentDirectory)) drift.push('Pi user extension alias is out of date');\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');\n }\n if (state.baseline.themePath !== themePath || state.baseline.themeName !== DEFAULT_THEME_NAME) {\n drift.push('synced theme location is out of date');\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/** Resolves the matrix, stages it into home-scoped worktree storage, and points Pi at the result. */\nexport class SyncCommand {\n readonly name = SYNC_COMMAND;\n private readonly settingsMode: SyncSettingsMode;\n private readonly homeDirectory: string | undefined;\n\n constructor(options: SyncCommandOptions = {}) {\n this.settingsMode = options.settingsMode ?? 'persisted';\n this.homeDirectory = options.homeDirectory;\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 const check = args.includes(CHECK_OPTION);\n const rest = args.slice(1).filter((argument) => argument !== CHECK_OPTION);\n const inheritedRoot = environment[HARNESS_ROOT_ENV];\n const repoRoot = inheritedRoot ? path.resolve(inheritedRoot) : findRepositoryRoot(currentDirectory);\n const defaultMajorMode = loadMajorModesConfig(repoRoot).defaultMajorMode;\n const defaultDomains = loadDomains(repoRoot).defaultDomains;\n const parsed = parseHarnessArgs(\n rest,\n selectionEnvironment(repoRoot, environment),\n currentDirectory,\n defaultMajorMode,\n defaultDomains,\n );\n const selection = toSelection(parsed.options);\n const homeDirectory = this.homeDirectory ?? environment.HOME ?? os.homedir();\n\n if (check) {\n const majorModesConfig = loadMajorModesConfig(repoRoot);\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);\n const drift = collectDrift(\n repoRoot,\n selection,\n located?.state,\n environment,\n this.settingsMode,\n expectedCompositionFingerprint,\n );\n if (located?.layout === 'legacy') drift.unshift('legacy repository-local sync state requires migration');\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 const releaseLock = await acquireSyncLocationLock(resolveSyncLocation(repoRoot, homeDirectory));\n let result: SyncResult;\n try {\n result = await this.stage(repoRoot, parsed.options, environment, homeDirectory);\n } finally {\n await releaseLock();\n }\n emitFrontendHooks(repoRoot, output);\n output.write(formatSyncResult(result, this.settingsMode === 'embedded' ? 'dpi' : 'pi'));\n return 0;\n }\n\n private async stage(\n repoRoot: string,\n options: Omit<HarnessOptions, 'repoRoot'>,\n environment: NodeJS.ProcessEnv,\n homeDirectory: string,\n ): Promise<SyncResult> {\n const location = resolveSyncLocation(repoRoot, homeDirectory);\n const directory = location.directory;\n // A full regeneration, so nothing a previous selection staged survives. Keep\n // per-session run directories because another Pi session may be using one,\n // and retain content-addressed cache and dist artifacts across selections.\n const preserved = new Set([RUN_DIRECTORY, EXTENSION_CACHE_DIRECTORY, MODE_DIST_DIRECTORY, SYNC_LOCK_FILE]);\n for (const entry of fs.existsSync(directory) ? fs.readdirSync(directory) : []) {\n if (!preserved.has(entry)) fs.rmSync(path.join(directory, entry), { recursive: true, force: true });\n }\n\n const context = await buildHarnessContext({\n ...options,\n repoRoot,\n homeDirectory,\n // Sync describes the repository, not the directory it was invoked from.\n cwd: repoRoot,\n resourceDirectory: directory,\n });\n await ensureLayerPackages({\n repoRoot,\n config: context.majorModesConfig,\n layers: Object.keys(context.majorModesConfig.layers),\n environment,\n });\n const selection = toSelection(options);\n const resolvers = createLayerResolvers(repoRoot);\n const resolved = recordResolvedEntries(context.majorModesConfig, resolvers);\n const compositionFingerprint = selectionCompositionFingerprint(repoRoot, options);\n const agentDirectory = piAgentDirectory(environment);\n const themeDirectory = piThemeDirectory(agentDirectory);\n await fs.promises.mkdir(themeDirectory, { recursive: true });\n const themePath = await writeDefaultTheme(themeDirectory);\n if (path.resolve(context.defaultThemePath) !== path.resolve(themePath)) {\n await fs.promises.rm(context.defaultThemePath, { force: true });\n }\n const state: SyncState = {\n version: SYNC_STATE_VERSION,\n root: repoRoot,\n identity: location.identity,\n inputsHash: computeInputsHash(repoRoot, selection, homeDirectory),\n compositionFingerprint,\n selection,\n env: recordedEnvironment(context.environment),\n // Read back from the session file the context just wrote, which is the\n // authority for fields deliberately omitted from the child 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 const statePath = await writeSyncState(repoRoot, state, homeDirectory);\n await buildSyncedRuntime(repoRoot, environment, homeDirectory);\n writePiExtensionAlias(agentDirectory);\n const settingsPath =\n this.settingsMode === 'persisted'\n ? writePiSettings(agentDirectory, {\n themePath: state.baseline.themePath,\n themeName: state.baseline.themeName,\n })\n : undefined;\n // After the user scope is current, so the repository is only ever stripped\n // of a registration the user scope already provides.\n const projectSettingsPath = this.settingsMode === 'persisted' ? writeProjectPiSettings(repoRoot) : undefined;\n\n return {\n statePath,\n ...(settingsPath ? { settingsPath } : {}),\n ...(projectSettingsPath ? { projectSettingsPath } : {}),\n selection,\n mcpServers: state.baseline.mcpConfigPath ? readMcpServerNames(state.baseline.mcpConfigPath) : [],\n skillCount: context.resources.skillCount,\n agentCount: context.resources.agentCount,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"syncCommand.cjs","names":["path","HARNESS_STATE_POINTER","loadDoomConfig","DOOMPI_MAJOR_MODE_ENV","DOOMPI_PROFILE_ENV","DOOMPI_DOMAINS_ENV","loadMajorModesConfig","createLayerResolvers","resolveExtensionComposition","PERSONA_ENTRY","filterHookDisabledLayers","resolveLayers","syncStateRootMatches","computeInputsHash","os","recordResolvedEntries","readBootstrapStatus","piAgentDirectory","piThemeDirectory","DEFAULT_THEME_NAME","readPiSettings","mergePiSettings","serializePiSettings","projectRegistersDoom","DUPLICATE_REGISTRATION_DRIFT","piExtensionAliasIsCurrent","DEFAULT_THEME","fs","spawnSync","findRepositoryRoot","loadDomains","parseHarnessArgs","missingLayerPackageSpecifiers","readLocatedSyncState","SyncProgress","acquireSyncLocationLock","resolveSyncLocation","buildHarnessContext","ensureLayerPackages","writeDefaultTheme","loadHarnessState","writeSyncState","buildSyncedRuntime","writePiSettings","writeProjectPiSettings","readMcpServerNames"],"sources":["../../../src/commands/syncCommand.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { filterHookDisabledLayers, resolveLayers } from '@agimon-ai/doompi-config/majorModes';\nimport { buildHarnessContext } from '../adapters/harnessContext.ts';\nimport { ensureLayerPackages, missingLayerPackageSpecifiers } from '../adapters/layerPackageInstaller.ts';\nimport { readBootstrapStatus } from '../adapters/bootstrapLocator.ts';\nimport { piExtensionAliasIsCurrent, writePiExtensionAlias } from '../adapters/piExtensionAlias.ts';\nimport {\n mergePiSettings,\n piAgentDirectory,\n piThemeDirectory,\n readPiSettings,\n serializePiSettings,\n writePiSettings,\n} from '../adapters/piSettings.ts';\nimport {\n DUPLICATE_REGISTRATION_DRIFT,\n projectRegistersDoom,\n writeProjectPiSettings,\n} from '../adapters/projectPiSettings.ts';\nimport { buildSyncedRuntime } from '../adapters/syncedRuntimeBuilder.ts';\nimport { acquireSyncLocationLock, resolveSyncLocation } from '../adapters/syncLocation.ts';\nimport {\n computeInputsHash,\n readLocatedSyncState,\n readMcpServerNames,\n recordResolvedEntries,\n SYNC_STATE_VERSION,\n type SyncSelection,\n type SyncState,\n syncStateRootMatches,\n writeSyncState,\n} from '../adapters/syncState.ts';\nimport { HARNESS_STATE_POINTER, loadHarnessState } from '../adapters/config/harnessState';\nimport { loadDomains } from '@agimon-ai/doompi-config/domains';\nimport { loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { DEFAULT_THEME, DEFAULT_THEME_NAME, writeDefaultTheme } from '@agimon-ai/doompi-ui/theme';\nimport { loadDoomConfig } from '../services/config/projectTrust';\nimport { createLayerResolvers, PERSONA_ENTRY, resolveExtensionComposition } from '../services/extensionAssembler.ts';\nimport type { HarnessOptions } from '../types/interfaces/harness';\nimport { findRepositoryRoot } from '../adapters/repository/repository';\nimport { DOOMPI_DOMAINS_ENV, DOOMPI_MAJOR_MODE_ENV, DOOMPI_PROFILE_ENV } from './cli/matrixOptions.ts';\nimport { parseHarnessArgs } from './cli/options.ts';\nimport { SyncProgress, type SyncProgressOutput } from './syncPresenter.ts';\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';\nconst HARNESS_ROOT_ENV = 'DOOMPI_ROOT';\nconst PERSONA_FILE_ENV = 'DOOMPI_PERSONA_FILE';\nconst HOOK_EMITTER = path.join('tools', 'harness', 'emit-hooks.mjs');\nconst RUN_DIRECTORY = 'run';\nconst EXTENSION_CACHE_DIRECTORY = 'cache';\nconst MODE_DIST_DIRECTORY = 'dist';\nconst SYNC_LOCK_FILE = '.sync.lock';\nconst NONE = '(none)';\nconst SYNC_LABEL = 'sync';\nconst RUNTIME_LABEL = 'runtime';\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}\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 * 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(repoRoot: string, environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const { selection } = loadDoomConfig(repoRoot);\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): string {\n const majorModesConfig = loadMajorModesConfig(repoRoot);\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/** 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 (!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(recordResolvedEntries(loadMajorModesConfig(repoRoot), createLayerResolvers(repoRoot))) !==\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 const agentDirectory = piAgentDirectory(environment);\n const themePath = path.join(piThemeDirectory(agentDirectory), `${DEFAULT_THEME_NAME}.json`);\n if (settingsMode === 'persisted') {\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');\n }\n if (projectRegistersDoom(repoRoot)) drift.push(DUPLICATE_REGISTRATION_DRIFT);\n }\n if (!piExtensionAliasIsCurrent(agentDirectory)) drift.push('Pi user extension alias is out of date');\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');\n }\n if (state.baseline.themePath !== themePath || state.baseline.themeName !== DEFAULT_THEME_NAME) {\n drift.push('synced theme location is out of date');\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/** Resolves the matrix, stages it into home-scoped worktree storage, and points Pi at the result. */\nexport class SyncCommand {\n readonly name = SYNC_COMMAND;\n private readonly settingsMode: SyncSettingsMode;\n private readonly homeDirectory: string | undefined;\n\n constructor(options: SyncCommandOptions = {}) {\n this.settingsMode = options.settingsMode ?? 'persisted';\n this.homeDirectory = options.homeDirectory;\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 const check = args.includes(CHECK_OPTION);\n const rest = args.slice(1).filter((argument) => argument !== CHECK_OPTION);\n const inheritedRoot = environment[HARNESS_ROOT_ENV];\n const repoRoot = inheritedRoot ? path.resolve(inheritedRoot) : findRepositoryRoot(currentDirectory);\n const defaultMajorMode = loadMajorModesConfig(repoRoot).defaultMajorMode;\n const defaultDomains = loadDomains(repoRoot).defaultDomains;\n const parsed = parseHarnessArgs(\n rest,\n selectionEnvironment(repoRoot, environment),\n currentDirectory,\n defaultMajorMode,\n defaultDomains,\n );\n const selection = toSelection(parsed.options);\n const homeDirectory = this.homeDirectory ?? environment.HOME ?? os.homedir();\n\n if (check) {\n const majorModesConfig = loadMajorModesConfig(repoRoot);\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);\n const drift = collectDrift(\n repoRoot,\n selection,\n located?.state,\n environment,\n this.settingsMode,\n expectedCompositionFingerprint,\n );\n if (located?.layout === 'legacy') drift.unshift('legacy repository-local sync state requires migration');\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 const progress = new SyncProgress(output);\n const releaseLock = await acquireSyncLocationLock(resolveSyncLocation(repoRoot, homeDirectory));\n let result: SyncResult;\n try {\n result = await this.stage(repoRoot, parsed.options, environment, homeDirectory, progress);\n } finally {\n await releaseLock();\n }\n emitFrontendHooks(repoRoot, output);\n output.write(formatSyncResult(result, this.settingsMode === 'embedded' ? 'dpi' : 'pi'));\n return 0;\n }\n\n private async stage(\n repoRoot: string,\n options: Omit<HarnessOptions, 'repoRoot'>,\n environment: NodeJS.ProcessEnv,\n homeDirectory: string,\n progress: SyncProgress,\n ): Promise<SyncResult> {\n const location = resolveSyncLocation(repoRoot, homeDirectory);\n const directory = location.directory;\n // A full regeneration, so nothing a previous selection staged survives. Keep\n // per-session run directories because another Pi session may be using one,\n // and retain content-addressed cache and dist artifacts across selections.\n const preserved = new Set([RUN_DIRECTORY, EXTENSION_CACHE_DIRECTORY, MODE_DIST_DIRECTORY, SYNC_LOCK_FILE]);\n for (const entry of fs.existsSync(directory) ? fs.readdirSync(directory) : []) {\n if (!preserved.has(entry)) fs.rmSync(path.join(directory, entry), { recursive: true, force: true });\n }\n\n const staged = progress.start(SYNC_LABEL, 'resolving the matrix and staging resources');\n const context = await buildHarnessContext({\n ...options,\n repoRoot,\n homeDirectory,\n // Sync describes the repository, not the directory it was invoked from.\n cwd: repoRoot,\n resourceDirectory: directory,\n });\n await ensureLayerPackages({\n repoRoot,\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(repoRoot);\n const resolved = recordResolvedEntries(context.majorModesConfig, resolvers);\n const compositionFingerprint = selectionCompositionFingerprint(repoRoot, options);\n const agentDirectory = piAgentDirectory(environment);\n const themeDirectory = piThemeDirectory(agentDirectory);\n await fs.promises.mkdir(themeDirectory, { recursive: true });\n const themePath = await writeDefaultTheme(themeDirectory);\n if (path.resolve(context.defaultThemePath) !== path.resolve(themePath)) {\n await fs.promises.rm(context.defaultThemePath, { force: true });\n }\n const state: SyncState = {\n version: SYNC_STATE_VERSION,\n root: repoRoot,\n identity: location.identity,\n inputsHash: computeInputsHash(repoRoot, selection, homeDirectory),\n compositionFingerprint,\n selection,\n env: recordedEnvironment(context.environment),\n // Read back from the session file the context just wrote, which is the\n // authority for fields deliberately omitted from the child 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 const statePath = await writeSyncState(repoRoot, state, homeDirectory);\n const precompiled = progress.start(RUNTIME_LABEL, 'precompiling the mode bundles');\n const synced = await buildSyncedRuntime(repoRoot, environment, homeDirectory);\n precompiled(`${String(Object.keys(synced.bundles).length)} mode bundles`);\n writePiExtensionAlias(agentDirectory);\n const settingsPath =\n this.settingsMode === 'persisted'\n ? writePiSettings(agentDirectory, {\n themePath: state.baseline.themePath,\n themeName: state.baseline.themeName,\n })\n : undefined;\n // After the user scope is current, so the repository is only ever stripped\n // of a registration the user scope already provides.\n const projectSettingsPath = this.settingsMode === 'persisted' ? writeProjectPiSettings(repoRoot) : undefined;\n\n return {\n statePath,\n ...(settingsPath ? { settingsPath } : {}),\n ...(projectSettingsPath ? { projectSettingsPath } : {}),\n selection,\n mcpServers: state.baseline.mcpConfigPath ? readMcpServerNames(state.baseline.mcpConfigPath) : [],\n skillCount: context.resources.skillCount,\n agentCount: context.resources.agentCount,\n };\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import { HarnessOptions } from "../types/interfaces/harness.cjs";
2
2
  import { SyncSelection, SyncState } from "../adapters/syncState.cjs";
3
+ import { SyncProgressOutput } from "./syncPresenter.cjs";
3
4
  //#region src/commands/syncCommand.d.ts
4
- type SyncOutput = Pick<NodeJS.WritableStream, 'write'>;
5
+ type SyncOutput = SyncProgressOutput;
5
6
  type SyncSettingsMode = 'persisted' | 'embedded';
6
7
  interface SyncCommandOptions {
7
8
  settingsMode?: SyncSettingsMode;
@@ -1 +1 @@
1
- {"version":3,"file":"syncCommand.d.cts","names":[],"sources":["../../../src/commands/syncCommand.ts"],"mappings":";;;KA6FK,aAAa,KAAK,OAAO;KAElB;UAEK;EACf,eAAe;;EAEf;;UAGe;EACf;;EAEA;;EAEA;EACA,WAAW;EACX;EACA;EACA;;iBAGc,oBAAoB,aAAa,OAAO,aAAa;;;;;;;;;iBAmBrD,qBAAqB,kBAAkB,aAAa,OAAO,aAAa,OAAO;iBAe/E,YACd,SAAS,KAAK,kEACb;iBASa,gCACd,kBACA,SAAS,KAAK;;iBAsBA,aACd,kBACA,WAAW,eACX,OAAO,uBACP,cAAa,OAAO,YACpB,eAAc,kBACd;iBAuEc,iBAAiB,QAAQ,YAAY;;cAqBxC;WACF;mBACQ;mBACA;EAEjB,YAAY,UAAS;EAKrB,QAAQ;EAIF,QACJ,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,aACP;UAsEW"}
1
+ {"version":3,"file":"syncCommand.d.cts","names":[],"sources":["../../../src/commands/syncCommand.ts"],"mappings":";;;;KAgGK,aAAa;KAEN;UAEK;EACf,eAAe;;EAEf;;UAGe;EACf;;EAEA;;EAEA;EACA,WAAW;EACX;EACA;EACA;;iBAGc,oBAAoB,aAAa,OAAO,aAAa;;;;;;;;;iBAmBrD,qBAAqB,kBAAkB,aAAa,OAAO,aAAa,OAAO;iBAe/E,YACd,SAAS,KAAK,kEACb;iBASa,gCACd,kBACA,SAAS,KAAK;;iBAsBA,aACd,kBACA,WAAW,eACX,OAAO,uBACP,cAAa,OAAO,YACpB,eAAc,kBACd;iBAuEc,iBAAiB,QAAQ,YAAY;;cAqBxC;WACF;mBACQ;mBACA;EAEjB,YAAY,UAAS;EAKrB,QAAQ;EAIF,QACJ,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,aACP;UAuEW"}
@@ -1,7 +1,8 @@
1
1
  import { HarnessOptions } from "../types/interfaces/harness.mjs";
2
2
  import { SyncSelection, SyncState } from "../adapters/syncState.mjs";
3
+ import { SyncProgressOutput } from "./syncPresenter.mjs";
3
4
  //#region src/commands/syncCommand.d.ts
4
- type SyncOutput = Pick<NodeJS.WritableStream, 'write'>;
5
+ type SyncOutput = SyncProgressOutput;
5
6
  type SyncSettingsMode = 'persisted' | 'embedded';
6
7
  interface SyncCommandOptions {
7
8
  settingsMode?: SyncSettingsMode;
@@ -1 +1 @@
1
- {"version":3,"file":"syncCommand.d.mts","names":[],"sources":["../../../src/commands/syncCommand.ts"],"mappings":";;;KA6FK,aAAa,KAAK,OAAO;KAElB;UAEK;EACf,eAAe;;EAEf;;UAGe;EACf;;EAEA;;EAEA;EACA,WAAW;EACX;EACA;EACA;;iBAGc,oBAAoB,aAAa,OAAO,aAAa;;;;;;;;;iBAmBrD,qBAAqB,kBAAkB,aAAa,OAAO,aAAa,OAAO;iBAe/E,YACd,SAAS,KAAK,kEACb;iBASa,gCACd,kBACA,SAAS,KAAK;;iBAsBA,aACd,kBACA,WAAW,eACX,OAAO,uBACP,cAAa,OAAO,YACpB,eAAc,kBACd;iBAuEc,iBAAiB,QAAQ,YAAY;;cAqBxC;WACF;mBACQ;mBACA;EAEjB,YAAY,UAAS;EAKrB,QAAQ;EAIF,QACJ,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,aACP;UAsEW"}
1
+ {"version":3,"file":"syncCommand.d.mts","names":[],"sources":["../../../src/commands/syncCommand.ts"],"mappings":";;;;KAgGK,aAAa;KAEN;UAEK;EACf,eAAe;;EAEf;;UAGe;EACf;;EAEA;;EAEA;EACA,WAAW;EACX;EACA;EACA;;iBAGc,oBAAoB,aAAa,OAAO,aAAa;;;;;;;;;iBAmBrD,qBAAqB,kBAAkB,aAAa,OAAO,aAAa,OAAO;iBAe/E,YACd,SAAS,KAAK,kEACb;iBASa,gCACd,kBACA,SAAS,KAAK;;iBAsBA,aACd,kBACA,WAAW,eACX,OAAO,uBACP,cAAa,OAAO,YACpB,eAAc,kBACd;iBAuEc,iBAAiB,QAAQ,YAAY;;cAqBxC;WACF;mBACQ;mBACA;EAEjB,YAAY,UAAS;EAKrB,QAAQ;EAIF,QACJ,gBACA,cAAa,OAAO,YACpB,2BACA,SAAQ,aACP;UAuEW"}