@agimon-ai/doompi 0.0.1-alpha.4 → 0.0.1-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/commands/buildCommand.cjs +2 -2
- package/dist/commands/buildCommand.cjs.map +1 -1
- package/dist/commands/buildCommand.mjs +2 -2
- package/dist/commands/compatibilityCommand.cjs +1 -1
- package/dist/commands/compatibilityCommand.cjs.map +1 -1
- package/dist/commands/compatibilityCommand.mjs +1 -1
- package/dist/commands/emitMcpCommand.cjs +1 -1
- package/dist/commands/emitMcpCommand.mjs +1 -1
- package/dist/commands/explainCommand.cjs +3 -3
- package/dist/commands/explainCommand.mjs +3 -3
- package/dist/commands/launchCommand.cjs +1 -1
- package/dist/commands/launchCommand.mjs +1 -1
- package/dist/commands/syncCommand.cjs +4 -4
- package/dist/commands/syncCommand.cjs.map +1 -1
- package/dist/commands/syncCommand.d.cts.map +1 -1
- package/dist/commands/syncCommand.d.mts.map +1 -1
- package/dist/commands/syncCommand.mjs +5 -5
- package/dist/commands/syncCommand.mjs.map +1 -1
- package/dist/config/index.cjs +1 -1
- package/dist/config/index.d.mts +2 -2
- package/dist/config/index.mjs +1 -1
- package/dist/entries/components/skillsOverlay.cjs +2 -2
- package/dist/entries/components/skillsOverlay.mjs +2 -2
- package/dist/entries/persona.cjs +1 -1
- package/dist/entries/persona.cjs.map +1 -1
- package/dist/entries/persona.mjs +1 -1
- package/dist/entries/repositoryHooks.cjs +1 -1
- package/dist/entries/repositoryHooks.mjs +1 -1
- package/dist/entries/styleSystem.cjs +2 -2
- package/dist/entries/styleSystem.mjs +2 -2
- package/dist/index.cjs +2 -1
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +7 -1
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +7 -1
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -0
- package/dist/services/compatibility/antigravity.cjs +1 -1
- package/dist/services/compatibility/antigravity.mjs +1 -1
- package/dist/services/compatibility/codex.cjs +1 -1
- package/dist/services/compatibility/codex.mjs +1 -1
- package/dist/services/compatibility/process.cjs +1 -1
- package/dist/services/compatibility/process.mjs +1 -1
- package/dist/services/compatibilityContext.cjs +1 -1
- package/dist/services/compatibilityContext.mjs +1 -1
- package/dist/services/composer.cjs +1 -1
- package/dist/services/composer.mjs +1 -1
- package/dist/services/extensionCompiler.cjs +2 -2
- package/dist/services/extensionCompiler.cjs.map +1 -1
- package/dist/services/extensionCompiler.mjs +2 -2
- package/dist/services/extensionCompiler.mjs.map +1 -1
- package/dist/services/harnessContext.cjs +1 -1
- package/dist/services/harnessContext.mjs +1 -1
- package/dist/services/index.cjs +1 -1
- package/dist/services/index.mjs +1 -1
- package/dist/services/matrixSwitcher.cjs +1 -1
- package/dist/services/matrixSwitcher.d.mts +1 -1
- package/dist/services/matrixSwitcher.mjs +1 -1
- package/dist/services/mcpFilter.cjs +2 -2
- package/dist/services/mcpFilter.mjs +2 -2
- package/dist/services/piExtensionAlias.cjs +2 -0
- package/dist/services/piExtensionAlias.cjs.map +1 -0
- package/dist/services/piExtensionAlias.mjs +2 -0
- package/dist/services/piExtensionAlias.mjs.map +1 -0
- package/dist/services/piSettings.cjs +1 -1
- package/dist/services/piSettings.cjs.map +1 -1
- package/dist/services/piSettings.d.cts +3 -3
- package/dist/services/piSettings.d.cts.map +1 -1
- package/dist/services/piSettings.d.mts +3 -3
- package/dist/services/piSettings.d.mts.map +1 -1
- package/dist/services/piSettings.mjs +1 -1
- package/dist/services/piSettings.mjs.map +1 -1
- package/dist/services/resourceCollector.cjs +2 -2
- package/dist/services/resourceCollector.mjs +2 -2
- package/dist/services/skillCatalog.cjs +1 -1
- package/dist/services/skillCatalog.mjs +1 -1
- package/dist/services/skillSources.cjs +1 -1
- package/dist/services/skillSources.mjs +1 -1
- package/dist/services/syncState.cjs +1 -1
- package/dist/services/syncState.cjs.map +1 -1
- package/dist/services/syncState.d.cts +1 -1
- package/dist/services/syncState.d.mts +1 -1
- package/dist/services/syncState.mjs +1 -1
- package/dist/services/syncState.mjs.map +1 -1
- package/dist/utils/json.cjs +1 -1
- package/dist/utils/json.mjs +1 -1
- package/dist/utils/moduleResolution.cjs +1 -1
- package/dist/utils/moduleResolution.cjs.map +1 -1
- package/dist/utils/moduleResolution.mjs +1 -1
- package/dist/utils/repository.cjs +1 -1
- package/dist/utils/repository.mjs +1 -1
- package/package.json +21 -16
package/README.md
CHANGED
|
@@ -445,8 +445,11 @@ Under the launcher, everything is resolved per run and nothing is written back i
|
|
|
445
445
|
repository. Editing a YAML file is the entire change.
|
|
446
446
|
|
|
447
447
|
`doompi sync` trades that for a pinned setup: the same resolution runs once and lands in
|
|
448
|
-
`.pi/doom/`,
|
|
449
|
-
|
|
448
|
+
`.pi/doom/`, while `.pi/settings.json` keeps the stable `@agimon-ai/doompi` extension name.
|
|
449
|
+
An internal `.pi/@agimon-ai/doompi` package alias lets Pi resolve that stable name; the
|
|
450
|
+
package entry then locates and dynamically loads the generated bootstrap from synced state.
|
|
451
|
+
Edit a YAML file and the next session says the config changed, the way doom emacs asks you
|
|
452
|
+
to re-run `doom sync`.
|
|
450
453
|
|
|
451
454
|
`doompi init` seeds `~/.pi/.doom` with all five file names, but only `config.yaml` from
|
|
452
455
|
there is read at runtime. Major modes, domains, profiles, and hooks are always read from the
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../
|
|
2
|
-
`)}var u=class{name=`build`;telemetry;constructor(e){this.telemetry=e}matches(e){return e[0]===this.name}async execute(e,u=process.env,d=process.cwd(),f=process.stdout){let p=u.DOOMPI_ROOT,m=p?o.default.resolve(p):
|
|
1
|
+
const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../utils/repository.cjs`);require(`../utils/index.cjs`),require(`../config/index.cjs`);const n=require(`../services/harnessContext.cjs`),r=require(`../cli/options.cjs`),i=require(`../services/runtimeBundle.cjs`),a=require(`./syncCommand.cjs`);let o=require(`node:path`);o=e.__toESM(o,1);let s=require(`@agimon-ai/doompi-config/majorModes`),c=require(`@agimon-ai/doompi-config/domains`);function l(e){return[`bundle: ${e.bundle}`,`manifest: ${e.manifest}`,`extensions: ${e.extensionCount} -> 1`,`skills: ${e.skillCount}`,`agents: ${e.agentCount}`,``,`Doom Pi is built. The next launch will use the dist mode extension.`,``].join(`
|
|
2
|
+
`)}var u=class{name=`build`;telemetry;constructor(e){this.telemetry=e}matches(e){return e[0]===this.name}async execute(e,u=process.env,d=process.cwd(),f=process.stdout){let p=u.DOOMPI_ROOT,m=p?o.default.resolve(p):t.findRepositoryRoot(d),h=await n.buildHarnessContext({...r.parseHarnessArgs(e.slice(1),a.selectionEnvironment(m,u),d,(0,s.loadMajorModesConfig)(m).defaultMajorMode,(0,c.loadDomains)(m).defaultDomains).options,repoRoot:m},this.telemetry);try{let e=await i.buildRuntimeBundle(h);return f.write(l({bundle:e.bundle,manifest:e.manifest,extensionCount:e.extensions.length,skillCount:h.resources.skillCount,agentCount:h.resources.agentCount})),0}finally{await h.cleanup()}}};exports.BuildCommand=u,exports.formatBuildResult=l;
|
|
3
3
|
//# sourceMappingURL=buildCommand.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildCommand.cjs","names":["path","findRepositoryRoot","buildHarnessContext","parseHarnessArgs","selectionEnvironment","buildRuntimeBundle"],"sources":["../../src/commands/buildCommand.ts"],"sourcesContent":["import path from 'node:path';\nimport { parseHarnessArgs } from '../cli/options.ts';\nimport { loadDomains, loadMajorModesConfig } from '../config/index.ts';\nimport type { HarnessTelemetry } from '../logSinkTelemetry.ts';\nimport { buildHarnessContext } from '../services/harnessContext.ts';\nimport { buildRuntimeBundle } from '../services/runtimeBundle.ts';\nimport { findRepositoryRoot } from '../utils/index.ts';\nimport { selectionEnvironment } from './syncCommand.ts';\n\nconst BUILD_COMMAND = 'build';\nconst HARNESS_ROOT_ENV = 'DOOMPI_ROOT';\n\ntype BuildOutput = Pick<NodeJS.WritableStream, 'write'>;\n\nexport interface BuildResult {\n bundle: string;\n manifest: string;\n extensionCount: number;\n skillCount: number;\n agentCount: number;\n}\n\nexport function formatBuildResult(result: BuildResult): string {\n return [\n `bundle: ${result.bundle}`,\n `manifest: ${result.manifest}`,\n `extensions: ${result.extensionCount} -> 1`,\n `skills: ${result.skillCount}`,\n `agents: ${result.agentCount}`,\n '',\n 'Doom Pi is built. The next launch will use the dist mode extension.',\n '',\n ].join('\\n');\n}\n\n/** Builds the selected mode extension without launching Pi or changing settings. */\nexport class BuildCommand {\n readonly name = BUILD_COMMAND;\n private readonly telemetry: HarnessTelemetry | undefined;\n\n constructor(telemetry?: HarnessTelemetry) {\n this.telemetry = telemetry;\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: BuildOutput = process.stdout,\n ): Promise<number> {\n const inheritedRoot = environment[HARNESS_ROOT_ENV];\n const repoRoot = inheritedRoot ? path.resolve(inheritedRoot) : findRepositoryRoot(currentDirectory);\n const parsed = parseHarnessArgs(\n args.slice(1),\n selectionEnvironment(repoRoot, environment),\n currentDirectory,\n loadMajorModesConfig(repoRoot).defaultMajorMode,\n loadDomains(repoRoot).defaultDomains,\n );\n const context = await buildHarnessContext({ ...parsed.options, repoRoot }, this.telemetry);\n try {\n const built = await buildRuntimeBundle(context);\n output.write(\n formatBuildResult({\n bundle: built.bundle,\n manifest: built.manifest,\n extensionCount: built.extensions.length,\n skillCount: context.resources.skillCount,\n agentCount: context.resources.agentCount,\n }),\n );\n return 0;\n } finally {\n await context.cleanup();\n }\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"buildCommand.cjs","names":["path","findRepositoryRoot","buildHarnessContext","parseHarnessArgs","selectionEnvironment","buildRuntimeBundle"],"sources":["../../src/commands/buildCommand.ts"],"sourcesContent":["import path from 'node:path';\nimport { parseHarnessArgs } from '../cli/options.ts';\nimport { loadDomains, loadMajorModesConfig } from '../config/index.ts';\nimport type { HarnessTelemetry } from '../logSinkTelemetry.ts';\nimport { buildHarnessContext } from '../services/harnessContext.ts';\nimport { buildRuntimeBundle } from '../services/runtimeBundle.ts';\nimport { findRepositoryRoot } from '../utils/index.ts';\nimport { selectionEnvironment } from './syncCommand.ts';\n\nconst BUILD_COMMAND = 'build';\nconst HARNESS_ROOT_ENV = 'DOOMPI_ROOT';\n\ntype BuildOutput = Pick<NodeJS.WritableStream, 'write'>;\n\nexport interface BuildResult {\n bundle: string;\n manifest: string;\n extensionCount: number;\n skillCount: number;\n agentCount: number;\n}\n\nexport function formatBuildResult(result: BuildResult): string {\n return [\n `bundle: ${result.bundle}`,\n `manifest: ${result.manifest}`,\n `extensions: ${result.extensionCount} -> 1`,\n `skills: ${result.skillCount}`,\n `agents: ${result.agentCount}`,\n '',\n 'Doom Pi is built. The next launch will use the dist mode extension.',\n '',\n ].join('\\n');\n}\n\n/** Builds the selected mode extension without launching Pi or changing settings. */\nexport class BuildCommand {\n readonly name = BUILD_COMMAND;\n private readonly telemetry: HarnessTelemetry | undefined;\n\n constructor(telemetry?: HarnessTelemetry) {\n this.telemetry = telemetry;\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: BuildOutput = process.stdout,\n ): Promise<number> {\n const inheritedRoot = environment[HARNESS_ROOT_ENV];\n const repoRoot = inheritedRoot ? path.resolve(inheritedRoot) : findRepositoryRoot(currentDirectory);\n const parsed = parseHarnessArgs(\n args.slice(1),\n selectionEnvironment(repoRoot, environment),\n currentDirectory,\n loadMajorModesConfig(repoRoot).defaultMajorMode,\n loadDomains(repoRoot).defaultDomains,\n );\n const context = await buildHarnessContext({ ...parsed.options, repoRoot }, this.telemetry);\n try {\n const built = await buildRuntimeBundle(context);\n output.write(\n formatBuildResult({\n bundle: built.bundle,\n manifest: built.manifest,\n extensionCount: built.extensions.length,\n skillCount: context.resources.skillCount,\n agentCount: context.resources.agentCount,\n }),\n );\n return 0;\n } finally {\n await context.cleanup();\n }\n }\n}\n"],"mappings":"kcAsBA,SAAgB,EAAkB,EAA6B,CAC7D,MAAO,CACL,eAAe,EAAO,SACtB,eAAe,EAAO,WACtB,eAAe,EAAO,eAAe,OACrC,eAAe,EAAO,aACtB,eAAe,EAAO,aACtB,GACA,sEACA,GACD,CAAC,KAAK;EAAK,CAId,IAAa,EAAb,KAA0B,CACxB,KAAgB,QAChB,UAEA,YAAY,EAA8B,CACxC,KAAK,UAAY,EAGnB,QAAQ,EAAyB,CAC/B,OAAO,EAAK,KAAO,KAAK,KAG1B,MAAM,QACJ,EACA,EAAiC,QAAQ,IACzC,EAAmB,QAAQ,KAAK,CAChC,EAAsB,QAAQ,OACb,CACjB,IAAM,EAAgB,EAAY,YAC5B,EAAW,EAAgBA,EAAAA,QAAK,QAAQ,EAAc,CAAGC,EAAAA,mBAAmB,EAAiB,CAQ7F,EAAU,MAAMC,EAAAA,oBAAoB,CAAE,GAP7BC,EAAAA,iBACb,EAAK,MAAM,EAAE,CACbC,EAAAA,qBAAqB,EAAU,EAAY,CAC3C,GAAA,EAAA,EAAA,sBACqB,EAAS,CAAC,kBAAA,EAAA,EAAA,aACnB,EAAS,CAAC,eAE6B,CAAC,QAAS,WAAU,CAAE,KAAK,UAAU,CAC1F,GAAI,CACF,IAAM,EAAQ,MAAMC,EAAAA,mBAAmB,EAAQ,CAU/C,OATA,EAAO,MACL,EAAkB,CAChB,OAAQ,EAAM,OACd,SAAU,EAAM,SAChB,eAAgB,EAAM,WAAW,OACjC,WAAY,EAAQ,UAAU,WAC9B,WAAY,EAAQ,UAAU,WAC/B,CAAC,CACH,CACM,SACC,CACR,MAAM,EAAQ,SAAS"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`)}var l=class{name=`build`;telemetry;constructor(e){this.telemetry=e}matches(e){return e[0]===this.name}async execute(l,u=process.env,d=process.cwd(),f=process.stdout){let p=u.DOOMPI_ROOT,m=p?s.resolve(p):
|
|
1
|
+
import{findRepositoryRoot as e}from"../utils/repository.mjs";import"../utils/index.mjs";import{loadDomains as t,loadMajorModesConfig as n}from"../config/index.mjs";import{buildHarnessContext as r}from"../services/harnessContext.mjs";import{parseHarnessArgs as i}from"../cli/options.mjs";import{buildRuntimeBundle as a}from"../services/runtimeBundle.mjs";import{selectionEnvironment as o}from"./syncCommand.mjs";import s from"node:path";function c(e){return[`bundle: ${e.bundle}`,`manifest: ${e.manifest}`,`extensions: ${e.extensionCount} -> 1`,`skills: ${e.skillCount}`,`agents: ${e.agentCount}`,``,`Doom Pi is built. The next launch will use the dist mode extension.`,``].join(`
|
|
2
|
+
`)}var l=class{name=`build`;telemetry;constructor(e){this.telemetry=e}matches(e){return e[0]===this.name}async execute(l,u=process.env,d=process.cwd(),f=process.stdout){let p=u.DOOMPI_ROOT,m=p?s.resolve(p):e(d),h=await r({...i(l.slice(1),o(m,u),d,n(m).defaultMajorMode,t(m).defaultDomains).options,repoRoot:m},this.telemetry);try{let e=await a(h);return f.write(c({bundle:e.bundle,manifest:e.manifest,extensionCount:e.extensions.length,skillCount:h.resources.skillCount,agentCount:h.resources.agentCount})),0}finally{await h.cleanup()}}};export{l as BuildCommand,c as formatBuildResult};
|
|
3
3
|
//# sourceMappingURL=buildCommand.mjs.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../logSinkTelemetry.cjs`),n=require(`../utils/repository.cjs`);require(`../utils/index.cjs`),require(`../config/index.cjs`);const r=require(`../cli/compatibilityOptions.cjs`),i=require(`../services/compatibilityContext.cjs`),a=require(`../services/compatibility/index.cjs`);require(`../services/index.cjs`);let o=require(`node:path`);o=e.__toESM(o,1);let s=require(`@agimon-ai/doompi-config/majorModes`),c=require(`@agimon-ai/doompi-config/domains`);var l=class{name=`compat`;telemetry;constructor(e=t.createHarnessTelemetry()){this.telemetry=e}matches(e){return e[0]===this.name}async execute(e,l=process.env,u=process.cwd()){let d=r.parseCompatibilityProvider(e[1]),f=l.DOOMPI_ROOT,p=f?o.default.resolve(f):n.findRepositoryRoot(u),m=r.parseCompatibilityArgs(e.slice(2),l,u,(0,s.loadMajorModesConfig)(p).defaultMajorMode,(0,c.loadDomains)(p).defaultDomains),h=Date.now();return this.telemetry.runInSpan(`doom_pi.compatibility`,{"harness.provider":d,"harness.mode":`compatibility`},async()=>{let e=await i.buildCompatibilityContext({repoRoot:p,provider:d,...m.options});try{let n=await a.launchCompatibility(e);return this.telemetry.recordEvent(t.HARNESS_EVENT.launchCompleted,{"harness.provider":d,"harness.exit_code":n,"harness.duration_ms":Date.now()-h}),n}catch(e){throw await this.telemetry.recordError(t.HARNESS_EVENT.compatibilityLaunchFailed,e,{"harness.provider":d,"harness.duration_ms":Date.now()-h}),e}finally{await e.cleanup()}})}};exports.CompatibilityCommand=l;
|
|
2
2
|
//# sourceMappingURL=compatibilityCommand.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compatibilityCommand.cjs","names":["createHarnessTelemetry","parseCompatibilityProvider","path","findRepositoryRoot","parseCompatibilityArgs","buildCompatibilityContext","launchCompatibility","HARNESS_EVENT"],"sources":["../../src/commands/compatibilityCommand.ts"],"sourcesContent":["import path from 'node:path';\nimport { parseCompatibilityArgs, parseCompatibilityProvider } from '../cli/compatibilityOptions.ts';\nimport { loadDomains, loadMajorModesConfig } from '../config/index.ts';\nimport { createHarnessTelemetry, HARNESS_EVENT, type HarnessTelemetry } from '../logSinkTelemetry.ts';\nimport { buildCompatibilityContext, launchCompatibility } from '../services/index.ts';\nimport { findRepositoryRoot } from '../utils/index.ts';\n\nconst COMPAT_COMMAND = 'compat';\nconst HARNESS_ROOT_ENV = 'DOOMPI_ROOT';\n\n/** Launches a non-Pi frontend with the doom-pi matrix resolved for that run. */\nexport class CompatibilityCommand {\n readonly name = COMPAT_COMMAND;\n private readonly telemetry: HarnessTelemetry;\n\n // Not a constructor parameter property: the launcher scripts run this file\n // straight from source under Node's strip-only TypeScript mode, which\n // rejects them.\n constructor(telemetry: HarnessTelemetry = createHarnessTelemetry()) {\n this.telemetry = telemetry;\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 ): Promise<number> {\n const provider = parseCompatibilityProvider(args[1]);\n const inheritedRoot = environment[HARNESS_ROOT_ENV];\n const repoRoot = inheritedRoot ? path.resolve(inheritedRoot) : findRepositoryRoot(currentDirectory);\n const parsed = parseCompatibilityArgs(\n args.slice(2),\n environment,\n currentDirectory,\n loadMajorModesConfig(repoRoot).defaultMajorMode,\n loadDomains(repoRoot).defaultDomains,\n );\n const startedAt = Date.now();\n\n // Compatibility launches shell out to a third-party CLI and to state-sync\n // scripts, so most of what can go wrong here is invisible to Pi entirely.\n return this.telemetry.runInSpan(\n 'doom_pi.compatibility',\n { 'harness.provider': provider, 'harness.mode': 'compatibility' },\n async () => {\n const context = await buildCompatibilityContext({ repoRoot, provider, ...parsed.options });\n try {\n const exitCode = await launchCompatibility(context);\n void this.telemetry.recordEvent(HARNESS_EVENT.launchCompleted, {\n 'harness.provider': provider,\n 'harness.exit_code': exitCode,\n 'harness.duration_ms': Date.now() - startedAt,\n });\n return exitCode;\n } catch (error) {\n await this.telemetry.recordError(HARNESS_EVENT.compatibilityLaunchFailed, error, {\n 'harness.provider': provider,\n 'harness.duration_ms': Date.now() - startedAt,\n });\n throw error;\n } finally {\n await context.cleanup();\n }\n },\n );\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"compatibilityCommand.cjs","names":["createHarnessTelemetry","parseCompatibilityProvider","path","findRepositoryRoot","parseCompatibilityArgs","buildCompatibilityContext","launchCompatibility","HARNESS_EVENT"],"sources":["../../src/commands/compatibilityCommand.ts"],"sourcesContent":["import path from 'node:path';\nimport { parseCompatibilityArgs, parseCompatibilityProvider } from '../cli/compatibilityOptions.ts';\nimport { loadDomains, loadMajorModesConfig } from '../config/index.ts';\nimport { createHarnessTelemetry, HARNESS_EVENT, type HarnessTelemetry } from '../logSinkTelemetry.ts';\nimport { buildCompatibilityContext, launchCompatibility } from '../services/index.ts';\nimport { findRepositoryRoot } from '../utils/index.ts';\n\nconst COMPAT_COMMAND = 'compat';\nconst HARNESS_ROOT_ENV = 'DOOMPI_ROOT';\n\n/** Launches a non-Pi frontend with the doom-pi matrix resolved for that run. */\nexport class CompatibilityCommand {\n readonly name = COMPAT_COMMAND;\n private readonly telemetry: HarnessTelemetry;\n\n // Not a constructor parameter property: the launcher scripts run this file\n // straight from source under Node's strip-only TypeScript mode, which\n // rejects them.\n constructor(telemetry: HarnessTelemetry = createHarnessTelemetry()) {\n this.telemetry = telemetry;\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 ): Promise<number> {\n const provider = parseCompatibilityProvider(args[1]);\n const inheritedRoot = environment[HARNESS_ROOT_ENV];\n const repoRoot = inheritedRoot ? path.resolve(inheritedRoot) : findRepositoryRoot(currentDirectory);\n const parsed = parseCompatibilityArgs(\n args.slice(2),\n environment,\n currentDirectory,\n loadMajorModesConfig(repoRoot).defaultMajorMode,\n loadDomains(repoRoot).defaultDomains,\n );\n const startedAt = Date.now();\n\n // Compatibility launches shell out to a third-party CLI and to state-sync\n // scripts, so most of what can go wrong here is invisible to Pi entirely.\n return this.telemetry.runInSpan(\n 'doom_pi.compatibility',\n { 'harness.provider': provider, 'harness.mode': 'compatibility' },\n async () => {\n const context = await buildCompatibilityContext({ repoRoot, provider, ...parsed.options });\n try {\n const exitCode = await launchCompatibility(context);\n void this.telemetry.recordEvent(HARNESS_EVENT.launchCompleted, {\n 'harness.provider': provider,\n 'harness.exit_code': exitCode,\n 'harness.duration_ms': Date.now() - startedAt,\n });\n return exitCode;\n } catch (error) {\n await this.telemetry.recordError(HARNESS_EVENT.compatibilityLaunchFailed, error, {\n 'harness.provider': provider,\n 'harness.duration_ms': Date.now() - startedAt,\n });\n throw error;\n } finally {\n await context.cleanup();\n }\n },\n );\n }\n}\n"],"mappings":"qkBAWA,IAAa,EAAb,KAAkC,CAChC,KAAgB,SAChB,UAKA,YAAY,EAA8BA,EAAAA,wBAAwB,CAAE,CAClE,KAAK,UAAY,EAGnB,QAAQ,EAAyB,CAC/B,OAAO,EAAK,KAAO,KAAK,KAG1B,MAAM,QACJ,EACA,EAAiC,QAAQ,IACzC,EAAmB,QAAQ,KAAK,CACf,CACjB,IAAM,EAAWC,EAAAA,2BAA2B,EAAK,GAAG,CAC9C,EAAgB,EAAY,YAC5B,EAAW,EAAgBC,EAAAA,QAAK,QAAQ,EAAc,CAAGC,EAAAA,mBAAmB,EAAiB,CAC7F,EAASC,EAAAA,uBACb,EAAK,MAAM,EAAE,CACb,EACA,GAAA,EAAA,EAAA,sBACqB,EAAS,CAAC,kBAAA,EAAA,EAAA,aACnB,EAAS,CAAC,eACvB,CACK,EAAY,KAAK,KAAK,CAI5B,OAAO,KAAK,UAAU,UACpB,wBACA,CAAE,mBAAoB,EAAU,eAAgB,gBAAiB,CACjE,SAAY,CACV,IAAM,EAAU,MAAMC,EAAAA,0BAA0B,CAAE,WAAU,WAAU,GAAG,EAAO,QAAS,CAAC,CAC1F,GAAI,CACF,IAAM,EAAW,MAAMC,EAAAA,oBAAoB,EAAQ,CAMnD,OALK,KAAK,UAAU,YAAYC,EAAAA,cAAc,gBAAiB,CAC7D,mBAAoB,EACpB,oBAAqB,EACrB,sBAAuB,KAAK,KAAK,CAAG,EACrC,CAAC,CACK,QACA,EAAO,CAKd,MAJA,MAAM,KAAK,UAAU,YAAYA,EAAAA,cAAc,0BAA2B,EAAO,CAC/E,mBAAoB,EACpB,sBAAuB,KAAK,KAAK,CAAG,EACrC,CAAC,CACI,SACE,CACR,MAAM,EAAQ,SAAS,GAG5B"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{HARNESS_EVENT as e,createHarnessTelemetry as t}from"../logSinkTelemetry.mjs";import{findRepositoryRoot as n}from"../utils/repository.mjs";import"../utils/index.mjs";import{loadDomains as r,loadMajorModesConfig as i}from"../config/index.mjs";import{parseCompatibilityArgs as a,parseCompatibilityProvider as o}from"../cli/compatibilityOptions.mjs";import{buildCompatibilityContext as s}from"../services/compatibilityContext.mjs";import{launchCompatibility as c}from"../services/compatibility/index.mjs";import"../services/index.mjs";import l from"node:path";var u=class{name=`compat`;telemetry;constructor(e=t()){this.telemetry=e}matches(e){return e[0]===this.name}async execute(t,u=process.env,d=process.cwd()){let f=o(t[1]),p=u.DOOMPI_ROOT,m=p?l.resolve(p):n(d),h=a(t.slice(2),u,d,i(m).defaultMajorMode,r(m).defaultDomains),g=Date.now();return this.telemetry.runInSpan(`doom_pi.compatibility`,{"harness.provider":f,"harness.mode":`compatibility`},async()=>{let t=await s({repoRoot:m,provider:f,...h.options});try{let n=await c(t);return this.telemetry.recordEvent(e.launchCompleted,{"harness.provider":f,"harness.exit_code":n,"harness.duration_ms":Date.now()-g}),n}catch(t){throw await this.telemetry.recordError(e.compatibilityLaunchFailed,t,{"harness.provider":f,"harness.duration_ms":Date.now()-g}),t}finally{await t.cleanup()}})}};export{u as CompatibilityCommand};
|
|
2
2
|
//# sourceMappingURL=compatibilityCommand.mjs.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../services/mcpFilter.cjs`),t=require(`./baseCommand.cjs`);require(`../services/index.cjs`);var n=class extends t.BaseCommand{name=`emit-mcp`;matches(e){return!!e.emitMcp}async execute(t){let{options:n,resources:r}=t;if(!n.emitMcp)throw Error(`--emit-mcp requires a target directory`);if(!r.mcpConfigPath)throw Error(`--emit-mcp requires MCP to be enabled`);let i=await e.persistMcpConfig(r.mcpConfigPath,r.temporaryDirectory,n.emitMcp);return process.stdout.write(`${i}\n`),0}};exports.EmitMcpCommand=n;
|
|
2
2
|
//# sourceMappingURL=emitMcpCommand.cjs.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{persistMcpConfig as e}from"../services/mcpFilter.mjs";import{BaseCommand as t}from"./baseCommand.mjs";import"../services/index.mjs";var n=class extends t{name=`emit-mcp`;matches(e){return!!e.emitMcp}async execute(t){let{options:n,resources:r}=t;if(!n.emitMcp)throw Error(`--emit-mcp requires a target directory`);if(!r.mcpConfigPath)throw Error(`--emit-mcp requires MCP to be enabled`);let i=await e(r.mcpConfigPath,r.temporaryDirectory,n.emitMcp);return process.stdout.write(`${i}\n`),0}};export{n as EmitMcpCommand};
|
|
2
2
|
//# sourceMappingURL=emitMcpCommand.mjs.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`./baseCommand.cjs`),n=require(`../services/skillCatalog.cjs`);let r=require(`node:
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`./baseCommand.cjs`),n=require(`../services/skillCatalog.cjs`);let r=require(`node:fs`);r=e.__toESM(r,1);let i=require(`@agimon-ai/doompi-config`),a=require(`node:path`);a=e.__toESM(a,1);function o(e){return JSON.parse(r.default.readFileSync(e,`utf8`)).mcpServers??{}}function s(e){let t=(e,t,n=`(none)`)=>`${e}:\n${t.length===0?` ${n}`:t.map(e=>` ${e}`).join(`
|
|
2
2
|
`)}\n`;return[`doompi resolved matrix
|
|
3
3
|
|
|
4
4
|
`,`major mode: ${e.majorMode}\n\n`,t(`profile`,e.profile?[e.profile]:[],`(none)`),`
|
|
@@ -6,8 +6,8 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
6
6
|
`,t(`domains (content)`,e.domains),`
|
|
7
7
|
`,t(`layers (behavior, from the major mode)`,e.layers,`core only`),`
|
|
8
8
|
`,t(`hook groups from layers`,e.hookGroups,e.hooksEnabled?`(core groups only)`:`(hooks disabled)`),`
|
|
9
|
-
`,t(`plugins`,e.plugins.map(e=>
|
|
10
|
-
`,t(`skill paths`,e.skillDirectories.map(e=>
|
|
9
|
+
`,t(`plugins`,e.plugins.map(e=>a.default.basename(e))),`
|
|
10
|
+
`,t(`skill paths`,e.skillDirectories.map(e=>a.default.relative(process.cwd(),e))),`
|
|
11
11
|
`,t(e.mcpFiltered?`mcp servers (filtered by domain allowlist)`:`mcp servers (unfiltered)`,e.mcpServers,`(mcp disabled)`),`
|
|
12
12
|
`,e.mcpProxyUpstreams?t(`mcp proxy upstreams (filtered)`,e.mcpProxyUpstreams):`mcp proxy upstreams:
|
|
13
13
|
(unfiltered, every upstream in mcp-config.yaml)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{BaseCommand as e}from"./baseCommand.mjs";import{buildSkillCatalog as t,counter as n}from"../services/skillCatalog.mjs";import r from"node:
|
|
1
|
+
import{BaseCommand as e}from"./baseCommand.mjs";import{buildSkillCatalog as t,counter as n}from"../services/skillCatalog.mjs";import r from"node:fs";import{buildPersonaPrompt as i}from"@agimon-ai/doompi-config";import a from"node:path";function o(e){return JSON.parse(r.readFileSync(e,`utf8`)).mcpServers??{}}function s(e){let t=(e,t,n=`(none)`)=>`${e}:\n${t.length===0?` ${n}`:t.map(e=>` ${e}`).join(`
|
|
2
2
|
`)}\n`;return[`doompi resolved matrix
|
|
3
3
|
|
|
4
4
|
`,`major mode: ${e.majorMode}\n\n`,t(`profile`,e.profile?[e.profile]:[],`(none)`),`
|
|
@@ -6,8 +6,8 @@ import{BaseCommand as e}from"./baseCommand.mjs";import{buildSkillCatalog as t,co
|
|
|
6
6
|
`,t(`domains (content)`,e.domains),`
|
|
7
7
|
`,t(`layers (behavior, from the major mode)`,e.layers,`core only`),`
|
|
8
8
|
`,t(`hook groups from layers`,e.hookGroups,e.hooksEnabled?`(core groups only)`:`(hooks disabled)`),`
|
|
9
|
-
`,t(`plugins`,e.plugins.map(e=>
|
|
10
|
-
`,t(`skill paths`,e.skillDirectories.map(e=>
|
|
9
|
+
`,t(`plugins`,e.plugins.map(e=>a.basename(e))),`
|
|
10
|
+
`,t(`skill paths`,e.skillDirectories.map(e=>a.relative(process.cwd(),e))),`
|
|
11
11
|
`,t(e.mcpFiltered?`mcp servers (filtered by domain allowlist)`:`mcp servers (unfiltered)`,e.mcpServers,`(mcp disabled)`),`
|
|
12
12
|
`,e.mcpProxyUpstreams?t(`mcp proxy upstreams (filtered)`,e.mcpProxyUpstreams):`mcp proxy upstreams:
|
|
13
13
|
(unfiltered, every upstream in mcp-config.yaml)
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../logSinkTelemetry.cjs`),n=require(`../utils/json.cjs`),r=require(`../utils/moduleResolution.cjs`);require(`../utils/index.cjs`);const i=require(`../config/projectTrust.cjs`);require(`../config/index.cjs`);const a=require(`./baseCommand.cjs`),o=require(`../services/compatibility/process.cjs`);let s=require(`cross-spawn`);s=e.__toESM(s,1);const c=`vibe-lint`,l=`pipe`;function u(e){let t;try{t=JSON.parse(e)}catch(e){throw Error(`Expected a JSON vibe-lint invocation on stdin`,{cause:e})}if(!n.isRecord(t)||typeof t.prompt!=`string`||typeof t.systemPrompt!=`string`)throw Error(`A vibe-lint invocation requires string prompt and systemPrompt fields`);if(t.maxTokens!==void 0&&typeof t.maxTokens!=`number`)throw Error(`A vibe-lint invocation maxTokens field must be a number`);if(t.screenshotPaths!==void 0&&(!Array.isArray(t.screenshotPaths)||!t.screenshotPaths.every(e=>typeof e==`string`)))throw Error(`A vibe-lint invocation screenshotPaths field must be an array of strings`);return{prompt:t.prompt,systemPrompt:t.systemPrompt,maxTokens:t.maxTokens,screenshotPaths:t.screenshotPaths}}function d(e,t){return[...e,`--print`,...i.hasProjectTrustOption(e)?[]:[`--approve`],`--no-session`,`--system-prompt`,t.systemPrompt,...(t.screenshotPaths??[]).map(e=>`@${e}`),t.prompt]}function f(e){return`${JSON.stringify({content:e.trim()})}\n`}function p(e,t){return[`--no-themes`,`--theme`,t,...e]}async function m(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf-8`)}var h=class extends a.BaseCommand{name=`launch`;matches(e){return!0}async execute(e,n){let{options:a,environment:h}=e,{buildRuntimeBundle:g,createRuntimeExtensionPlan:_}=await Promise.resolve().then(()=>require(`../services/runtimeBundle.cjs`)),{extensions:v,childExtensions:y}=_(e);h.DOOMPI_CHILD_EXTENSIONS=JSON.stringify(y);let b=v;try{b=[(await g(e,{extensions:v,childExtensions:y})).bundle],h.DOOMPI_EXTENSIONS_PROVIDED=`1`}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(`[doompi] extension bundle unavailable; using individual entries: ${t}\n`)}let x;try{x=i.loadDoomConfig(a.repoRoot)}catch(e){throw await n.recordError(t.HARNESS_EVENT.configLoadFailed,e,{"harness.phase":`load_config`}),e}n.recordEvent(t.HARNESS_EVENT.projectTrustResolved,{"harness.project_trust":x.projectTrust,"harness.project_trust.overridden":i.hasProjectTrustOption(a.piArgs)});let S=i.applyProjectTrust(a.piArgs,x);S=p(S,e.defaultThemePath);for(let e of b)S=[`--extension`,e,...S];if(a.outputFormat===c){let e=u(await m());S=d(S,e)}let C=Date.now(),w=(0,s.default)(process.execPath,[r.piCliPath(),...S],{cwd:a.cwd,env:h,stdio:a.outputFormat===c?[`ignore`,l,l]:`inherit`}),T=``;if(a.outputFormat===c){if(!w.stdout||!w.stderr)throw Error(`Failed to open Pi output streams`);w.stdout.on(`data`,e=>{T+=e.toString()}),w.stderr.on(`data`,e=>{process.stderr.write(e)})}let E=o.forwardSignals(w),D=await n.runInSpan(`doom_pi.pi_session`,{"harness.extension.count":b.length,"harness.mode":a.outputFormat},()=>o.waitForExit(w)).catch(async e=>{throw await n.recordError(t.HARNESS_EVENT.launchFailed,e,{"harness.duration_ms":Date.now()-C}),e}).finally(E);return n.recordEvent(t.HARNESS_EVENT.launchCompleted,{"harness.exit_code":D,"harness.duration_ms":Date.now()-C,"harness.mode":a.outputFormat??`default`}),a.outputFormat===c&&D===0&&process.stdout.write(f(T)),D}};exports.LaunchCommand=h,exports.buildVibeLintPiArgs=d,exports.formatVibeLintResponse=f,exports.overridePiThemes=p,exports.parseVibeLintInvocation=u;
|
|
2
2
|
//# sourceMappingURL=launchCommand.cjs.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{HARNESS_EVENT as e}from"../logSinkTelemetry.mjs";import{isRecord as t}from"../utils/json.mjs";import{piCliPath as n}from"../utils/moduleResolution.mjs";import"../utils/index.mjs";import{applyProjectTrust as r,hasProjectTrustOption as i,loadDoomConfig as a}from"../config/projectTrust.mjs";import"../config/index.mjs";import{BaseCommand as o}from"./baseCommand.mjs";import{forwardSignals as s,waitForExit as c}from"../services/compatibility/process.mjs";import l from"cross-spawn";const u=`vibe-lint`,d=`pipe`;function f(e){let n;try{n=JSON.parse(e)}catch(e){throw Error(`Expected a JSON vibe-lint invocation on stdin`,{cause:e})}if(!t(n)||typeof n.prompt!=`string`||typeof n.systemPrompt!=`string`)throw Error(`A vibe-lint invocation requires string prompt and systemPrompt fields`);if(n.maxTokens!==void 0&&typeof n.maxTokens!=`number`)throw Error(`A vibe-lint invocation maxTokens field must be a number`);if(n.screenshotPaths!==void 0&&(!Array.isArray(n.screenshotPaths)||!n.screenshotPaths.every(e=>typeof e==`string`)))throw Error(`A vibe-lint invocation screenshotPaths field must be an array of strings`);return{prompt:n.prompt,systemPrompt:n.systemPrompt,maxTokens:n.maxTokens,screenshotPaths:n.screenshotPaths}}function p(e,t){return[...e,`--print`,...i(e)?[]:[`--approve`],`--no-session`,`--system-prompt`,t.systemPrompt,...(t.screenshotPaths??[]).map(e=>`@${e}`),t.prompt]}function m(e){return`${JSON.stringify({content:e.trim()})}\n`}function h(e,t){return[`--no-themes`,`--theme`,t,...e]}async function g(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf-8`)}var _=class extends o{name=`launch`;matches(e){return!0}async execute(t,o){let{options:_,environment:v}=t,{buildRuntimeBundle:y,createRuntimeExtensionPlan:b}=await import(`../services/runtimeBundle.mjs`),{extensions:x,childExtensions:S}=b(t);v.DOOMPI_CHILD_EXTENSIONS=JSON.stringify(S);let C=x;try{C=[(await y(t,{extensions:x,childExtensions:S})).bundle],v.DOOMPI_EXTENSIONS_PROVIDED=`1`}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(`[doompi] extension bundle unavailable; using individual entries: ${t}\n`)}let w;try{w=a(_.repoRoot)}catch(t){throw await o.recordError(e.configLoadFailed,t,{"harness.phase":`load_config`}),t}o.recordEvent(e.projectTrustResolved,{"harness.project_trust":w.projectTrust,"harness.project_trust.overridden":i(_.piArgs)});let T=r(_.piArgs,w);T=h(T,t.defaultThemePath);for(let e of C)T=[`--extension`,e,...T];if(_.outputFormat===u){let e=f(await g());T=p(T,e)}let E=Date.now(),D=l(process.execPath,[n(),...T],{cwd:_.cwd,env:v,stdio:_.outputFormat===u?[`ignore`,d,d]:`inherit`}),O=``;if(_.outputFormat===u){if(!D.stdout||!D.stderr)throw Error(`Failed to open Pi output streams`);D.stdout.on(`data`,e=>{O+=e.toString()}),D.stderr.on(`data`,e=>{process.stderr.write(e)})}let k=s(D),A=await o.runInSpan(`doom_pi.pi_session`,{"harness.extension.count":C.length,"harness.mode":_.outputFormat},()=>c(D)).catch(async t=>{throw await o.recordError(e.launchFailed,t,{"harness.duration_ms":Date.now()-E}),t}).finally(k);return o.recordEvent(e.launchCompleted,{"harness.exit_code":A,"harness.duration_ms":Date.now()-E,"harness.mode":_.outputFormat??`default`}),_.outputFormat===u&&A===0&&process.stdout.write(m(O)),A}};export{_ as LaunchCommand,p as buildVibeLintPiArgs,m as formatVibeLintResponse,h as overridePiThemes,f as parseVibeLintInvocation};
|
|
2
2
|
//# sourceMappingURL=launchCommand.mjs.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../_virtual/_rolldown/runtime.cjs`),t=require(`../utils/moduleResolution.cjs`),n=require(`../utils/repository.cjs`);require(`../utils/index.cjs`);const r=require(`../services/extensionAssembler.cjs`),i=require(`../services/extensionCompiler.cjs`),a=require(`../config/projectTrust.cjs`);require(`../config/index.cjs`);const o=require(`../services/resourceCollector.cjs`),s=require(`../services/harnessContext.cjs`),c=require(`../services/syncState.cjs`),l=require(`../cli/matrixOptions.cjs`),u=require(`../cli/options.cjs`),d=require(`../services/runtimeBundle.cjs`),f=require(`../services/piSettings.cjs`),p=require(`../services/piExtensionAlias.cjs`);let m=require(`node:fs`);m=e.__toESM(m,1);let h=require(`node:path`);h=e.__toESM(h,1);let g=require(`node:child_process`),_=require(`@agimon-ai/doompi-config`),v=require(`@agimon-ai/doompi-config/majorModes`),y=require(`@agimon-ai/doompi-config/domains`),b=require(`@agimon-ai/doompi-ui/theme`);const x=`--check`,S=h.default.join(`tools`,`harness`,`emit-hooks.mjs`),C=`cache`,w=`dist`,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`,_.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}=a.loadDoomConfig(e);return n?{...t,...n.majorMode&&!t.DOOMPI_MAJOR_MODE?{[l.DOOMPI_MAJOR_MODE_ENV]:n.majorMode}:{},...n.profile&&!t.DOOMPI_PROFILE?{[l.DOOMPI_PROFILE_ENV]:n.profile}:{},...n.domains&&t.DOOMPI_DOMAINS===void 0?{[l.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,n){if(!n)return[`no sync state: run doompi sync`];let i=[],a=n.selection;(a.majorMode!==t.majorMode||a.profile!==t.profile||a.preset!==t.preset||a.domains.join(`,`)!==t.domains.join(`,`))&&i.push(`selection changed since the last sync`),c.computeInputsHash(e,a)!==n.inputsHash&&i.push(`.doom configuration changed`),JSON.stringify(c.recordResolvedEntries((0,v.loadMajorModesConfig)(e),r.createLayerResolvers(e)))!==JSON.stringify(n.resolved)&&i.push(`resolved extension paths changed`);let o=f.readPiSettings(e);return f.serializePiSettings(f.mergePiSettings(o,e,{themePath:n.baseline.themePath,themeName:n.baseline.themeName}))!==f.serializePiSettings(o)&&i.push(`.pi/settings.json is out of date`),p.piExtensionAliasIsCurrent(e)||i.push(`Pi extension package alias is out of date`),i}function N(e,t){let n=h.default.join(e,S);if(!m.default.existsSync(n))return;let r=(0,g.spawnSync)(process.execPath,[n,`--write`],{cwd:e,encoding:`utf8`});if(r.status===0){t.write(`hooks: regenerated for Claude Code and Codex
|
|
2
2
|
`);return}t.write(`hooks: emit-hooks failed (${r.stderr?.trim()||`exit ${String(r.status)}`})\n`)}function P(e,t){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}`,`settings: ${e.settingsPath}`,``,`Run pi from the repository root to use it.`,...t===`always`?[`Pi asks once before loading project extensions: answer yes, or run /trust in the session.`]:[],``].join(`
|
|
3
|
-
`)}async function F(e,t,n,
|
|
4
|
-
`),0):(
|
|
5
|
-
`)}\n`),1)}let
|
|
3
|
+
`)}async function F(e,t,n,a,s,l){let u={},f=(e,n)=>({majorMode:e,domains:[...t.options.domains],profile:t.options.profile,preset:t.options.preset,mute:n,autoStop:!1,agents:t.options.agents,mcp:t.options.mcp}),p=(e,i)=>{let s=c.createMapResolvers(n,i?void 0:a),l=o.selectWorkflowExtensionForProcess(s.optionalPackageEntry(r.CONTEXT_PACKAGES.workflow),s.optionalPackageEntry(r.CONTEXT_PACKAGES.dispatcherWorkflow),t.environment);return{base:{agents:t.options.agents,autoStop:!1,preset:t.options.preset,mcpEntry:t.options.mcp?s.optionalPackageEntry(r.CONTEXT_PACKAGES.mcp):void 0,personaEntry:s.ownEntry(r.PERSONA_ENTRY),workflowExtension:l,majorModesConfig:t.majorModesConfig,resolvers:s},layers:e}};for(let[n,a]of Object.entries(t.majorModesConfig.majorMode)){let o=r.filterHookDisabledLayers(a,t.options.hooks);for(let a of[!1,!0]){let c=p(o,!0),m=p(o,!1),h=[...r.assembleExtensions({...c.base,layers:c.layers,mute:a})].reverse(),g=[...r.assembleExtensions({...m.base,layers:m.layers,mute:a})].reverse(),_=r.assembleChildExtensions({...c.base,layers:c.layers,agents:!1,mute:!0}),v=i.extensionSetKey(g);try{u[v]=(await d.compileModeExtension({repoRoot:e,selection:f(n,a),extensionPaths:h,childExtensionPaths:_,skillPaths:t.skillPaths,pluginRoots:t.pluginRoots,outputName:`${n}${a?`.mute`:``}`,cacheDirectory:s,outputDirectory:l})).bundle}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(`[doompi] could not precompile ${n}${a?` (mute)`:``}: ${t}\n`)}}}return u}var I=class{name=`sync`;matches(e){return e[0]===this.name}async execute(e,t=process.env,r=process.cwd(),i=process.stdout){let o=e.includes(x),s=e.slice(1).filter(e=>e!==x),l=t.DOOMPI_ROOT,d=l?h.default.resolve(l):n.findRepositoryRoot(r),f=(0,v.loadMajorModesConfig)(d).defaultMajorMode,p=(0,y.loadDomains)(d).defaultDomains,m=u.parseHarnessArgs(s,A(d,t),r,f,p),g=j(m.options);if(o){let e=M(d,g,c.readSyncState(d));return e.length===0?(i.write(`doompi sync is up to date
|
|
4
|
+
`),0):(i.write(`doompi sync is out of date:\n${e.map(e=>` ${e}`).join(`
|
|
5
|
+
`)}\n`),1)}let _=await this.stage(d,m.options);return N(d,i),i.write(P(_,a.loadDoomConfig(d).projectTrust)),0}async stage(e,n){let a=c.syncDirectory(e),o=new Set([`run`,C,w]);for(let e of m.default.existsSync(a)?m.default.readdirSync(a):[])o.has(e)||m.default.rmSync(h.default.join(a,e),{recursive:!0,force:!0});let l=await s.buildHarnessContext({...n,repoRoot:e,cwd:e,resourceDirectory:a}),u=j(n),d=c.recordResolvedEntries(l.majorModesConfig,r.createLayerResolvers(e)),g=h.default.join(a,C),v=h.default.join(a,w),y=await i.compileResolvedEntries(d,g),x=await F(e,{options:n,majorModesConfig:l.majorModesConfig,environment:l.environment,skillPaths:l.resources.skillDirectories,pluginRoots:l.plugins.map(e=>e.directory)},d,y,g,v),S=await i.compileExtensionSet([t.ownEntry(`doom`)],g,{outputDirectory:v,outputName:`bootstrap`}),T={version:5,root:e,inputsHash:c.computeInputsHash(e,u),selection:u,env:k(l.environment),fileState:{profileEnvironment:(0,_.loadHarnessState)(l.environment).state.profileEnvironment,pluginHooks:l.resources.pluginHooks},resolved:d,compiled:y,bundles:x,bootstrap:S,baseline:{mcpConfigPath:l.resources.mcpConfigPath,personaFile:l.environment.DOOMPI_PERSONA_FILE,themePath:l.defaultThemePath,themeName:b.DEFAULT_THEME_NAME}},E=await c.writeSyncState(e,T);return p.writePiExtensionAlias(e),{statePath:E,settingsPath:f.writePiSettings(e,{themePath:T.baseline.themePath,themeName:T.baseline.themeName}),selection:u,mcpServers:T.baseline.mcpConfigPath?c.readMcpServerNames(T.baseline.mcpConfigPath):[],skillCount:l.resources.skillCount,agentCount:l.resources.agentCount}}};exports.SyncCommand=I,exports.collectDrift=M,exports.formatSyncResult=P,exports.recordedEnvironment=k,exports.selectionEnvironment=A,exports.toSelection=j;
|
|
6
6
|
//# 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","computeInputsHash","recordResolvedEntries","createLayerResolvers","readPiSettings","serializePiSettings","mergePiSettings","ownEntry","fs","createMapResolvers","selectWorkflowExtensionForProcess","CONTEXT_PACKAGES","PERSONA_ENTRY","filterHookDisabledLayers","assembleExtensions","assembleChildExtensions","extensionSetKey","compileModeExtension","findRepositoryRoot","parseHarnessArgs","readSyncState","syncDirectory","buildHarnessContext","compileResolvedEntries","compileExtensionSet","DEFAULT_THEME_NAME","writeSyncState","writePiSettings","readMcpServerNames"],"sources":["../../src/commands/syncCommand.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { DOOMPI_DOMAINS_ENV, DOOMPI_MAJOR_MODE_ENV, DOOMPI_PROFILE_ENV } from '../cli/matrixOptions.ts';\nimport { parseHarnessArgs } from '../cli/options.ts';\nimport {\n DEFAULT_THEME_NAME,\n HARNESS_STATE_POINTER,\n loadDomains,\n loadDoomConfig,\n loadHarnessState,\n loadMajorModesConfig,\n} from '../config/index.ts';\nimport type { HarnessOptions } from '../interfaces/index.ts';\nimport {\n assembleChildExtensions,\n assembleExtensions,\n CONTEXT_PACKAGES,\n createLayerResolvers,\n filterHookDisabledLayers,\n PERSONA_ENTRY,\n} from '../services/extensionAssembler.ts';\nimport { compileExtensionSet, compileResolvedEntries, extensionSetKey } from '../services/extensionCompiler.ts';\nimport { buildHarnessContext } from '../services/harnessContext.ts';\nimport { mergePiSettings, readPiSettings, serializePiSettings, writePiSettings } from '../services/piSettings.ts';\nimport { selectWorkflowExtensionForProcess } from '../services/resourceCollector.ts';\nimport { compileModeExtension, type ModeBuildSelection } from '../services/runtimeBundle.ts';\nimport {\n computeInputsHash,\n createMapResolvers,\n readMcpServerNames,\n readSyncState,\n recordResolvedEntries,\n SYNC_STATE_VERSION,\n type SyncSelection,\n type SyncState,\n syncDirectory,\n writeSyncState,\n} from '../services/syncState.ts';\nimport { findRepositoryRoot, ownEntry } from '../utils/index.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';\n/** Compiled extension bundles, keyed by content hash. */\nconst EXTENSION_CACHE_DIRECTORY = 'cache';\nconst MODE_DIST_DIRECTORY = 'dist';\nconst DOOM_ENTRY = 'doom';\nconst TRUST_ALWAYS = 'always';\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\ninterface BundleContext {\n options: Omit<HarnessOptions, 'repoRoot'>;\n majorModesConfig: Awaited<ReturnType<typeof buildHarnessContext>>['majorModesConfig'];\n environment: NodeJS.ProcessEnv;\n skillPaths: readonly string[];\n pluginRoots: readonly string[];\n}\n\nexport interface SyncResult {\n statePath: string;\n settingsPath: 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\n/** Differences between what a sync would produce and what is on disk. */\nexport function collectDrift(repoRoot: string, selection: SyncSelection, state: SyncState | undefined): string[] {\n if (!state) return ['no sync state: run doompi sync'];\n const drift: string[] = [];\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) !== state.inputsHash) drift.push('.doom configuration changed');\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 const settings = readPiSettings(repoRoot);\n const merged = mergePiSettings(settings, repoRoot, {\n extensionEntry: state.bootstrap ?? ownEntry(DOOM_ENTRY),\n themePath: state.baseline.themePath,\n themeName: state.baseline.themeName,\n });\n if (serializePiSettings(merged) !== serializePiSettings(settings)) drift.push('.pi/settings.json is out of date');\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, projectTrust: string): 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 `settings: ${result.settingsPath}`,\n '',\n 'Run pi from the repository root to use it.',\n // projectTrust only reaches Pi as a launcher flag, so a synced session has\n // to be told once that it will be asked.\n ...(projectTrust === TRUST_ALWAYS\n ? ['Pi asks once before loading project extensions: answer yes, or run /trust in the session.']\n : []),\n '',\n ].join('\\n');\n}\n\n/** Precompiles every mode Pi can switch to without restarting. */\nasync function compileModeBundles(\n repoRoot: string,\n context: BundleContext,\n resolved: Record<string, string>,\n compiled: Record<string, string>,\n cacheDirectory: string,\n outputDirectory: string,\n): Promise<Record<string, string>> {\n const bundles: Record<string, string> = {};\n const selectionFor = (majorMode: string, mute: boolean): ModeBuildSelection => ({\n majorMode,\n domains: [...context.options.domains],\n profile: context.options.profile,\n preset: context.options.preset,\n mute,\n autoStop: false,\n agents: context.options.agents,\n mcp: context.options.mcp,\n });\n const planFor = (layers: string[], source: boolean) => {\n const resolvers = createMapResolvers(resolved, source ? undefined : compiled);\n const workflowExtension = selectWorkflowExtensionForProcess(\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.workflow),\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.dispatcherWorkflow),\n context.environment,\n );\n return {\n base: {\n agents: context.options.agents,\n autoStop: false,\n preset: context.options.preset,\n mcpEntry: context.options.mcp ? resolvers.optionalPackageEntry(CONTEXT_PACKAGES.mcp) : undefined,\n personaEntry: resolvers.ownEntry(PERSONA_ENTRY),\n workflowExtension,\n majorModesConfig: context.majorModesConfig,\n resolvers,\n },\n layers,\n };\n };\n\n for (const [majorMode, layers] of Object.entries(context.majorModesConfig.majorMode)) {\n const selectedLayers = filterHookDisabledLayers(layers, context.options.hooks);\n for (const mute of [false, true]) {\n const source = planFor(selectedLayers, true);\n const runtime = planFor(selectedLayers, false);\n const sourceEntries = [...assembleExtensions({ ...source.base, layers: source.layers, mute })].reverse();\n const runtimeEntries = [...assembleExtensions({ ...runtime.base, layers: runtime.layers, mute })].reverse();\n const childExtensionPaths = assembleChildExtensions({\n ...source.base,\n layers: source.layers,\n agents: false,\n mute: true,\n });\n const key = extensionSetKey(runtimeEntries);\n try {\n const built = await compileModeExtension({\n repoRoot,\n selection: selectionFor(majorMode, mute),\n extensionPaths: sourceEntries,\n childExtensionPaths,\n skillPaths: context.skillPaths,\n pluginRoots: context.pluginRoots,\n outputName: `${majorMode}${mute ? '.mute' : ''}`,\n cacheDirectory,\n outputDirectory,\n });\n bundles[key] = built.bundle;\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[doompi] could not precompile ${majorMode}${mute ? ' (mute)' : ''}: ${detail}\\n`);\n }\n }\n }\n return bundles;\n}\n\n/** Resolves the matrix, stages it into `.pi/doom`, and points Pi at the result. */\nexport class SyncCommand {\n readonly name = SYNC_COMMAND;\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\n if (check) {\n const drift = collectDrift(repoRoot, selection, readSyncState(repoRoot));\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 result = await this.stage(repoRoot, parsed.options);\n emitFrontendHooks(repoRoot, output);\n output.write(formatSyncResult(result, loadDoomConfig(repoRoot).projectTrust));\n return 0;\n }\n\n private async stage(repoRoot: string, options: Omit<HarnessOptions, 'repoRoot'>): Promise<SyncResult> {\n const directory = syncDirectory(repoRoot);\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]);\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 // Sync describes the repository, not the directory it was invoked from.\n cwd: repoRoot,\n resourceDirectory: directory,\n });\n const selection = toSelection(options);\n // Pi would otherwise transpile every TypeScript extension through jiti on\n // each launch. Resolution already knows the whole list, so it is bundled to\n // plain ESM once here and imported natively for the rest of its life.\n const resolved = recordResolvedEntries(context.majorModesConfig, createLayerResolvers(repoRoot));\n const cacheDirectory = path.join(directory, EXTENSION_CACHE_DIRECTORY);\n const outputDirectory = path.join(directory, MODE_DIST_DIRECTORY);\n const compiled = await compileResolvedEntries(resolved, cacheDirectory);\n const bundles = await compileModeBundles(\n repoRoot,\n {\n options,\n majorModesConfig: context.majorModesConfig,\n environment: context.environment,\n skillPaths: context.resources.skillDirectories,\n pluginRoots: context.plugins.map((plugin) => plugin.directory),\n },\n resolved,\n compiled,\n cacheDirectory,\n outputDirectory,\n );\n // Pi loads this entry before it can inspect the synced state. Flattening\n // the bootstrap and composer graph removes their module-resolution fanout;\n // it then selects one of the mode bundles above without compiling at run\n // time. The content hash also lets settings replace an upgraded bootstrap\n // atomically on the next sync.\n const bootstrap = await compileExtensionSet([ownEntry(DOOM_ENTRY)], cacheDirectory, {\n outputDirectory,\n outputName: 'bootstrap',\n });\n const state: SyncState = {\n version: SYNC_STATE_VERSION,\n root: repoRoot,\n inputsHash: computeInputsHash(repoRoot, selection),\n selection,\n env: recordedEnvironment(context.environment),\n // Read back from the session file the context just wrote, which is the\n // only place these two live now.\n fileState: {\n profileEnvironment: loadHarnessState(context.environment).state.profileEnvironment,\n pluginHooks: context.resources.pluginHooks,\n },\n resolved,\n compiled,\n bundles,\n bootstrap,\n baseline: {\n mcpConfigPath: context.resources.mcpConfigPath,\n personaFile: context.environment[PERSONA_FILE_ENV],\n themePath: context.defaultThemePath,\n themeName: DEFAULT_THEME_NAME,\n },\n };\n\n return {\n statePath: await writeSyncState(repoRoot, state),\n settingsPath: writePiSettings(repoRoot, {\n extensionEntry: bootstrap,\n themePath: state.baseline.themePath,\n themeName: state.baseline.themeName,\n }),\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","computeInputsHash","recordResolvedEntries","createLayerResolvers","readPiSettings","serializePiSettings","mergePiSettings","piExtensionAliasIsCurrent","fs","createMapResolvers","selectWorkflowExtensionForProcess","CONTEXT_PACKAGES","PERSONA_ENTRY","filterHookDisabledLayers","assembleExtensions","assembleChildExtensions","extensionSetKey","compileModeExtension","findRepositoryRoot","parseHarnessArgs","readSyncState","syncDirectory","buildHarnessContext","compileResolvedEntries","compileExtensionSet","ownEntry","DEFAULT_THEME_NAME","writeSyncState","writePiSettings","readMcpServerNames"],"sources":["../../src/commands/syncCommand.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { DOOMPI_DOMAINS_ENV, DOOMPI_MAJOR_MODE_ENV, DOOMPI_PROFILE_ENV } from '../cli/matrixOptions.ts';\nimport { parseHarnessArgs } from '../cli/options.ts';\nimport {\n DEFAULT_THEME_NAME,\n HARNESS_STATE_POINTER,\n loadDomains,\n loadDoomConfig,\n loadHarnessState,\n loadMajorModesConfig,\n} from '../config/index.ts';\nimport type { HarnessOptions } from '../interfaces/index.ts';\nimport {\n assembleChildExtensions,\n assembleExtensions,\n CONTEXT_PACKAGES,\n createLayerResolvers,\n filterHookDisabledLayers,\n PERSONA_ENTRY,\n} from '../services/extensionAssembler.ts';\nimport { compileExtensionSet, compileResolvedEntries, extensionSetKey } from '../services/extensionCompiler.ts';\nimport { buildHarnessContext } from '../services/harnessContext.ts';\nimport { piExtensionAliasIsCurrent, writePiExtensionAlias } from '../services/piExtensionAlias.ts';\nimport { mergePiSettings, readPiSettings, serializePiSettings, writePiSettings } from '../services/piSettings.ts';\nimport { selectWorkflowExtensionForProcess } from '../services/resourceCollector.ts';\nimport { compileModeExtension, type ModeBuildSelection } from '../services/runtimeBundle.ts';\nimport {\n computeInputsHash,\n createMapResolvers,\n readMcpServerNames,\n readSyncState,\n recordResolvedEntries,\n SYNC_STATE_VERSION,\n type SyncSelection,\n type SyncState,\n syncDirectory,\n writeSyncState,\n} from '../services/syncState.ts';\nimport { findRepositoryRoot, ownEntry } from '../utils/index.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';\n/** Compiled extension bundles, keyed by content hash. */\nconst EXTENSION_CACHE_DIRECTORY = 'cache';\nconst MODE_DIST_DIRECTORY = 'dist';\nconst DOOM_ENTRY = 'doom';\nconst TRUST_ALWAYS = 'always';\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\ninterface BundleContext {\n options: Omit<HarnessOptions, 'repoRoot'>;\n majorModesConfig: Awaited<ReturnType<typeof buildHarnessContext>>['majorModesConfig'];\n environment: NodeJS.ProcessEnv;\n skillPaths: readonly string[];\n pluginRoots: readonly string[];\n}\n\nexport interface SyncResult {\n statePath: string;\n settingsPath: 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\n/** Differences between what a sync would produce and what is on disk. */\nexport function collectDrift(repoRoot: string, selection: SyncSelection, state: SyncState | undefined): string[] {\n if (!state) return ['no sync state: run doompi sync'];\n const drift: string[] = [];\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) !== state.inputsHash) drift.push('.doom configuration changed');\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 const settings = readPiSettings(repoRoot);\n const merged = mergePiSettings(settings, repoRoot, {\n themePath: state.baseline.themePath,\n themeName: state.baseline.themeName,\n });\n if (serializePiSettings(merged) !== serializePiSettings(settings)) drift.push('.pi/settings.json is out of date');\n if (!piExtensionAliasIsCurrent(repoRoot)) drift.push('Pi extension package alias is out of date');\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, projectTrust: string): 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 `settings: ${result.settingsPath}`,\n '',\n 'Run pi from the repository root to use it.',\n // projectTrust only reaches Pi as a launcher flag, so a synced session has\n // to be told once that it will be asked.\n ...(projectTrust === TRUST_ALWAYS\n ? ['Pi asks once before loading project extensions: answer yes, or run /trust in the session.']\n : []),\n '',\n ].join('\\n');\n}\n\n/** Precompiles every mode Pi can switch to without restarting. */\nasync function compileModeBundles(\n repoRoot: string,\n context: BundleContext,\n resolved: Record<string, string>,\n compiled: Record<string, string>,\n cacheDirectory: string,\n outputDirectory: string,\n): Promise<Record<string, string>> {\n const bundles: Record<string, string> = {};\n const selectionFor = (majorMode: string, mute: boolean): ModeBuildSelection => ({\n majorMode,\n domains: [...context.options.domains],\n profile: context.options.profile,\n preset: context.options.preset,\n mute,\n autoStop: false,\n agents: context.options.agents,\n mcp: context.options.mcp,\n });\n const planFor = (layers: string[], source: boolean) => {\n const resolvers = createMapResolvers(resolved, source ? undefined : compiled);\n const workflowExtension = selectWorkflowExtensionForProcess(\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.workflow),\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.dispatcherWorkflow),\n context.environment,\n );\n return {\n base: {\n agents: context.options.agents,\n autoStop: false,\n preset: context.options.preset,\n mcpEntry: context.options.mcp ? resolvers.optionalPackageEntry(CONTEXT_PACKAGES.mcp) : undefined,\n personaEntry: resolvers.ownEntry(PERSONA_ENTRY),\n workflowExtension,\n majorModesConfig: context.majorModesConfig,\n resolvers,\n },\n layers,\n };\n };\n\n for (const [majorMode, layers] of Object.entries(context.majorModesConfig.majorMode)) {\n const selectedLayers = filterHookDisabledLayers(layers, context.options.hooks);\n for (const mute of [false, true]) {\n const source = planFor(selectedLayers, true);\n const runtime = planFor(selectedLayers, false);\n const sourceEntries = [...assembleExtensions({ ...source.base, layers: source.layers, mute })].reverse();\n const runtimeEntries = [...assembleExtensions({ ...runtime.base, layers: runtime.layers, mute })].reverse();\n const childExtensionPaths = assembleChildExtensions({\n ...source.base,\n layers: source.layers,\n agents: false,\n mute: true,\n });\n const key = extensionSetKey(runtimeEntries);\n try {\n const built = await compileModeExtension({\n repoRoot,\n selection: selectionFor(majorMode, mute),\n extensionPaths: sourceEntries,\n childExtensionPaths,\n skillPaths: context.skillPaths,\n pluginRoots: context.pluginRoots,\n outputName: `${majorMode}${mute ? '.mute' : ''}`,\n cacheDirectory,\n outputDirectory,\n });\n bundles[key] = built.bundle;\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[doompi] could not precompile ${majorMode}${mute ? ' (mute)' : ''}: ${detail}\\n`);\n }\n }\n }\n return bundles;\n}\n\n/** Resolves the matrix, stages it into `.pi/doom`, and points Pi at the result. */\nexport class SyncCommand {\n readonly name = SYNC_COMMAND;\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\n if (check) {\n const drift = collectDrift(repoRoot, selection, readSyncState(repoRoot));\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 result = await this.stage(repoRoot, parsed.options);\n emitFrontendHooks(repoRoot, output);\n output.write(formatSyncResult(result, loadDoomConfig(repoRoot).projectTrust));\n return 0;\n }\n\n private async stage(repoRoot: string, options: Omit<HarnessOptions, 'repoRoot'>): Promise<SyncResult> {\n const directory = syncDirectory(repoRoot);\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]);\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 // Sync describes the repository, not the directory it was invoked from.\n cwd: repoRoot,\n resourceDirectory: directory,\n });\n const selection = toSelection(options);\n // Pi would otherwise transpile every TypeScript extension through jiti on\n // each launch. Resolution already knows the whole list, so it is bundled to\n // plain ESM once here and imported natively for the rest of its life.\n const resolved = recordResolvedEntries(context.majorModesConfig, createLayerResolvers(repoRoot));\n const cacheDirectory = path.join(directory, EXTENSION_CACHE_DIRECTORY);\n const outputDirectory = path.join(directory, MODE_DIST_DIRECTORY);\n const compiled = await compileResolvedEntries(resolved, cacheDirectory);\n const bundles = await compileModeBundles(\n repoRoot,\n {\n options,\n majorModesConfig: context.majorModesConfig,\n environment: context.environment,\n skillPaths: context.resources.skillDirectories,\n pluginRoots: context.plugins.map((plugin) => plugin.directory),\n },\n resolved,\n compiled,\n cacheDirectory,\n outputDirectory,\n );\n // The package entry loads this artifact after locating the synced state.\n // Flattening the bootstrap and composer graph removes their module-resolution\n // fanout; it then selects one of the mode bundles above without compiling at\n // run time. The content hash makes upgraded bootstraps coexist atomically.\n const bootstrap = await compileExtensionSet([ownEntry(DOOM_ENTRY)], cacheDirectory, {\n outputDirectory,\n outputName: 'bootstrap',\n });\n const state: SyncState = {\n version: SYNC_STATE_VERSION,\n root: repoRoot,\n inputsHash: computeInputsHash(repoRoot, selection),\n selection,\n env: recordedEnvironment(context.environment),\n // Read back from the session file the context just wrote, which is the\n // only place these two live now.\n fileState: {\n profileEnvironment: loadHarnessState(context.environment).state.profileEnvironment,\n pluginHooks: context.resources.pluginHooks,\n },\n resolved,\n compiled,\n bundles,\n bootstrap,\n baseline: {\n mcpConfigPath: context.resources.mcpConfigPath,\n personaFile: context.environment[PERSONA_FILE_ENV],\n themePath: context.defaultThemePath,\n themeName: DEFAULT_THEME_NAME,\n },\n };\n\n const statePath = await writeSyncState(repoRoot, state);\n writePiExtensionAlias(repoRoot);\n return {\n statePath,\n settingsPath: writePiSettings(repoRoot, {\n themePath: state.baseline.themePath,\n themeName: state.baseline.themeName,\n }),\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncCommand.d.cts","names":[],"sources":["../../src/commands/syncCommand.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"syncCommand.d.cts","names":[],"sources":["../../src/commands/syncCommand.ts"],"mappings":";;;;KA2FK,UAAA,GAAa,IAAA,CAAK,MAAA,CAAO,cAAA;AAAA,UAUb,UAAA;EACf,SAAA;EACA,YAAA;EACA,SAAA,EAAW,aAAA;EACX,UAAA;EACA,UAAA;EACA,UAAA;AAAA;AAAA,iBAGc,mBAAA,CAAoB,WAAA,EAAa,MAAA,CAAO,UAAA,GAAa,MAAA;;AATrE;;;;;;;iBA4BgB,oBAAA,CAAqB,QAAA,UAAkB,WAAA,EAAa,MAAA,CAAO,UAAA,GAAa,MAAA,CAAO,UAAA;AAAA,iBAe/E,WAAA,CACd,OAAA,EAAS,IAAA,CAAK,cAAA,oDACb,aAAA;;iBAUa,YAAA,CAAa,QAAA,UAAkB,SAAA,EAAW,aAAA,EAAe,KAAA,EAAO,SAAA;AAAA,iBA6ChE,gBAAA,CAAiB,MAAA,EAAQ,UAAA,EAAY,YAAA;;cAqGxC,WAAA;EAAA,SACF,IAAA;EAET,OAAA,CAAQ,IAAA;EAIF,OAAA,CACJ,IAAA,YACA,WAAA,GAAa,MAAA,CAAO,UAAA,EACpB,gBAAA,WACA,MAAA,GAAQ,UAAA,GACP,OAAA;EAAA,QAgCW,KAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncCommand.d.mts","names":[],"sources":["../../src/commands/syncCommand.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"syncCommand.d.mts","names":[],"sources":["../../src/commands/syncCommand.ts"],"mappings":";;;;KA2FK,UAAA,GAAa,IAAA,CAAK,MAAA,CAAO,cAAA;AAAA,UAUb,UAAA;EACf,SAAA;EACA,YAAA;EACA,SAAA,EAAW,aAAA;EACX,UAAA;EACA,UAAA;EACA,UAAA;AAAA;AAAA,iBAGc,mBAAA,CAAoB,WAAA,EAAa,MAAA,CAAO,UAAA,GAAa,MAAA;;AATrE;;;;;;;iBA4BgB,oBAAA,CAAqB,QAAA,UAAkB,WAAA,EAAa,MAAA,CAAO,UAAA,GAAa,MAAA,CAAO,UAAA;AAAA,iBAe/E,WAAA,CACd,OAAA,EAAS,IAAA,CAAK,cAAA,oDACb,aAAA;;iBAUa,YAAA,CAAa,QAAA,UAAkB,SAAA,EAAW,aAAA,EAAe,KAAA,EAAO,SAAA;AAAA,iBA6ChE,gBAAA,CAAiB,MAAA,EAAQ,UAAA,EAAY,YAAA;;cAqGxC,WAAA;EAAA,SACF,IAAA;EAET,OAAA,CAAQ,IAAA;EAIF,OAAA,CACJ,IAAA,YACA,WAAA,GAAa,MAAA,CAAO,UAAA,EACpB,gBAAA,WACA,MAAA,GAAQ,UAAA,GACP,OAAA;EAAA,QAgCW,KAAA;AAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{
|
|
2
|
-
`);return}t.write(`hooks: emit-hooks failed (${r.stderr?.trim()||`exit ${String(r.status)}`})\n`)}function
|
|
3
|
-
`)}async function
|
|
4
|
-
`),0):(
|
|
5
|
-
`)}\n`),1)}let
|
|
1
|
+
import{HARNESS_STATE_POINTER as e,loadHarnessState as t}from"../config/harnessState.mjs";import{ownEntry as n}from"../utils/moduleResolution.mjs";import{findRepositoryRoot as r}from"../utils/repository.mjs";import"../utils/index.mjs";import{CONTEXT_PACKAGES as i,PERSONA_ENTRY as a,assembleChildExtensions as o,assembleExtensions as s,createLayerResolvers as c,filterHookDisabledLayers as l}from"../services/extensionAssembler.mjs";import{compileExtensionSet as u,compileResolvedEntries as d,extensionSetKey as f}from"../services/extensionCompiler.mjs";import{loadDoomConfig as p}from"../config/projectTrust.mjs";import{DEFAULT_THEME_NAME as m,loadDomains as h,loadMajorModesConfig as g}from"../config/index.mjs";import{selectWorkflowExtensionForProcess as _}from"../services/resourceCollector.mjs";import{buildHarnessContext as v}from"../services/harnessContext.mjs";import{computeInputsHash as y,createMapResolvers as b,readMcpServerNames as x,readSyncState as S,recordResolvedEntries as C,syncDirectory as w,writeSyncState as T}from"../services/syncState.mjs";import{DOOMPI_DOMAINS_ENV as E,DOOMPI_MAJOR_MODE_ENV as D,DOOMPI_PROFILE_ENV as O}from"../cli/matrixOptions.mjs";import{parseHarnessArgs as k}from"../cli/options.mjs";import{compileModeExtension as A}from"../services/runtimeBundle.mjs";import{mergePiSettings as j,readPiSettings as M,serializePiSettings as N,writePiSettings as P}from"../services/piSettings.mjs";import{piExtensionAliasIsCurrent as F,writePiExtensionAlias as I}from"../services/piExtensionAlias.mjs";import L from"node:fs";import R from"node:path";import{spawnSync as z}from"node:child_process";const B=`--check`,V=R.join(`tools`,`harness`,`emit-hooks.mjs`),H=`cache`,U=`dist`,W=`(none)`,G=[`DOOMPI_`],K=[`CLAUDE_PROJECT_DIR`,`CODEX_REPO_ROOT`,`ORIGINAL_REPO_PATH`,`MCP_UI_VIEWER`],q=new Set([`DOOMPI_CHILD_EXTENSIONS`,`DOOMPI_COMPOSED`,`DOOMPI_MUTE`,`DOOMPI_TEMP_DIR`,e,`PI_SUBAGENT_PI_BINARY`]);function J(e){let t={};for(let[n,r]of Object.entries(e))r===void 0||q.has(n)||(K.includes(n)||G.some(e=>n.startsWith(e)))&&(t[n]=r);return t}function Y(e,t){let{selection:n}=p(e);return n?{...t,...n.majorMode&&!t.DOOMPI_MAJOR_MODE?{[D]:n.majorMode}:{},...n.profile&&!t.DOOMPI_PROFILE?{[O]:n.profile}:{},...n.domains&&t.DOOMPI_DOMAINS===void 0?{[E]:n.domains.join(`,`)}:{}}:t}function X(e){return{majorMode:e.majorMode,domains:e.domains,profile:e.profile,preset:e.preset}}function Z(e,t,n){if(!n)return[`no sync state: run doompi sync`];let r=[],i=n.selection;(i.majorMode!==t.majorMode||i.profile!==t.profile||i.preset!==t.preset||i.domains.join(`,`)!==t.domains.join(`,`))&&r.push(`selection changed since the last sync`),y(e,i)!==n.inputsHash&&r.push(`.doom configuration changed`),JSON.stringify(C(g(e),c(e)))!==JSON.stringify(n.resolved)&&r.push(`resolved extension paths changed`);let a=M(e);return N(j(a,e,{themePath:n.baseline.themePath,themeName:n.baseline.themeName}))!==N(a)&&r.push(`.pi/settings.json is out of date`),F(e)||r.push(`Pi extension package alias is out of date`),r}function Q(e,t){let n=R.join(e,V);if(!L.existsSync(n))return;let r=z(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 $(e,t){let{selection:n}=e;return[`mode: ${n.majorMode}`,`domains: ${n.domains.join(`, `)||W}`,`profile: ${n.profile??W}`,`skills: ${e.skillCount}`,`agents: ${e.agentCount}`,`mcp: ${e.mcpServers.join(`, `)||W}`,`state: ${e.statePath}`,`settings: ${e.settingsPath}`,``,`Run pi from the repository root to use it.`,...t===`always`?[`Pi asks once before loading project extensions: answer yes, or run /trust in the session.`]:[],``].join(`
|
|
3
|
+
`)}async function ee(e,t,n,r,c,u){let d={},p=(e,n)=>({majorMode:e,domains:[...t.options.domains],profile:t.options.profile,preset:t.options.preset,mute:n,autoStop:!1,agents:t.options.agents,mcp:t.options.mcp}),m=(e,o)=>{let s=b(n,o?void 0:r),c=_(s.optionalPackageEntry(i.workflow),s.optionalPackageEntry(i.dispatcherWorkflow),t.environment);return{base:{agents:t.options.agents,autoStop:!1,preset:t.options.preset,mcpEntry:t.options.mcp?s.optionalPackageEntry(i.mcp):void 0,personaEntry:s.ownEntry(a),workflowExtension:c,majorModesConfig:t.majorModesConfig,resolvers:s},layers:e}};for(let[n,r]of Object.entries(t.majorModesConfig.majorMode)){let i=l(r,t.options.hooks);for(let r of[!1,!0]){let a=m(i,!0),l=m(i,!1),h=[...s({...a.base,layers:a.layers,mute:r})].reverse(),g=[...s({...l.base,layers:l.layers,mute:r})].reverse(),_=o({...a.base,layers:a.layers,agents:!1,mute:!0}),v=f(g);try{d[v]=(await A({repoRoot:e,selection:p(n,r),extensionPaths:h,childExtensionPaths:_,skillPaths:t.skillPaths,pluginRoots:t.pluginRoots,outputName:`${n}${r?`.mute`:``}`,cacheDirectory:c,outputDirectory:u})).bundle}catch(e){let t=e instanceof Error?e.message:String(e);process.stderr.write(`[doompi] could not precompile ${n}${r?` (mute)`:``}: ${t}\n`)}}}return d}var te=class{name=`sync`;matches(e){return e[0]===this.name}async execute(e,t=process.env,n=process.cwd(),i=process.stdout){let a=e.includes(B),o=e.slice(1).filter(e=>e!==B),s=t.DOOMPI_ROOT,c=s?R.resolve(s):r(n),l=g(c).defaultMajorMode,u=h(c).defaultDomains,d=k(o,Y(c,t),n,l,u),f=X(d.options);if(a){let e=Z(c,f,S(c));return e.length===0?(i.write(`doompi sync is up to date
|
|
4
|
+
`),0):(i.write(`doompi sync is out of date:\n${e.map(e=>` ${e}`).join(`
|
|
5
|
+
`)}\n`),1)}let m=await this.stage(c,d.options);return Q(c,i),i.write($(m,p(c).projectTrust)),0}async stage(e,r){let i=w(e),a=new Set([`run`,H,U]);for(let e of L.existsSync(i)?L.readdirSync(i):[])a.has(e)||L.rmSync(R.join(i,e),{recursive:!0,force:!0});let o=await v({...r,repoRoot:e,cwd:e,resourceDirectory:i}),s=X(r),l=C(o.majorModesConfig,c(e)),f=R.join(i,H),p=R.join(i,U),h=await d(l,f),g=await ee(e,{options:r,majorModesConfig:o.majorModesConfig,environment:o.environment,skillPaths:o.resources.skillDirectories,pluginRoots:o.plugins.map(e=>e.directory)},l,h,f,p),_=await u([n(`doom`)],f,{outputDirectory:p,outputName:`bootstrap`}),b={version:5,root:e,inputsHash:y(e,s),selection:s,env:J(o.environment),fileState:{profileEnvironment:t(o.environment).state.profileEnvironment,pluginHooks:o.resources.pluginHooks},resolved:l,compiled:h,bundles:g,bootstrap:_,baseline:{mcpConfigPath:o.resources.mcpConfigPath,personaFile:o.environment.DOOMPI_PERSONA_FILE,themePath:o.defaultThemePath,themeName:m}},S=await T(e,b);return I(e),{statePath:S,settingsPath:P(e,{themePath:b.baseline.themePath,themeName:b.baseline.themeName}),selection:s,mcpServers:b.baseline.mcpConfigPath?x(b.baseline.mcpConfigPath):[],skillCount:o.resources.skillCount,agentCount:o.resources.agentCount}}};export{te as SyncCommand,Z as collectDrift,$ as formatSyncResult,J as recordedEnvironment,Y as selectionEnvironment,X as toSelection};
|
|
6
6
|
//# sourceMappingURL=syncCommand.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncCommand.mjs","names":[],"sources":["../../src/commands/syncCommand.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { DOOMPI_DOMAINS_ENV, DOOMPI_MAJOR_MODE_ENV, DOOMPI_PROFILE_ENV } from '../cli/matrixOptions.ts';\nimport { parseHarnessArgs } from '../cli/options.ts';\nimport {\n DEFAULT_THEME_NAME,\n HARNESS_STATE_POINTER,\n loadDomains,\n loadDoomConfig,\n loadHarnessState,\n loadMajorModesConfig,\n} from '../config/index.ts';\nimport type { HarnessOptions } from '../interfaces/index.ts';\nimport {\n assembleChildExtensions,\n assembleExtensions,\n CONTEXT_PACKAGES,\n createLayerResolvers,\n filterHookDisabledLayers,\n PERSONA_ENTRY,\n} from '../services/extensionAssembler.ts';\nimport { compileExtensionSet, compileResolvedEntries, extensionSetKey } from '../services/extensionCompiler.ts';\nimport { buildHarnessContext } from '../services/harnessContext.ts';\nimport { mergePiSettings, readPiSettings, serializePiSettings, writePiSettings } from '../services/piSettings.ts';\nimport { selectWorkflowExtensionForProcess } from '../services/resourceCollector.ts';\nimport { compileModeExtension, type ModeBuildSelection } from '../services/runtimeBundle.ts';\nimport {\n computeInputsHash,\n createMapResolvers,\n readMcpServerNames,\n readSyncState,\n recordResolvedEntries,\n SYNC_STATE_VERSION,\n type SyncSelection,\n type SyncState,\n syncDirectory,\n writeSyncState,\n} from '../services/syncState.ts';\nimport { findRepositoryRoot, ownEntry } from '../utils/index.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';\n/** Compiled extension bundles, keyed by content hash. */\nconst EXTENSION_CACHE_DIRECTORY = 'cache';\nconst MODE_DIST_DIRECTORY = 'dist';\nconst DOOM_ENTRY = 'doom';\nconst TRUST_ALWAYS = 'always';\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\ninterface BundleContext {\n options: Omit<HarnessOptions, 'repoRoot'>;\n majorModesConfig: Awaited<ReturnType<typeof buildHarnessContext>>['majorModesConfig'];\n environment: NodeJS.ProcessEnv;\n skillPaths: readonly string[];\n pluginRoots: readonly string[];\n}\n\nexport interface SyncResult {\n statePath: string;\n settingsPath: 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\n/** Differences between what a sync would produce and what is on disk. */\nexport function collectDrift(repoRoot: string, selection: SyncSelection, state: SyncState | undefined): string[] {\n if (!state) return ['no sync state: run doompi sync'];\n const drift: string[] = [];\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) !== state.inputsHash) drift.push('.doom configuration changed');\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 const settings = readPiSettings(repoRoot);\n const merged = mergePiSettings(settings, repoRoot, {\n extensionEntry: state.bootstrap ?? ownEntry(DOOM_ENTRY),\n themePath: state.baseline.themePath,\n themeName: state.baseline.themeName,\n });\n if (serializePiSettings(merged) !== serializePiSettings(settings)) drift.push('.pi/settings.json is out of date');\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, projectTrust: string): 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 `settings: ${result.settingsPath}`,\n '',\n 'Run pi from the repository root to use it.',\n // projectTrust only reaches Pi as a launcher flag, so a synced session has\n // to be told once that it will be asked.\n ...(projectTrust === TRUST_ALWAYS\n ? ['Pi asks once before loading project extensions: answer yes, or run /trust in the session.']\n : []),\n '',\n ].join('\\n');\n}\n\n/** Precompiles every mode Pi can switch to without restarting. */\nasync function compileModeBundles(\n repoRoot: string,\n context: BundleContext,\n resolved: Record<string, string>,\n compiled: Record<string, string>,\n cacheDirectory: string,\n outputDirectory: string,\n): Promise<Record<string, string>> {\n const bundles: Record<string, string> = {};\n const selectionFor = (majorMode: string, mute: boolean): ModeBuildSelection => ({\n majorMode,\n domains: [...context.options.domains],\n profile: context.options.profile,\n preset: context.options.preset,\n mute,\n autoStop: false,\n agents: context.options.agents,\n mcp: context.options.mcp,\n });\n const planFor = (layers: string[], source: boolean) => {\n const resolvers = createMapResolvers(resolved, source ? undefined : compiled);\n const workflowExtension = selectWorkflowExtensionForProcess(\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.workflow),\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.dispatcherWorkflow),\n context.environment,\n );\n return {\n base: {\n agents: context.options.agents,\n autoStop: false,\n preset: context.options.preset,\n mcpEntry: context.options.mcp ? resolvers.optionalPackageEntry(CONTEXT_PACKAGES.mcp) : undefined,\n personaEntry: resolvers.ownEntry(PERSONA_ENTRY),\n workflowExtension,\n majorModesConfig: context.majorModesConfig,\n resolvers,\n },\n layers,\n };\n };\n\n for (const [majorMode, layers] of Object.entries(context.majorModesConfig.majorMode)) {\n const selectedLayers = filterHookDisabledLayers(layers, context.options.hooks);\n for (const mute of [false, true]) {\n const source = planFor(selectedLayers, true);\n const runtime = planFor(selectedLayers, false);\n const sourceEntries = [...assembleExtensions({ ...source.base, layers: source.layers, mute })].reverse();\n const runtimeEntries = [...assembleExtensions({ ...runtime.base, layers: runtime.layers, mute })].reverse();\n const childExtensionPaths = assembleChildExtensions({\n ...source.base,\n layers: source.layers,\n agents: false,\n mute: true,\n });\n const key = extensionSetKey(runtimeEntries);\n try {\n const built = await compileModeExtension({\n repoRoot,\n selection: selectionFor(majorMode, mute),\n extensionPaths: sourceEntries,\n childExtensionPaths,\n skillPaths: context.skillPaths,\n pluginRoots: context.pluginRoots,\n outputName: `${majorMode}${mute ? '.mute' : ''}`,\n cacheDirectory,\n outputDirectory,\n });\n bundles[key] = built.bundle;\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[doompi] could not precompile ${majorMode}${mute ? ' (mute)' : ''}: ${detail}\\n`);\n }\n }\n }\n return bundles;\n}\n\n/** Resolves the matrix, stages it into `.pi/doom`, and points Pi at the result. */\nexport class SyncCommand {\n readonly name = SYNC_COMMAND;\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\n if (check) {\n const drift = collectDrift(repoRoot, selection, readSyncState(repoRoot));\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 result = await this.stage(repoRoot, parsed.options);\n emitFrontendHooks(repoRoot, output);\n output.write(formatSyncResult(result, loadDoomConfig(repoRoot).projectTrust));\n return 0;\n }\n\n private async stage(repoRoot: string, options: Omit<HarnessOptions, 'repoRoot'>): Promise<SyncResult> {\n const directory = syncDirectory(repoRoot);\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]);\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 // Sync describes the repository, not the directory it was invoked from.\n cwd: repoRoot,\n resourceDirectory: directory,\n });\n const selection = toSelection(options);\n // Pi would otherwise transpile every TypeScript extension through jiti on\n // each launch. Resolution already knows the whole list, so it is bundled to\n // plain ESM once here and imported natively for the rest of its life.\n const resolved = recordResolvedEntries(context.majorModesConfig, createLayerResolvers(repoRoot));\n const cacheDirectory = path.join(directory, EXTENSION_CACHE_DIRECTORY);\n const outputDirectory = path.join(directory, MODE_DIST_DIRECTORY);\n const compiled = await compileResolvedEntries(resolved, cacheDirectory);\n const bundles = await compileModeBundles(\n repoRoot,\n {\n options,\n majorModesConfig: context.majorModesConfig,\n environment: context.environment,\n skillPaths: context.resources.skillDirectories,\n pluginRoots: context.plugins.map((plugin) => plugin.directory),\n },\n resolved,\n compiled,\n cacheDirectory,\n outputDirectory,\n );\n // Pi loads this entry before it can inspect the synced state. Flattening\n // the bootstrap and composer graph removes their module-resolution fanout;\n // it then selects one of the mode bundles above without compiling at run\n // time. The content hash also lets settings replace an upgraded bootstrap\n // atomically on the next sync.\n const bootstrap = await compileExtensionSet([ownEntry(DOOM_ENTRY)], cacheDirectory, {\n outputDirectory,\n outputName: 'bootstrap',\n });\n const state: SyncState = {\n version: SYNC_STATE_VERSION,\n root: repoRoot,\n inputsHash: computeInputsHash(repoRoot, selection),\n selection,\n env: recordedEnvironment(context.environment),\n // Read back from the session file the context just wrote, which is the\n // only place these two live now.\n fileState: {\n profileEnvironment: loadHarnessState(context.environment).state.profileEnvironment,\n pluginHooks: context.resources.pluginHooks,\n },\n resolved,\n compiled,\n bundles,\n bootstrap,\n baseline: {\n mcpConfigPath: context.resources.mcpConfigPath,\n personaFile: context.environment[PERSONA_FILE_ENV],\n themePath: context.defaultThemePath,\n themeName: DEFAULT_THEME_NAME,\n },\n };\n\n return {\n statePath: await writeSyncState(repoRoot, state),\n settingsPath: writePiSettings(repoRoot, {\n extensionEntry: bootstrap,\n themePath: state.baseline.themePath,\n themeName: state.baseline.themeName,\n }),\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":"i/CAkDA,MACM,EAAe,UAGf,EAAe,EAAK,KAAK,QAAS,UAAW,iBAAiB,CAG9D,EAA4B,QAC5B,EAAsB,OACtB,EAAa,OAEb,EAAO,SASP,EAAoB,CAAC,UAAU,CAC/B,EAAgB,CAAC,qBAAsB,kBAAmB,qBAAsB,gBAAgB,CAQhG,EAAgB,IAAI,IAAI,CAC5B,0BACA,kBACA,cACA,kBAGA,EACA,wBACD,CAAC,CAqBF,SAAgB,EAAoB,EAAwD,CAC1F,IAAM,EAAmC,EAAE,CAC3C,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,EAAY,CAChD,IAAU,IAAA,IAAa,EAAc,IAAI,EAAI,GAC7C,EAAc,SAAS,EAAI,EAAI,EAAkB,KAAM,GAAW,EAAI,WAAW,EAAO,CAAC,IAC3F,EAAS,GAAO,GAGpB,OAAO,EAWT,SAAgB,EAAqB,EAAkB,EAAmD,CACxG,GAAM,CAAE,aAAc,EAAe,EAAS,CAE9C,OADK,EACE,CACL,GAAG,EACH,GAAI,EAAU,WAAa,CAAC,EAAA,kBACxB,EAAG,GAAwB,EAAU,UAAW,CAChD,EAAE,CACN,GAAI,EAAU,SAAW,CAAC,EAAA,eAAkC,EAAG,GAAqB,EAAU,QAAS,CAAG,EAAE,CAC5G,GAAI,EAAU,SAAW,EAAA,iBAAoC,IAAA,GACzD,EAAG,GAAqB,EAAU,QAAQ,KAAK,IAAI,CAAE,CACrD,EAAE,CACP,CAVsB,EAazB,SAAgB,EACd,EACe,CACf,MAAO,CACL,UAAW,EAAQ,UACnB,QAAS,EAAQ,QACjB,QAAS,EAAQ,QACjB,OAAQ,EAAQ,OACjB,CAIH,SAAgB,EAAa,EAAkB,EAA0B,EAAwC,CAC/G,GAAI,CAAC,EAAO,MAAO,CAAC,iCAAiC,CACrD,IAAM,EAAkB,EAAE,CACpB,EAAW,EAAM,WAErB,EAAS,YAAc,EAAU,WACjC,EAAS,UAAY,EAAU,SAC/B,EAAS,SAAW,EAAU,QAC9B,EAAS,QAAQ,KAAK,IAAI,GAAK,EAAU,QAAQ,KAAK,IAAI,GAE1D,EAAM,KAAK,wCAAwC,CAIjD,EAAkB,EAAU,EAAS,GAAK,EAAM,YAAY,EAAM,KAAK,8BAA8B,CAIvG,KAAK,UAAU,EAAsB,EAAqB,EAAS,CAAE,EAAqB,EAAS,CAAC,CAAC,GACrG,KAAK,UAAU,EAAM,SAAS,EAE9B,EAAM,KAAK,mCAAmC,CAEhD,IAAM,EAAW,EAAe,EAAS,CAOzC,OADI,EALW,EAAgB,EAAU,EAAU,CACjD,eAAgB,EAAM,WAAa,EAAS,EAAW,CACvD,UAAW,EAAM,SAAS,UAC1B,UAAW,EAAM,SAAS,UAC3B,CAC6B,CAAC,GAAK,EAAoB,EAAS,EAAE,EAAM,KAAK,mCAAmC,CAC1G,EAIT,SAAS,EAAkB,EAAkB,EAA0B,CACrE,IAAM,EAAU,EAAK,KAAK,EAAU,EAAa,CACjD,GAAI,CAAC,EAAG,WAAW,EAAQ,CAAE,OAC7B,IAAM,EAAS,EAAU,QAAQ,SAAU,CAAC,EAAS,UAAU,CAAE,CAAE,IAAK,EAAU,SAAU,OAAQ,CAAC,CACrG,GAAI,EAAO,SAAW,EAAG,CACvB,EAAO,MAAM;EAAoD,CACjE,OAEF,EAAO,MAAM,gCAAgC,EAAO,QAAQ,MAAM,EAAI,QAAQ,OAAO,EAAO,OAAO,GAAG,KAAK,CAG7G,SAAgB,EAAiB,EAAoB,EAA8B,CACjF,GAAM,CAAE,aAAc,EACtB,MAAO,CACL,aAAa,EAAU,YACvB,aAAa,EAAU,QAAQ,KAAK,KAAK,EAAI,IAC7C,aAAa,EAAU,SAAW,IAClC,aAAa,EAAO,aACpB,aAAa,EAAO,aACpB,aAAa,EAAO,WAAW,KAAK,KAAK,EAAI,IAC7C,aAAa,EAAO,YACpB,aAAa,EAAO,eACpB,GACA,6CAGA,GAAI,IAAiB,SACjB,CAAC,4FAA4F,CAC7F,EAAE,CACN,GACD,CAAC,KAAK;EAAK,CAId,eAAe,EACb,EACA,EACA,EACA,EACA,EACA,EACiC,CACjC,IAAM,EAAkC,EAAE,CACpC,GAAgB,EAAmB,KAAuC,CAC9E,YACA,QAAS,CAAC,GAAG,EAAQ,QAAQ,QAAQ,CACrC,QAAS,EAAQ,QAAQ,QACzB,OAAQ,EAAQ,QAAQ,OACxB,OACA,SAAU,GACV,OAAQ,EAAQ,QAAQ,OACxB,IAAK,EAAQ,QAAQ,IACtB,EACK,GAAW,EAAkB,IAAoB,CACrD,IAAM,EAAY,EAAmB,EAAU,EAAS,IAAA,GAAY,EAAS,CACvE,EAAoB,EACxB,EAAU,qBAAqB,EAAiB,SAAS,CACzD,EAAU,qBAAqB,EAAiB,mBAAmB,CACnE,EAAQ,YACT,CACD,MAAO,CACL,KAAM,CACJ,OAAQ,EAAQ,QAAQ,OACxB,SAAU,GACV,OAAQ,EAAQ,QAAQ,OACxB,SAAU,EAAQ,QAAQ,IAAM,EAAU,qBAAqB,EAAiB,IAAI,CAAG,IAAA,GACvF,aAAc,EAAU,SAAS,EAAc,CAC/C,oBACA,iBAAkB,EAAQ,iBAC1B,YACD,CACD,SACD,EAGH,IAAK,GAAM,CAAC,EAAW,KAAW,OAAO,QAAQ,EAAQ,iBAAiB,UAAU,CAAE,CACpF,IAAM,EAAiB,EAAyB,EAAQ,EAAQ,QAAQ,MAAM,CAC9E,IAAK,IAAM,IAAQ,CAAC,GAAO,GAAK,CAAE,CAChC,IAAM,EAAS,EAAQ,EAAgB,GAAK,CACtC,EAAU,EAAQ,EAAgB,GAAM,CACxC,EAAgB,CAAC,GAAG,EAAmB,CAAE,GAAG,EAAO,KAAM,OAAQ,EAAO,OAAQ,OAAM,CAAC,CAAC,CAAC,SAAS,CAClG,EAAiB,CAAC,GAAG,EAAmB,CAAE,GAAG,EAAQ,KAAM,OAAQ,EAAQ,OAAQ,OAAM,CAAC,CAAC,CAAC,SAAS,CACrG,EAAsB,EAAwB,CAClD,GAAG,EAAO,KACV,OAAQ,EAAO,OACf,OAAQ,GACR,KAAM,GACP,CAAC,CACI,EAAM,EAAgB,EAAe,CAC3C,GAAI,CAYF,EAAQ,IAAO,MAXK,EAAqB,CACvC,WACA,UAAW,EAAa,EAAW,EAAK,CACxC,eAAgB,EAChB,sBACA,WAAY,EAAQ,WACpB,YAAa,EAAQ,YACrB,WAAY,GAAG,IAAY,EAAO,QAAU,KAC5C,iBACA,kBACD,CAAC,EACmB,aACd,EAAO,CACd,IAAM,EAAS,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CACrE,QAAQ,OAAO,MAAM,iCAAiC,IAAY,EAAO,UAAY,GAAG,IAAI,EAAO,IAAI,GAI7G,OAAO,EAIT,IAAa,GAAb,KAAyB,CACvB,KAAgB,OAEhB,QAAQ,EAAyB,CAC/B,OAAO,EAAK,KAAO,KAAK,KAG1B,MAAM,QACJ,EACA,EAAiC,QAAQ,IACzC,EAAmB,QAAQ,KAAK,CAChC,EAAqB,QAAQ,OACZ,CACjB,IAAM,EAAQ,EAAK,SAAS,EAAa,CACnC,EAAO,EAAK,MAAM,EAAE,CAAC,OAAQ,GAAa,IAAa,EAAa,CACpE,EAAgB,EAAY,YAC5B,EAAW,EAAgB,EAAK,QAAQ,EAAc,CAAG,EAAmB,EAAiB,CAC7F,EAAmB,EAAqB,EAAS,CAAC,iBAClD,EAAiB,EAAY,EAAS,CAAC,eACvC,EAAS,EACb,EACA,EAAqB,EAAU,EAAY,CAC3C,EACA,EACA,EACD,CACK,EAAY,EAAY,EAAO,QAAQ,CAE7C,GAAI,EAAO,CACT,IAAM,EAAQ,EAAa,EAAU,EAAW,EAAc,EAAS,CAAC,CAMxE,OALI,EAAM,SAAW,GACnB,EAAO,MAAM;EAA8B,CACpC,IAET,EAAO,MAAM,gCAAgC,EAAM,IAAK,GAAU,KAAK,IAAQ,CAAC,KAAK;EAAK,CAAC,IAAI,CACxF,GAGT,IAAM,EAAS,MAAM,KAAK,MAAM,EAAU,EAAO,QAAQ,CAGzD,OAFA,EAAkB,EAAU,EAAO,CACnC,EAAO,MAAM,EAAiB,EAAQ,EAAe,EAAS,CAAC,aAAa,CAAC,CACtE,EAGT,MAAc,MAAM,EAAkB,EAAgE,CACpG,IAAM,EAAY,EAAc,EAAS,CAInC,EAAY,IAAI,IAAI,CAAC,MAAe,EAA2B,EAAoB,CAAC,CAC1F,IAAK,IAAM,KAAS,EAAG,WAAW,EAAU,CAAG,EAAG,YAAY,EAAU,CAAG,EAAE,CACtE,EAAU,IAAI,EAAM,EAAE,EAAG,OAAO,EAAK,KAAK,EAAW,EAAM,CAAE,CAAE,UAAW,GAAM,MAAO,GAAM,CAAC,CAGrG,IAAM,EAAU,MAAM,EAAoB,CACxC,GAAG,EACH,WAEA,IAAK,EACL,kBAAmB,EACpB,CAAC,CACI,EAAY,EAAY,EAAQ,CAIhC,EAAW,EAAsB,EAAQ,iBAAkB,EAAqB,EAAS,CAAC,CAC1F,EAAiB,EAAK,KAAK,EAAW,EAA0B,CAChE,EAAkB,EAAK,KAAK,EAAW,EAAoB,CAC3D,EAAW,MAAM,EAAuB,EAAU,EAAe,CACjE,EAAU,MAAM,EACpB,EACA,CACE,UACA,iBAAkB,EAAQ,iBAC1B,YAAa,EAAQ,YACrB,WAAY,EAAQ,UAAU,iBAC9B,YAAa,EAAQ,QAAQ,IAAK,GAAW,EAAO,UAAU,CAC/D,CACD,EACA,EACA,EACA,EACD,CAMK,EAAY,MAAM,EAAoB,CAAC,EAAS,EAAW,CAAC,CAAE,EAAgB,CAClF,kBACA,WAAY,YACb,CAAC,CACI,EAAmB,CACvB,QAAA,EACA,KAAM,EACN,WAAY,EAAkB,EAAU,EAAU,CAClD,YACA,IAAK,EAAoB,EAAQ,YAAY,CAG7C,UAAW,CACT,mBAAoB,EAAiB,EAAQ,YAAY,CAAC,MAAM,mBAChE,YAAa,EAAQ,UAAU,YAChC,CACD,WACA,WACA,UACA,YACA,SAAU,CACR,cAAe,EAAQ,UAAU,cACjC,YAAa,EAAQ,YAAY,oBACjC,UAAW,EAAQ,iBACnB,UAAW,EACZ,CACF,CAED,MAAO,CACL,UAAW,MAAM,EAAe,EAAU,EAAM,CAChD,aAAc,EAAgB,EAAU,CACtC,eAAgB,EAChB,UAAW,EAAM,SAAS,UAC1B,UAAW,EAAM,SAAS,UAC3B,CAAC,CACF,YACA,WAAY,EAAM,SAAS,cAAgB,EAAmB,EAAM,SAAS,cAAc,CAAG,EAAE,CAChG,WAAY,EAAQ,UAAU,WAC9B,WAAY,EAAQ,UAAU,WAC/B"}
|
|
1
|
+
{"version":3,"file":"syncCommand.mjs","names":[],"sources":["../../src/commands/syncCommand.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { DOOMPI_DOMAINS_ENV, DOOMPI_MAJOR_MODE_ENV, DOOMPI_PROFILE_ENV } from '../cli/matrixOptions.ts';\nimport { parseHarnessArgs } from '../cli/options.ts';\nimport {\n DEFAULT_THEME_NAME,\n HARNESS_STATE_POINTER,\n loadDomains,\n loadDoomConfig,\n loadHarnessState,\n loadMajorModesConfig,\n} from '../config/index.ts';\nimport type { HarnessOptions } from '../interfaces/index.ts';\nimport {\n assembleChildExtensions,\n assembleExtensions,\n CONTEXT_PACKAGES,\n createLayerResolvers,\n filterHookDisabledLayers,\n PERSONA_ENTRY,\n} from '../services/extensionAssembler.ts';\nimport { compileExtensionSet, compileResolvedEntries, extensionSetKey } from '../services/extensionCompiler.ts';\nimport { buildHarnessContext } from '../services/harnessContext.ts';\nimport { piExtensionAliasIsCurrent, writePiExtensionAlias } from '../services/piExtensionAlias.ts';\nimport { mergePiSettings, readPiSettings, serializePiSettings, writePiSettings } from '../services/piSettings.ts';\nimport { selectWorkflowExtensionForProcess } from '../services/resourceCollector.ts';\nimport { compileModeExtension, type ModeBuildSelection } from '../services/runtimeBundle.ts';\nimport {\n computeInputsHash,\n createMapResolvers,\n readMcpServerNames,\n readSyncState,\n recordResolvedEntries,\n SYNC_STATE_VERSION,\n type SyncSelection,\n type SyncState,\n syncDirectory,\n writeSyncState,\n} from '../services/syncState.ts';\nimport { findRepositoryRoot, ownEntry } from '../utils/index.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';\n/** Compiled extension bundles, keyed by content hash. */\nconst EXTENSION_CACHE_DIRECTORY = 'cache';\nconst MODE_DIST_DIRECTORY = 'dist';\nconst DOOM_ENTRY = 'doom';\nconst TRUST_ALWAYS = 'always';\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\ninterface BundleContext {\n options: Omit<HarnessOptions, 'repoRoot'>;\n majorModesConfig: Awaited<ReturnType<typeof buildHarnessContext>>['majorModesConfig'];\n environment: NodeJS.ProcessEnv;\n skillPaths: readonly string[];\n pluginRoots: readonly string[];\n}\n\nexport interface SyncResult {\n statePath: string;\n settingsPath: 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\n/** Differences between what a sync would produce and what is on disk. */\nexport function collectDrift(repoRoot: string, selection: SyncSelection, state: SyncState | undefined): string[] {\n if (!state) return ['no sync state: run doompi sync'];\n const drift: string[] = [];\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) !== state.inputsHash) drift.push('.doom configuration changed');\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 const settings = readPiSettings(repoRoot);\n const merged = mergePiSettings(settings, repoRoot, {\n themePath: state.baseline.themePath,\n themeName: state.baseline.themeName,\n });\n if (serializePiSettings(merged) !== serializePiSettings(settings)) drift.push('.pi/settings.json is out of date');\n if (!piExtensionAliasIsCurrent(repoRoot)) drift.push('Pi extension package alias is out of date');\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, projectTrust: string): 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 `settings: ${result.settingsPath}`,\n '',\n 'Run pi from the repository root to use it.',\n // projectTrust only reaches Pi as a launcher flag, so a synced session has\n // to be told once that it will be asked.\n ...(projectTrust === TRUST_ALWAYS\n ? ['Pi asks once before loading project extensions: answer yes, or run /trust in the session.']\n : []),\n '',\n ].join('\\n');\n}\n\n/** Precompiles every mode Pi can switch to without restarting. */\nasync function compileModeBundles(\n repoRoot: string,\n context: BundleContext,\n resolved: Record<string, string>,\n compiled: Record<string, string>,\n cacheDirectory: string,\n outputDirectory: string,\n): Promise<Record<string, string>> {\n const bundles: Record<string, string> = {};\n const selectionFor = (majorMode: string, mute: boolean): ModeBuildSelection => ({\n majorMode,\n domains: [...context.options.domains],\n profile: context.options.profile,\n preset: context.options.preset,\n mute,\n autoStop: false,\n agents: context.options.agents,\n mcp: context.options.mcp,\n });\n const planFor = (layers: string[], source: boolean) => {\n const resolvers = createMapResolvers(resolved, source ? undefined : compiled);\n const workflowExtension = selectWorkflowExtensionForProcess(\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.workflow),\n resolvers.optionalPackageEntry(CONTEXT_PACKAGES.dispatcherWorkflow),\n context.environment,\n );\n return {\n base: {\n agents: context.options.agents,\n autoStop: false,\n preset: context.options.preset,\n mcpEntry: context.options.mcp ? resolvers.optionalPackageEntry(CONTEXT_PACKAGES.mcp) : undefined,\n personaEntry: resolvers.ownEntry(PERSONA_ENTRY),\n workflowExtension,\n majorModesConfig: context.majorModesConfig,\n resolvers,\n },\n layers,\n };\n };\n\n for (const [majorMode, layers] of Object.entries(context.majorModesConfig.majorMode)) {\n const selectedLayers = filterHookDisabledLayers(layers, context.options.hooks);\n for (const mute of [false, true]) {\n const source = planFor(selectedLayers, true);\n const runtime = planFor(selectedLayers, false);\n const sourceEntries = [...assembleExtensions({ ...source.base, layers: source.layers, mute })].reverse();\n const runtimeEntries = [...assembleExtensions({ ...runtime.base, layers: runtime.layers, mute })].reverse();\n const childExtensionPaths = assembleChildExtensions({\n ...source.base,\n layers: source.layers,\n agents: false,\n mute: true,\n });\n const key = extensionSetKey(runtimeEntries);\n try {\n const built = await compileModeExtension({\n repoRoot,\n selection: selectionFor(majorMode, mute),\n extensionPaths: sourceEntries,\n childExtensionPaths,\n skillPaths: context.skillPaths,\n pluginRoots: context.pluginRoots,\n outputName: `${majorMode}${mute ? '.mute' : ''}`,\n cacheDirectory,\n outputDirectory,\n });\n bundles[key] = built.bundle;\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[doompi] could not precompile ${majorMode}${mute ? ' (mute)' : ''}: ${detail}\\n`);\n }\n }\n }\n return bundles;\n}\n\n/** Resolves the matrix, stages it into `.pi/doom`, and points Pi at the result. */\nexport class SyncCommand {\n readonly name = SYNC_COMMAND;\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\n if (check) {\n const drift = collectDrift(repoRoot, selection, readSyncState(repoRoot));\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 result = await this.stage(repoRoot, parsed.options);\n emitFrontendHooks(repoRoot, output);\n output.write(formatSyncResult(result, loadDoomConfig(repoRoot).projectTrust));\n return 0;\n }\n\n private async stage(repoRoot: string, options: Omit<HarnessOptions, 'repoRoot'>): Promise<SyncResult> {\n const directory = syncDirectory(repoRoot);\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]);\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 // Sync describes the repository, not the directory it was invoked from.\n cwd: repoRoot,\n resourceDirectory: directory,\n });\n const selection = toSelection(options);\n // Pi would otherwise transpile every TypeScript extension through jiti on\n // each launch. Resolution already knows the whole list, so it is bundled to\n // plain ESM once here and imported natively for the rest of its life.\n const resolved = recordResolvedEntries(context.majorModesConfig, createLayerResolvers(repoRoot));\n const cacheDirectory = path.join(directory, EXTENSION_CACHE_DIRECTORY);\n const outputDirectory = path.join(directory, MODE_DIST_DIRECTORY);\n const compiled = await compileResolvedEntries(resolved, cacheDirectory);\n const bundles = await compileModeBundles(\n repoRoot,\n {\n options,\n majorModesConfig: context.majorModesConfig,\n environment: context.environment,\n skillPaths: context.resources.skillDirectories,\n pluginRoots: context.plugins.map((plugin) => plugin.directory),\n },\n resolved,\n compiled,\n cacheDirectory,\n outputDirectory,\n );\n // The package entry loads this artifact after locating the synced state.\n // Flattening the bootstrap and composer graph removes their module-resolution\n // fanout; it then selects one of the mode bundles above without compiling at\n // run time. The content hash makes upgraded bootstraps coexist atomically.\n const bootstrap = await compileExtensionSet([ownEntry(DOOM_ENTRY)], cacheDirectory, {\n outputDirectory,\n outputName: 'bootstrap',\n });\n const state: SyncState = {\n version: SYNC_STATE_VERSION,\n root: repoRoot,\n inputsHash: computeInputsHash(repoRoot, selection),\n selection,\n env: recordedEnvironment(context.environment),\n // Read back from the session file the context just wrote, which is the\n // only place these two live now.\n fileState: {\n profileEnvironment: loadHarnessState(context.environment).state.profileEnvironment,\n pluginHooks: context.resources.pluginHooks,\n },\n resolved,\n compiled,\n bundles,\n bootstrap,\n baseline: {\n mcpConfigPath: context.resources.mcpConfigPath,\n personaFile: context.environment[PERSONA_FILE_ENV],\n themePath: context.defaultThemePath,\n themeName: DEFAULT_THEME_NAME,\n },\n };\n\n const statePath = await writeSyncState(repoRoot, state);\n writePiExtensionAlias(repoRoot);\n return {\n statePath,\n settingsPath: writePiSettings(repoRoot, {\n themePath: state.baseline.themePath,\n themeName: state.baseline.themeName,\n }),\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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"}
|