@agimon-ai/doompi 0.0.1-alpha.46 → 0.0.1-alpha.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +12 -10
  2. package/dist/src/adapters/bootstrapLocator.cjs +1 -1
  3. package/dist/src/adapters/bootstrapLocator.cjs.map +1 -1
  4. package/dist/src/adapters/bootstrapLocator.mjs +1 -1
  5. package/dist/src/adapters/bootstrapLocator.mjs.map +1 -1
  6. package/dist/src/adapters/composer.cjs +1 -1
  7. package/dist/src/adapters/composer.cjs.map +1 -1
  8. package/dist/src/adapters/composer.d.cts.map +1 -1
  9. package/dist/src/adapters/composer.d.mts.map +1 -1
  10. package/dist/src/adapters/composer.mjs +1 -1
  11. package/dist/src/adapters/composer.mjs.map +1 -1
  12. package/dist/src/adapters/config/harnessState.d.mts +2 -2
  13. package/dist/src/adapters/harnessContext.cjs +1 -1
  14. package/dist/src/adapters/harnessContext.mjs +1 -1
  15. package/dist/src/adapters/piExtensionDispatcher.cjs +7 -8
  16. package/dist/src/adapters/piExtensionDispatcher.cjs.map +1 -1
  17. package/dist/src/adapters/piExtensionDispatcher.mjs +7 -8
  18. package/dist/src/adapters/piExtensionDispatcher.mjs.map +1 -1
  19. package/dist/src/adapters/repository/repository.cjs +1 -1
  20. package/dist/src/adapters/repository/repository.cjs.map +1 -1
  21. package/dist/src/adapters/repository/repository.d.cts +3 -1
  22. package/dist/src/adapters/repository/repository.d.cts.map +1 -1
  23. package/dist/src/adapters/repository/repository.d.mts +3 -1
  24. package/dist/src/adapters/repository/repository.d.mts.map +1 -1
  25. package/dist/src/adapters/repository/repository.mjs +1 -1
  26. package/dist/src/adapters/repository/repository.mjs.map +1 -1
  27. package/dist/src/commands/buildCommand.cjs +2 -2
  28. package/dist/src/commands/buildCommand.cjs.map +1 -1
  29. package/dist/src/commands/buildCommand.mjs +2 -2
  30. package/dist/src/commands/buildCommand.mjs.map +1 -1
  31. package/dist/src/commands/syncCommand.cjs +7 -6
  32. package/dist/src/commands/syncCommand.cjs.map +1 -1
  33. package/dist/src/commands/syncCommand.d.cts.map +1 -1
  34. package/dist/src/commands/syncCommand.d.mts.map +1 -1
  35. package/dist/src/commands/syncCommand.mjs +7 -6
  36. package/dist/src/commands/syncCommand.mjs.map +1 -1
  37. package/dist/src/commands/syncPipeline.cjs +2 -2
  38. package/dist/src/commands/syncPipeline.cjs.map +1 -1
  39. package/dist/src/commands/syncPipeline.mjs +2 -2
  40. package/dist/src/commands/syncPipeline.mjs.map +1 -1
  41. package/dist/src/extensions/entries/modeCatalog.cjs +1 -1
  42. package/dist/src/extensions/entries/modeCatalog.cjs.map +1 -1
  43. package/dist/src/extensions/entries/modeCatalog.d.cts.map +1 -1
  44. package/dist/src/extensions/entries/modeCatalog.d.mts.map +1 -1
  45. package/dist/src/extensions/entries/modeCatalog.mjs +1 -1
  46. package/dist/src/extensions/entries/modeCatalog.mjs.map +1 -1
  47. package/dist/src/services/contextCatalog.cjs +2 -0
  48. package/dist/src/services/contextCatalog.cjs.map +1 -0
  49. package/dist/src/services/contextCatalog.mjs +2 -0
  50. package/dist/src/services/contextCatalog.mjs.map +1 -0
  51. package/dist/src/services/contextProjection.cjs +2 -0
  52. package/dist/src/services/contextProjection.cjs.map +1 -0
  53. package/dist/src/services/contextProjection.mjs +2 -0
  54. package/dist/src/services/contextProjection.mjs.map +1 -0
  55. package/dist/src/services/extensionAssembler.cjs +1 -1
  56. package/dist/src/services/extensionAssembler.cjs.map +1 -1
  57. package/dist/src/services/extensionAssembler.d.cts +19 -1
  58. package/dist/src/services/extensionAssembler.d.cts.map +1 -1
  59. package/dist/src/services/extensionAssembler.d.mts +19 -1
  60. package/dist/src/services/extensionAssembler.d.mts.map +1 -1
  61. package/dist/src/services/extensionAssembler.mjs +1 -1
  62. package/dist/src/services/extensionAssembler.mjs.map +1 -1
  63. package/dist/utils/repository.cjs +1 -1
  64. package/dist/utils/repository.d.cts +2 -2
  65. package/dist/utils/repository.d.mts +2 -2
  66. package/dist/utils/repository.mjs +1 -1
  67. package/package.json +13 -13
package/README.md CHANGED
@@ -76,24 +76,26 @@ dependency closure.
76
76
  ### Sync storage and worktrees
77
77
 
78
78
  DoomPi stores generated sync data under `~/.pi/.doom/sync`, outside the repository. Repository and
79
- worktree identities select separate namespaces. Each successful sync writes a new immutable generation,
80
- then atomically publishes that generation's registration as the final commit step. A failed sync removes
81
- its unpublished generation and leaves the previous registration active. Syncing one repository never
82
- rewrites another repository's registration or generation.
79
+ worktree identities select separate namespaces. Outside a repository, the canonical `~/.pi/.doom`
80
+ configuration gets its own global composition and registration. Each successful sync writes a new immutable
81
+ generation, then atomically publishes that generation's registration as the final commit step. A failed sync
82
+ removes its unpublished generation and leaves the previous registration active. One sync target never rewrites
83
+ another target's registration or generation.
83
84
 
84
85
  `dpi` preserves Pi's normal global and repository settings, then applies DoomPi's extension and theme
85
86
  settings in memory. It never writes those values to `.pi/settings.json`.
86
87
 
87
88
  Run `doompi sync --check` to detect missing, invalid, or stale registered state. Unregistered legacy state
88
- is not loaded. Run `doompi sync` in that repository to publish a current generation.
89
+ is not loaded. Inside a repository, `doompi sync` publishes that repository and worktree. Outside every
90
+ repository, it publishes the global composition from `~/.pi/.doom`.
89
91
 
90
92
  When you are comfortable with DoomPi and no longer need the side-by-side experiment, initialize the
91
- normal Pi integration and synchronize each repository you use:
93
+ normal Pi integration and synchronize the scopes you use:
92
94
 
93
95
  ```bash
94
96
  doompi init # seed ~/.pi/.doom and register the managed Pi integration
95
- doompi sync # publish this repository and worktree's generated runtime
96
- pi # resolve the nearest synchronized repository at startup
97
+ doompi sync # publish the current repository, or the global composition
98
+ pi # use the nearest repository, otherwise the global composition
97
99
  ```
98
100
 
99
101
  `doompi` remains available as an explicit harness when you want per-run matrix flags:
@@ -691,8 +693,8 @@ its owning package's controls; values supplied by callers are not automatically
691
693
  | -------------------------------------------- | --------------------------------------------------------------------------------- |
692
694
  | `dpi init`, `dpi sync`, `dpi` | Creates, synchronizes, and runs the repository-scoped comparison setup |
693
695
  | `doompi init` | Seeds personal config and installs the managed Pi dispatcher, settings, and theme |
694
- | `doompi sync` | Atomically publishes state, web, and API artifacts for one repository/worktree |
695
- | `doompi sync --check` | Exits non-zero when the repository's registered generation is missing or stale |
696
+ | `doompi sync` | Publishes the current repository, or the global composition outside repositories |
697
+ | `doompi sync --check` | Exits non-zero when the applicable registered generation is missing or stale |
696
698
  | `doompi compat <codex\|claude\|antigravity>` | Resolves the selected matrix and launches the compatibility adapter |
697
699
  | `doom-runner` | Inspects and controls supervised Runner processes and logs |
698
700
  | `--explain` | Prints the resolved matrix and estimated prompt cost without launching Pi |
@@ -1,2 +1,2 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./repository/repository.cjs"),n=require("./syncRegistration.cjs");require("./syncStateContract.cjs");const r=require("./syncState.cjs");let i=require("node:url"),a=require("node:path");a=e.__toESM(a,1);let o=require("node:crypto");o=e.__toESM(o,1);let s=require("node:fs");s=e.__toESM(s,1);let c=require("node:os");c=e.__toESM(c,1);const l=new Map;function u(e){try{return o.default.createHash(`sha256`).update(s.default.readFileSync(e)).digest(`hex`)}catch{return}}function d(e){let t;try{t=s.default.statSync(e)}catch{return}if(!t.isFile())return;let n=u(e);if(n!==void 0)return{path:e,size:t.size,mtimeMs:t.mtimeMs,sha256:n}}function f(e){if(m(e)&&typeof e.path==`string`&&typeof e.size==`number`&&typeof e.mtimeMs==`number`&&typeof e.sha256==`string`)return{path:e.path,size:e.size,mtimeMs:e.mtimeMs,sha256:e.sha256}}function p(e){let t=[],n=o.default.createHash(`sha256`);for(let r of e){let e;try{e=s.default.statSync(r.path)}catch{return!1}if(!e.isFile())return!1;n.update(`${r.path} ${String(e.size)} ${String(e.mtimeMs)} `),(e.size!==r.size||e.mtimeMs!==r.mtimeMs)&&t.push(r)}if(t.length===0)return!0;let r=n.digest(`hex`),i=l.get(r);if(i!==void 0)return i;let a=t.every(e=>u(e.path)===e.sha256);return l.set(r,a),a}function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function h(e){return m(e)?Object.fromEntries(Object.entries(e).filter(e=>typeof e[1]==`string`)):{}}function g(e){let t=a.default.resolve(e);try{return s.default.realpathSync(t)}catch{let e=a.default.dirname(t);return e===t?t:a.default.join(g(e),a.default.basename(t))}}function _(e,t){let n=a.default.relative(g(e),g(t));return n===``||!n.startsWith(`..`)&&!a.default.isAbsolute(n)}function v(e=require("url").pathToFileURL(__filename).href){let t=e.endsWith(`.ts`)?`ts`:`mjs`,n=a.default.dirname(a.default.dirname((0,i.fileURLToPath)(e)));return a.default.join(n,`extensions`,`entries`,`doom.${t}`)}function y(e,t){return n.readSyncRegistration(e,t)!==void 0}function b(e,n=c.default.homedir()){try{if(y(e,n))return g(e);let r=t.findRepositoryRoot(e);return y(r,n)?r:void 0}catch{return}}function x(e,t=c.default.homedir()){let i=n.readSyncRegistration(e,t);if(!i)return;let a=i.statePath,o=i.generationRoot,s=r.readSyncState(e,t);if(!s)return;let l=typeof s.bootstrap==`string`?s.bootstrap:void 0;if(l&&!_(o,l))throw Error(`Doom bootstrap must stay inside ${o}: ${l}`);let u;if(s.precompile!==void 0){let e=s.precompile;if(!m(e)||typeof e.version!=`number`||e.strategy!==`bundle`||typeof e.bootstrapEntry!=`string`||typeof e.bootstrapManifest!=`string`||!m(e.bundleManifests)||Object.values(e.bundleManifests).some(e=>typeof e!=`string`))throw Error(`Doom sync state at ${a} has an invalid precompile record`);u={version:e.version,strategy:e.strategy,bootstrapEntry:e.bootstrapEntry,bootstrapManifest:e.bootstrapManifest,bundleManifests:e.bundleManifests}}return{statePath:a,generatedDirectory:o,bootstrap:l,bundles:h(s.bundles),precompile:u}}function S(e,t){if(_(t,e))try{let n=JSON.parse(s.default.readFileSync(e,`utf8`));if(!m(n)||typeof n.output!=`string`||!Array.isArray(n.artifacts)||n.artifacts.some(e=>typeof e!=`string`)||!Array.isArray(n.entries)||n.entries.some(e=>typeof e!=`string`)||!Array.isArray(n.inputs))return;let r=[];for(let e of n.inputs){let t=f(e);if(t===void 0)return;r.push(t)}let i=n.artifacts,a=n.entries;return!_(t,n.output)||i.some(e=>!_(t,e))?void 0:{output:n.output,artifacts:i,entries:a,inputs:r}}catch{return}}function C(e){return!s.default.existsSync(e.output)||e.artifacts.some(e=>!s.default.existsSync(e))?!1:p(e.inputs)}function w(e,t){if(!e.bootstrap||!e.precompile||e.precompile.version!==5||g(e.precompile.bootstrapEntry)!==g(t))return;let n=S(e.precompile.bootstrapManifest,e.generatedDirectory);if(!(!n||!C(n)||n.output!==e.bootstrap||n.entries.length!==1||g(n.entries[0]??``)!==g(t)))return n}function T(e,t,n=c.default.homedir()){let r=x(e,n),i=t??v();return r?{bootstrap:r.bootstrap,fresh:w(r,i)!==void 0}:{bootstrap:void 0,fresh:!1}}function E(e,t,n=c.default.homedir()){let r=x(e,n),i=r?.bundles[t],a=r?.precompile?.bundleManifests[t];if(!r||r.precompile?.version!==5||!i||!a)return{bundle:i,fresh:!1};let o=S(a,r.generatedDirectory);return{bundle:i,fresh:!!(o&&o.output===i&&C(o))}}function D(e,t,n=c.default.homedir()){let r=x(e,n),i=t??v();if(!r?.bootstrap||!r.precompile||!w(r,i))return{bootstrap:r?.bootstrap,fresh:!1};let a=Object.values(r.precompile.bundleManifests);if(new Set(a).size!==a.length)return{bootstrap:r.bootstrap,fresh:!1};let o=Object.entries(r.bundles).every(([e,t])=>{let n=r.precompile?.bundleManifests[e];if(!n)return!1;let i=S(n,r.generatedDirectory);return!!(i&&i.output===t&&C(i))});return{bootstrap:r.bootstrap,fresh:o}}exports.findSyncedRoot=b,exports.fingerprintInput=d,exports.inputsAreFresh=p,exports.parseInputFingerprint=f,exports.readBootstrapStatus=D,exports.readBundleStatus=E,exports.readStartupBootstrapStatus=T;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./repository/repository.cjs"),n=require("./syncRegistration.cjs");require("./syncStateContract.cjs");const r=require("./syncState.cjs");let i=require("node:url"),a=require("node:path");a=e.__toESM(a,1);let o=require("node:crypto");o=e.__toESM(o,1);let s=require("node:fs");s=e.__toESM(s,1);let c=require("node:os");c=e.__toESM(c,1);const l=new Map;function u(e){try{return o.default.createHash(`sha256`).update(s.default.readFileSync(e)).digest(`hex`)}catch{return}}function d(e){let t;try{t=s.default.statSync(e)}catch{return}if(!t.isFile())return;let n=u(e);if(n!==void 0)return{path:e,size:t.size,mtimeMs:t.mtimeMs,sha256:n}}function f(e){if(m(e)&&typeof e.path==`string`&&typeof e.size==`number`&&typeof e.mtimeMs==`number`&&typeof e.sha256==`string`)return{path:e.path,size:e.size,mtimeMs:e.mtimeMs,sha256:e.sha256}}function p(e){let t=[],n=o.default.createHash(`sha256`);for(let r of e){let e;try{e=s.default.statSync(r.path)}catch{return!1}if(!e.isFile())return!1;n.update(`${r.path} ${String(e.size)} ${String(e.mtimeMs)} `),(e.size!==r.size||e.mtimeMs!==r.mtimeMs)&&t.push(r)}if(t.length===0)return!0;let r=n.digest(`hex`),i=l.get(r);if(i!==void 0)return i;let a=t.every(e=>u(e.path)===e.sha256);return l.set(r,a),a}function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function h(e){return m(e)?Object.fromEntries(Object.entries(e).filter(e=>typeof e[1]==`string`)):{}}function g(e){let t=a.default.resolve(e);try{return s.default.realpathSync(t)}catch{let e=a.default.dirname(t);return e===t?t:a.default.join(g(e),a.default.basename(t))}}function _(e,t){let n=a.default.relative(g(e),g(t));return n===``||!n.startsWith(`..`)&&!a.default.isAbsolute(n)}function v(e=require("url").pathToFileURL(__filename).href){let t=e.endsWith(`.ts`)?`ts`:`mjs`,n=a.default.dirname(a.default.dirname((0,i.fileURLToPath)(e)));return a.default.join(n,`extensions`,`entries`,`doom.${t}`)}function y(e,t){return n.readSyncRegistration(e,t)!==void 0}function b(e,n=c.default.homedir()){if(y(e,n))return g(e);let r=t.resolveDoomConfigurationRoot(e,n);return y(r,n)?g(r):void 0}function x(e,t=c.default.homedir()){let i=n.readSyncRegistration(e,t);if(!i)return;let a=i.statePath,o=i.generationRoot,s=r.readSyncState(e,t);if(!s)return;let l=typeof s.bootstrap==`string`?s.bootstrap:void 0;if(l&&!_(o,l))throw Error(`Doom bootstrap must stay inside ${o}: ${l}`);let u;if(s.precompile!==void 0){let e=s.precompile;if(!m(e)||typeof e.version!=`number`||e.strategy!==`bundle`||typeof e.bootstrapEntry!=`string`||typeof e.bootstrapManifest!=`string`||!m(e.bundleManifests)||Object.values(e.bundleManifests).some(e=>typeof e!=`string`))throw Error(`Doom sync state at ${a} has an invalid precompile record`);u={version:e.version,strategy:e.strategy,bootstrapEntry:e.bootstrapEntry,bootstrapManifest:e.bootstrapManifest,bundleManifests:e.bundleManifests}}return{statePath:a,generatedDirectory:o,bootstrap:l,bundles:h(s.bundles),precompile:u}}function S(e,t){if(_(t,e))try{let n=JSON.parse(s.default.readFileSync(e,`utf8`));if(!m(n)||typeof n.output!=`string`||!Array.isArray(n.artifacts)||n.artifacts.some(e=>typeof e!=`string`)||!Array.isArray(n.entries)||n.entries.some(e=>typeof e!=`string`)||!Array.isArray(n.inputs))return;let r=[];for(let e of n.inputs){let t=f(e);if(t===void 0)return;r.push(t)}let i=n.artifacts,a=n.entries;return!_(t,n.output)||i.some(e=>!_(t,e))?void 0:{output:n.output,artifacts:i,entries:a,inputs:r}}catch{return}}function C(e){return!s.default.existsSync(e.output)||e.artifacts.some(e=>!s.default.existsSync(e))?!1:p(e.inputs)}function w(e,t){if(!e.bootstrap||!e.precompile||e.precompile.version!==5||g(e.precompile.bootstrapEntry)!==g(t))return;let n=S(e.precompile.bootstrapManifest,e.generatedDirectory);if(!(!n||!C(n)||n.output!==e.bootstrap||n.entries.length!==1||g(n.entries[0]??``)!==g(t)))return n}function T(e,t,n=c.default.homedir()){let r=x(e,n),i=t??v();return r?{bootstrap:r.bootstrap,fresh:w(r,i)!==void 0}:{bootstrap:void 0,fresh:!1}}function E(e,t,n=c.default.homedir()){let r=x(e,n),i=r?.bundles[t],a=r?.precompile?.bundleManifests[t];if(!r||r.precompile?.version!==5||!i||!a)return{bundle:i,fresh:!1};let o=S(a,r.generatedDirectory);return{bundle:i,fresh:!!(o&&o.output===i&&C(o))}}function D(e,t,n=c.default.homedir()){let r=x(e,n),i=t??v();if(!r?.bootstrap||!r.precompile||!w(r,i))return{bootstrap:r?.bootstrap,fresh:!1};let a=Object.values(r.precompile.bundleManifests);if(new Set(a).size!==a.length)return{bootstrap:r.bootstrap,fresh:!1};let o=Object.entries(r.bundles).every(([e,t])=>{let n=r.precompile?.bundleManifests[e];if(!n)return!1;let i=S(n,r.generatedDirectory);return!!(i&&i.output===t&&C(i))});return{bootstrap:r.bootstrap,fresh:o}}exports.findSyncedRoot=b,exports.fingerprintInput=d,exports.inputsAreFresh=p,exports.parseInputFingerprint=f,exports.readBootstrapStatus=D,exports.readBundleStatus=E,exports.readStartupBootstrapStatus=T;
2
2
  //# sourceMappingURL=bootstrapLocator.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrapLocator.cjs","names":["crypto","fs","path","fileURLToPath","readSyncRegistration","os","findRepositoryRoot","readSyncState"],"sources":["../../../src/adapters/bootstrapLocator.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { findRepositoryRoot } from './repository/repository.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\nimport { readSyncState } from './syncState.ts';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from './syncStateContract.ts';\n\n/**\n * One recorded build input: the stat pair for speed, the digest for truth.\n *\n * Comparing only `size` and `mtimeMs` makes every rebuild look like a change,\n * including one that restored byte-identical output from a build cache, and a\n * sync triggered that way republishes a generation nobody asked for. Comparing\n * content alone is correct but reads every input on every check, and the\n * cockpit polls this. Recording both lets the stat pair answer the common\n * \"nothing was touched\" case without opening a file, while the digest stays the\n * authority for anything whose stat moved.\n */\nexport interface InputFingerprint {\n path: string;\n size: number;\n mtimeMs: number;\n sha256: string;\n}\n\n/**\n * Verdicts already reached, so a rebuild that rewrote every input without\n * changing it does not re-hash the whole graph on every poll.\n *\n * Keyed by the stats actually found on disk, so it can never answer for a set\n * it did not see. Bounded by the compiler manifests in one composition, which\n * is single digits.\n */\nconst freshnessVerdicts = new Map<string, boolean>();\n\nfunction digestOf(filePath: string): string | undefined {\n try {\n return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');\n } catch {\n // Unreadable is indistinguishable from absent here: either way the recorded\n // digest cannot be confirmed, so the caller must treat the build as stale.\n return undefined;\n }\n}\n\n/** Records one build input, or undefined when it is not a readable regular file. */\nexport function fingerprintInput(target: string): InputFingerprint | undefined {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(target);\n } catch {\n // A path that vanished between discovery and recording is simply not an\n // input; the compiler records the set it could read.\n return undefined;\n }\n if (!stat.isFile()) return undefined;\n const sha256 = digestOf(target);\n if (sha256 === undefined) return undefined;\n return { path: target, size: stat.size, mtimeMs: stat.mtimeMs, sha256 };\n}\n\n/** Parses one recorded input, rejecting a record written before digests existed. */\nexport function parseInputFingerprint(value: unknown): InputFingerprint | undefined {\n if (!isRecord(value)) return undefined;\n if (\n typeof value.path !== 'string' ||\n typeof value.size !== 'number' ||\n typeof value.mtimeMs !== 'number' ||\n typeof value.sha256 !== 'string'\n ) {\n return undefined;\n }\n return { path: value.path, size: value.size, mtimeMs: value.mtimeMs, sha256: value.sha256 };\n}\n\n/** Whether every recorded input still holds the bytes it was recorded with. */\nexport function inputsAreFresh(inputs: readonly InputFingerprint[]): boolean {\n const moved: InputFingerprint[] = [];\n const key = crypto.createHash('sha256');\n for (const input of inputs) {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(input.path);\n } catch {\n // A recorded input that is gone cannot be confirmed, and no digest will\n // bring it back; the build it describes is stale.\n return false;\n }\n if (!stat.isFile()) return false;\n key.update(`${input.path} ${String(stat.size)} ${String(stat.mtimeMs)} `);\n if (stat.size !== input.size || stat.mtimeMs !== input.mtimeMs) moved.push(input);\n }\n if (moved.length === 0) return true;\n\n const cacheKey = key.digest('hex');\n const remembered = freshnessVerdicts.get(cacheKey);\n if (remembered !== undefined) return remembered;\n const fresh = moved.every((input) => digestOf(input.path) === input.sha256);\n freshnessVerdicts.set(cacheKey, fresh);\n return fresh;\n}\n\n/** Test seam: the memo is process-local and must not leak between cases. */\nexport function resetInputFreshnessCache(): void {\n freshnessVerdicts.clear();\n}\n\ninterface CompilerManifest {\n output: string;\n artifacts: string[];\n entries: string[];\n inputs: InputFingerprint[];\n}\n\ninterface BootstrapState {\n statePath: string;\n generatedDirectory: string;\n bootstrap?: string;\n bundles: Record<string, string>;\n precompile?: {\n version: number;\n strategy: 'bundle';\n bootstrapEntry: string;\n bootstrapManifest: string;\n bundleManifests: Record<string, string>;\n };\n}\n\nexport interface BootstrapStatus {\n bootstrap?: string;\n fresh: boolean;\n}\n\nexport interface BundleStatus {\n bundle?: string;\n fresh: boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {};\n return Object.fromEntries(\n Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),\n );\n}\n\nfunction canonicalPath(target: string): string {\n const absolute = path.resolve(target);\n try {\n return fs.realpathSync(absolute);\n } catch {\n const parent = path.dirname(absolute);\n if (parent === absolute) return absolute;\n return path.join(canonicalPath(parent), path.basename(absolute));\n }\n}\n\nfunction isInside(directory: string, target: string): boolean {\n const relative = path.relative(canonicalPath(directory), canonicalPath(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction packagedDoomEntry(moduleUrl: string = import.meta.url): string {\n const extension = moduleUrl.endsWith('.ts') ? 'ts' : 'mjs';\n const sourceRoot = path.dirname(path.dirname(fileURLToPath(moduleUrl)));\n return path.join(sourceRoot, 'extensions', 'entries', `doom.${extension}`);\n}\n\nfunction locationHasState(repoRoot: string, homeDirectory: string): boolean {\n return readSyncRegistration(repoRoot, homeDirectory) !== undefined;\n}\n\n/** Finds the nearest configured repository with generated Doom sync state. */\nexport function findSyncedRoot(cwd: string, homeDirectory: string = os.homedir()): string | undefined {\n try {\n if (locationHasState(cwd, homeDirectory)) return canonicalPath(cwd);\n const root = findRepositoryRoot(cwd);\n return locationHasState(root, homeDirectory) ? root : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction readBootstrapState(repoRoot: string, homeDirectory: string = os.homedir()): BootstrapState | undefined {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n if (!registration) return undefined;\n const statePath = registration.statePath;\n const generatedDirectory = registration.generationRoot;\n const state = readSyncState(repoRoot, homeDirectory);\n if (!state) return undefined;\n const bootstrap = typeof state.bootstrap === 'string' ? state.bootstrap : undefined;\n if (bootstrap && !isInside(generatedDirectory, bootstrap)) {\n throw new Error(`Doom bootstrap must stay inside ${generatedDirectory}: ${bootstrap}`);\n }\n\n let precompile: BootstrapState['precompile'];\n if (state.precompile !== undefined) {\n const value = state.precompile;\n if (\n !isRecord(value) ||\n typeof value.version !== 'number' ||\n value.strategy !== BUNDLED_PRECOMPILE_STRATEGY ||\n typeof value.bootstrapEntry !== 'string' ||\n typeof value.bootstrapManifest !== 'string' ||\n !isRecord(value.bundleManifests) ||\n Object.values(value.bundleManifests).some((manifest) => typeof manifest !== 'string')\n ) {\n throw new Error(`Doom sync state at ${statePath} has an invalid precompile record`);\n }\n precompile = {\n version: value.version,\n strategy: value.strategy,\n bootstrapEntry: value.bootstrapEntry,\n bootstrapManifest: value.bootstrapManifest,\n bundleManifests: value.bundleManifests as Record<string, string>,\n };\n }\n\n return {\n statePath,\n generatedDirectory,\n bootstrap,\n bundles: stringRecord(state.bundles),\n precompile,\n };\n}\n\nfunction readCompilerManifest(manifestPath: string, generatedDirectory: string): CompilerManifest | undefined {\n if (!isInside(generatedDirectory, manifestPath)) return undefined;\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown;\n if (\n !isRecord(parsed) ||\n typeof parsed.output !== 'string' ||\n !Array.isArray(parsed.artifacts) ||\n parsed.artifacts.some((artifact) => typeof artifact !== 'string') ||\n !Array.isArray(parsed.entries) ||\n parsed.entries.some((entry) => typeof entry !== 'string') ||\n !Array.isArray(parsed.inputs)\n ) {\n return undefined;\n }\n const inputs: InputFingerprint[] = [];\n for (const input of parsed.inputs) {\n // A manifest written before digests were recorded cannot be confirmed by\n // content, so it reads as absent and the next sync rewrites it.\n const fingerprint = parseInputFingerprint(input);\n if (fingerprint === undefined) return undefined;\n inputs.push(fingerprint);\n }\n const artifacts = parsed.artifacts as string[];\n const entries = parsed.entries as string[];\n if (\n !isInside(generatedDirectory, parsed.output) ||\n artifacts.some((artifact) => !isInside(generatedDirectory, artifact))\n ) {\n return undefined;\n }\n return { output: parsed.output, artifacts, entries, inputs };\n } catch {\n return undefined;\n }\n}\n\nfunction compilerManifestIsFresh(manifest: CompilerManifest): boolean {\n if (!fs.existsSync(manifest.output) || manifest.artifacts.some((artifact) => !fs.existsSync(artifact))) return false;\n return inputsAreFresh(manifest.inputs);\n}\n\nfunction freshBootstrapRecord(state: BootstrapState, expectedBootstrapEntry: string): CompilerManifest | undefined {\n if (!state.bootstrap || !state.precompile) return undefined;\n if (\n state.precompile.version !== PRECOMPILE_STATE_VERSION ||\n canonicalPath(state.precompile.bootstrapEntry) !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n const record = readCompilerManifest(state.precompile.bootstrapManifest, state.generatedDirectory);\n if (\n !record ||\n !compilerManifestIsFresh(record) ||\n record.output !== state.bootstrap ||\n record.entries.length !== 1 ||\n canonicalPath(record.entries[0] ?? '') !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n return record;\n}\n\n/** Validates only the bootstrap graph needed before the generated bootstrap is imported. */\nexport function readStartupBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state) return { bootstrap: undefined, fresh: false };\n return {\n bootstrap: state.bootstrap,\n fresh: freshBootstrapRecord(state, expected) !== undefined,\n };\n}\n\n/** Validates one selected composition without touching any inactive bundle inputs. */\nexport function readBundleStatus(\n repoRoot: string,\n compositionFingerprint: string,\n homeDirectory: string = os.homedir(),\n): BundleStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const bundle = state?.bundles[compositionFingerprint];\n const manifestPath = state?.precompile?.bundleManifests[compositionFingerprint];\n if (!state || state.precompile?.version !== PRECOMPILE_STATE_VERSION || !bundle || !manifestPath) {\n return { bundle, fresh: false };\n }\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return {\n bundle,\n fresh: Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest)),\n };\n}\n\n/** Validates the bootstrap and every bundle for `doompi sync --check` diagnostics. */\nexport function readBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state?.bootstrap || !state.precompile || !freshBootstrapRecord(state, expected)) {\n return { bootstrap: state?.bootstrap, fresh: false };\n }\n const manifests = Object.values(state.precompile.bundleManifests);\n if (new Set(manifests).size !== manifests.length) return { bootstrap: state.bootstrap, fresh: false };\n const fresh = Object.entries(state.bundles).every(([fingerprint, bundle]) => {\n const manifestPath = state.precompile?.bundleManifests[fingerprint];\n if (!manifestPath) return false;\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest));\n });\n return { bootstrap: state.bootstrap, fresh };\n}\n\n/** Reads only the generated bootstrap pointer while retaining state validation. */\nexport function readBootstrapPointer(repoRoot: string, homeDirectory: string = os.homedir()): string | undefined {\n return readBootstrapState(repoRoot, homeDirectory)?.bootstrap;\n}\n"],"mappings":"+YAoCA,MAAM,EAAoB,IAAI,IAE9B,SAAS,EAAS,EAAsC,CACtD,GAAI,CACF,OAAOA,EAAAA,QAAO,WAAW,QAAQ,CAAC,CAAC,OAAOC,EAAAA,QAAG,aAAa,CAAQ,CAAC,CAAC,CAAC,OAAO,KAAK,CACnF,MAAQ,CAGN,MACF,CACF,CAGA,SAAgB,EAAiB,EAA8C,CAC7E,IAAI,EACJ,GAAI,CACF,EAAOA,EAAAA,QAAG,SAAS,CAAM,CAC3B,MAAQ,CAGN,MACF,CACA,GAAI,CAAC,EAAK,OAAO,EAAG,OACpB,IAAM,EAAS,EAAS,CAAM,EAC1B,OAAW,IAAA,GACf,MAAO,CAAE,KAAM,EAAQ,KAAM,EAAK,KAAM,QAAS,EAAK,QAAS,QAAO,CACxE,CAGA,SAAgB,EAAsB,EAA8C,CAC7E,KAAS,CAAK,GAEjB,OAAO,EAAM,MAAS,UACtB,OAAO,EAAM,MAAS,UACtB,OAAO,EAAM,SAAY,UACzB,OAAO,EAAM,QAAW,SAI1B,MAAO,CAAE,KAAM,EAAM,KAAM,KAAM,EAAM,KAAM,QAAS,EAAM,QAAS,OAAQ,EAAM,MAAO,CAC5F,CAGA,SAAgB,EAAe,EAA8C,CAC3E,IAAM,EAA4B,CAAC,EAC7B,EAAMD,EAAAA,QAAO,WAAW,QAAQ,EACtC,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAI,EACJ,GAAI,CACF,EAAOC,EAAAA,QAAG,SAAS,EAAM,IAAI,CAC/B,MAAQ,CAGN,MAAO,EACT,CACA,GAAI,CAAC,EAAK,OAAO,EAAG,MAAO,GAC3B,EAAI,OAAO,GAAG,EAAM,KAAK,GAAG,OAAO,EAAK,IAAI,EAAE,GAAG,OAAO,EAAK,OAAO,EAAE,EAAE,GACpE,EAAK,OAAS,EAAM,MAAQ,EAAK,UAAY,EAAM,UAAS,EAAM,KAAK,CAAK,CAClF,CACA,GAAI,EAAM,SAAW,EAAG,MAAO,GAE/B,IAAM,EAAW,EAAI,OAAO,KAAK,EAC3B,EAAa,EAAkB,IAAI,CAAQ,EACjD,GAAI,IAAe,IAAA,GAAW,OAAO,EACrC,IAAM,EAAQ,EAAM,MAAO,GAAU,EAAS,EAAM,IAAI,IAAM,EAAM,MAAM,EAE1E,OADA,EAAkB,IAAI,EAAU,CAAK,EAC9B,CACT,CAsCA,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,SAAS,EAAa,EAAwC,CAE5D,OADK,EAAS,CAAK,EACZ,OAAO,YACZ,OAAO,QAAQ,CAAK,CAAC,CAAC,OAAQ,GAAqC,OAAO,EAAM,IAAO,QAAQ,CACjG,EAH6B,CAAC,CAIhC,CAEA,SAAS,EAAc,EAAwB,CAC7C,IAAM,EAAWC,EAAAA,QAAK,QAAQ,CAAM,EACpC,GAAI,CACF,OAAOD,EAAAA,QAAG,aAAa,CAAQ,CACjC,MAAQ,CACN,IAAM,EAASC,EAAAA,QAAK,QAAQ,CAAQ,EAEpC,OADI,IAAW,EAAiB,EACzBA,EAAAA,QAAK,KAAK,EAAc,CAAM,EAAGA,EAAAA,QAAK,SAAS,CAAQ,CAAC,CACjE,CACF,CAEA,SAAS,EAAS,EAAmB,EAAyB,CAC5D,IAAM,EAAWA,EAAAA,QAAK,SAAS,EAAc,CAAS,EAAG,EAAc,CAAM,CAAC,EAC9E,OAAO,IAAa,IAAO,CAAC,EAAS,WAAW,IAAI,GAAK,CAACA,EAAAA,QAAK,WAAW,CAAQ,CACpF,CAEA,SAAS,EAAkB,EAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,KAA6C,CACtE,IAAM,EAAY,EAAU,SAAS,KAAK,EAAI,KAAO,MAC/C,EAAaA,EAAAA,QAAK,QAAQA,EAAAA,QAAK,SAAA,EAAQC,EAAAA,cAAAA,CAAc,CAAS,CAAC,CAAC,EACtE,OAAOD,EAAAA,QAAK,KAAK,EAAY,aAAc,UAAW,QAAQ,GAAW,CAC3E,CAEA,SAAS,EAAiB,EAAkB,EAAgC,CAC1E,OAAOE,EAAAA,qBAAqB,EAAU,CAAa,IAAM,IAAA,EAC3D,CAGA,SAAgB,EAAe,EAAa,EAAwBC,EAAAA,QAAG,QAAQ,EAAuB,CACpG,GAAI,CACF,GAAI,EAAiB,EAAK,CAAa,EAAG,OAAO,EAAc,CAAG,EAClE,IAAM,EAAOC,EAAAA,mBAAmB,CAAG,EACnC,OAAO,EAAiB,EAAM,CAAa,EAAI,EAAO,IAAA,EACxD,MAAQ,CACN,MACF,CACF,CAEA,SAAS,EAAmB,EAAkB,EAAwBD,EAAAA,QAAG,QAAQ,EAA+B,CAC9G,IAAM,EAAeD,EAAAA,qBAAqB,EAAU,CAAa,EACjE,GAAI,CAAC,EAAc,OACnB,IAAM,EAAY,EAAa,UACzB,EAAqB,EAAa,eAClC,EAAQG,EAAAA,cAAc,EAAU,CAAa,EACnD,GAAI,CAAC,EAAO,OACZ,IAAM,EAAY,OAAO,EAAM,WAAc,SAAW,EAAM,UAAY,IAAA,GAC1E,GAAI,GAAa,CAAC,EAAS,EAAoB,CAAS,EACtD,MAAU,MAAM,mCAAmC,EAAmB,IAAI,GAAW,EAGvF,IAAI,EACJ,GAAI,EAAM,aAAe,IAAA,GAAW,CAClC,IAAM,EAAQ,EAAM,WACpB,GACE,CAAC,EAAS,CAAK,GACf,OAAO,EAAM,SAAY,UACzB,EAAM,WAAA,UACN,OAAO,EAAM,gBAAmB,UAChC,OAAO,EAAM,mBAAsB,UACnC,CAAC,EAAS,EAAM,eAAe,GAC/B,OAAO,OAAO,EAAM,eAAe,CAAC,CAAC,KAAM,GAAa,OAAO,GAAa,QAAQ,EAEpF,MAAU,MAAM,sBAAsB,EAAU,kCAAkC,EAEpF,EAAa,CACX,QAAS,EAAM,QACf,SAAU,EAAM,SAChB,eAAgB,EAAM,eACtB,kBAAmB,EAAM,kBACzB,gBAAiB,EAAM,eACzB,CACF,CAEA,MAAO,CACL,YACA,qBACA,YACA,QAAS,EAAa,EAAM,OAAO,EACnC,YACF,CACF,CAEA,SAAS,EAAqB,EAAsB,EAA0D,CACvG,KAAS,EAAoB,CAAY,EAC9C,GAAI,CACF,IAAM,EAAS,KAAK,MAAMN,EAAAA,QAAG,aAAa,EAAc,MAAM,CAAC,EAC/D,GACE,CAAC,EAAS,CAAM,GAChB,OAAO,EAAO,QAAW,UACzB,CAAC,MAAM,QAAQ,EAAO,SAAS,GAC/B,EAAO,UAAU,KAAM,GAAa,OAAO,GAAa,QAAQ,GAChE,CAAC,MAAM,QAAQ,EAAO,OAAO,GAC7B,EAAO,QAAQ,KAAM,GAAU,OAAO,GAAU,QAAQ,GACxD,CAAC,MAAM,QAAQ,EAAO,MAAM,EAE5B,OAEF,IAAM,EAA6B,CAAC,EACpC,IAAK,IAAM,KAAS,EAAO,OAAQ,CAGjC,IAAM,EAAc,EAAsB,CAAK,EAC/C,GAAI,IAAgB,IAAA,GAAW,OAC/B,EAAO,KAAK,CAAW,CACzB,CACA,IAAM,EAAY,EAAO,UACnB,EAAU,EAAO,QAOvB,MALE,CAAC,EAAS,EAAoB,EAAO,MAAM,GAC3C,EAAU,KAAM,GAAa,CAAC,EAAS,EAAoB,CAAQ,CAAC,EAEpE,OAEK,CAAE,OAAQ,EAAO,OAAQ,YAAW,UAAS,QAAO,CAC7D,MAAQ,CACN,MACF,CACF,CAEA,SAAS,EAAwB,EAAqC,CAEpE,MADI,CAACA,EAAAA,QAAG,WAAW,EAAS,MAAM,GAAK,EAAS,UAAU,KAAM,GAAa,CAACA,EAAAA,QAAG,WAAW,CAAQ,CAAC,EAAU,GACxG,EAAe,EAAS,MAAM,CACvC,CAEA,SAAS,EAAqB,EAAuB,EAA8D,CAEjH,GADI,CAAC,EAAM,WAAa,CAAC,EAAM,YAE7B,EAAM,WAAW,UAAA,GACjB,EAAc,EAAM,WAAW,cAAc,IAAM,EAAc,CAAsB,EAEvF,OAEF,IAAM,EAAS,EAAqB,EAAM,WAAW,kBAAmB,EAAM,kBAAkB,EAE9F,MAAC,GACD,CAAC,EAAwB,CAAM,GAC/B,EAAO,SAAW,EAAM,WACxB,EAAO,QAAQ,SAAW,GAC1B,EAAc,EAAO,QAAQ,IAAM,EAAE,IAAM,EAAc,CAAsB,GAIjF,OAAO,CACT,CAGA,SAAgB,EACd,EACA,EACA,EAAwBI,EAAAA,QAAG,QAAQ,EAClB,CACjB,IAAM,EAAQ,EAAmB,EAAU,CAAa,EAClD,EAAW,GAA0B,EAAkB,EAE7D,OADK,EACE,CACL,UAAW,EAAM,UACjB,MAAO,EAAqB,EAAO,CAAQ,IAAM,IAAA,EACnD,EAJmB,CAAE,UAAW,IAAA,GAAW,MAAO,EAAM,CAK1D,CAGA,SAAgB,EACd,EACA,EACA,EAAwBA,EAAAA,QAAG,QAAQ,EACrB,CACd,IAAM,EAAQ,EAAmB,EAAU,CAAa,EAClD,EAAS,GAAO,QAAQ,GACxB,EAAe,GAAO,YAAY,gBAAgB,GACxD,GAAI,CAAC,GAAS,EAAM,YAAY,UAAA,GAAwC,CAAC,GAAU,CAAC,EAClF,MAAO,CAAE,SAAQ,MAAO,EAAM,EAEhC,IAAM,EAAW,EAAqB,EAAc,EAAM,kBAAkB,EAC5E,MAAO,CACL,SACA,MAAO,GAAQ,GAAY,EAAS,SAAW,GAAU,EAAwB,CAAQ,EAC3F,CACF,CAGA,SAAgB,EACd,EACA,EACA,EAAwBA,EAAAA,QAAG,QAAQ,EAClB,CACjB,IAAM,EAAQ,EAAmB,EAAU,CAAa,EAClD,EAAW,GAA0B,EAAkB,EAC7D,GAAI,CAAC,GAAO,WAAa,CAAC,EAAM,YAAc,CAAC,EAAqB,EAAO,CAAQ,EACjF,MAAO,CAAE,UAAW,GAAO,UAAW,MAAO,EAAM,EAErD,IAAM,EAAY,OAAO,OAAO,EAAM,WAAW,eAAe,EAChE,GAAI,IAAI,IAAI,CAAS,CAAC,CAAC,OAAS,EAAU,OAAQ,MAAO,CAAE,UAAW,EAAM,UAAW,MAAO,EAAM,EACpG,IAAM,EAAQ,OAAO,QAAQ,EAAM,OAAO,CAAC,CAAC,OAAO,CAAC,EAAa,KAAY,CAC3E,IAAM,EAAe,EAAM,YAAY,gBAAgB,GACvD,GAAI,CAAC,EAAc,MAAO,GAC1B,IAAM,EAAW,EAAqB,EAAc,EAAM,kBAAkB,EAC5E,MAAO,GAAQ,GAAY,EAAS,SAAW,GAAU,EAAwB,CAAQ,EAC3F,CAAC,EACD,MAAO,CAAE,UAAW,EAAM,UAAW,OAAM,CAC7C"}
1
+ {"version":3,"file":"bootstrapLocator.cjs","names":["crypto","fs","path","fileURLToPath","readSyncRegistration","os","resolveDoomConfigurationRoot","readSyncState"],"sources":["../../../src/adapters/bootstrapLocator.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { resolveDoomConfigurationRoot } from './repository/repository.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\nimport { readSyncState } from './syncState.ts';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from './syncStateContract.ts';\n\n/**\n * One recorded build input: the stat pair for speed, the digest for truth.\n *\n * Comparing only `size` and `mtimeMs` makes every rebuild look like a change,\n * including one that restored byte-identical output from a build cache, and a\n * sync triggered that way republishes a generation nobody asked for. Comparing\n * content alone is correct but reads every input on every check, and the\n * cockpit polls this. Recording both lets the stat pair answer the common\n * \"nothing was touched\" case without opening a file, while the digest stays the\n * authority for anything whose stat moved.\n */\nexport interface InputFingerprint {\n path: string;\n size: number;\n mtimeMs: number;\n sha256: string;\n}\n\n/**\n * Verdicts already reached, so a rebuild that rewrote every input without\n * changing it does not re-hash the whole graph on every poll.\n *\n * Keyed by the stats actually found on disk, so it can never answer for a set\n * it did not see. Bounded by the compiler manifests in one composition, which\n * is single digits.\n */\nconst freshnessVerdicts = new Map<string, boolean>();\n\nfunction digestOf(filePath: string): string | undefined {\n try {\n return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');\n } catch {\n // Unreadable is indistinguishable from absent here: either way the recorded\n // digest cannot be confirmed, so the caller must treat the build as stale.\n return undefined;\n }\n}\n\n/** Records one build input, or undefined when it is not a readable regular file. */\nexport function fingerprintInput(target: string): InputFingerprint | undefined {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(target);\n } catch {\n // A path that vanished between discovery and recording is simply not an\n // input; the compiler records the set it could read.\n return undefined;\n }\n if (!stat.isFile()) return undefined;\n const sha256 = digestOf(target);\n if (sha256 === undefined) return undefined;\n return { path: target, size: stat.size, mtimeMs: stat.mtimeMs, sha256 };\n}\n\n/** Parses one recorded input, rejecting a record written before digests existed. */\nexport function parseInputFingerprint(value: unknown): InputFingerprint | undefined {\n if (!isRecord(value)) return undefined;\n if (\n typeof value.path !== 'string' ||\n typeof value.size !== 'number' ||\n typeof value.mtimeMs !== 'number' ||\n typeof value.sha256 !== 'string'\n ) {\n return undefined;\n }\n return { path: value.path, size: value.size, mtimeMs: value.mtimeMs, sha256: value.sha256 };\n}\n\n/** Whether every recorded input still holds the bytes it was recorded with. */\nexport function inputsAreFresh(inputs: readonly InputFingerprint[]): boolean {\n const moved: InputFingerprint[] = [];\n const key = crypto.createHash('sha256');\n for (const input of inputs) {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(input.path);\n } catch {\n // A recorded input that is gone cannot be confirmed, and no digest will\n // bring it back; the build it describes is stale.\n return false;\n }\n if (!stat.isFile()) return false;\n key.update(`${input.path} ${String(stat.size)} ${String(stat.mtimeMs)} `);\n if (stat.size !== input.size || stat.mtimeMs !== input.mtimeMs) moved.push(input);\n }\n if (moved.length === 0) return true;\n\n const cacheKey = key.digest('hex');\n const remembered = freshnessVerdicts.get(cacheKey);\n if (remembered !== undefined) return remembered;\n const fresh = moved.every((input) => digestOf(input.path) === input.sha256);\n freshnessVerdicts.set(cacheKey, fresh);\n return fresh;\n}\n\n/** Test seam: the memo is process-local and must not leak between cases. */\nexport function resetInputFreshnessCache(): void {\n freshnessVerdicts.clear();\n}\n\ninterface CompilerManifest {\n output: string;\n artifacts: string[];\n entries: string[];\n inputs: InputFingerprint[];\n}\n\ninterface BootstrapState {\n statePath: string;\n generatedDirectory: string;\n bootstrap?: string;\n bundles: Record<string, string>;\n precompile?: {\n version: number;\n strategy: 'bundle';\n bootstrapEntry: string;\n bootstrapManifest: string;\n bundleManifests: Record<string, string>;\n };\n}\n\nexport interface BootstrapStatus {\n bootstrap?: string;\n fresh: boolean;\n}\n\nexport interface BundleStatus {\n bundle?: string;\n fresh: boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {};\n return Object.fromEntries(\n Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),\n );\n}\n\nfunction canonicalPath(target: string): string {\n const absolute = path.resolve(target);\n try {\n return fs.realpathSync(absolute);\n } catch {\n const parent = path.dirname(absolute);\n if (parent === absolute) return absolute;\n return path.join(canonicalPath(parent), path.basename(absolute));\n }\n}\n\nfunction isInside(directory: string, target: string): boolean {\n const relative = path.relative(canonicalPath(directory), canonicalPath(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction packagedDoomEntry(moduleUrl: string = import.meta.url): string {\n const extension = moduleUrl.endsWith('.ts') ? 'ts' : 'mjs';\n const sourceRoot = path.dirname(path.dirname(fileURLToPath(moduleUrl)));\n return path.join(sourceRoot, 'extensions', 'entries', `doom.${extension}`);\n}\n\nfunction locationHasState(repoRoot: string, homeDirectory: string): boolean {\n return readSyncRegistration(repoRoot, homeDirectory) !== undefined;\n}\n\n/** Finds the nearest configured repository with generated Doom sync state. */\nexport function findSyncedRoot(cwd: string, homeDirectory: string = os.homedir()): string | undefined {\n if (locationHasState(cwd, homeDirectory)) return canonicalPath(cwd);\n const root = resolveDoomConfigurationRoot(cwd, homeDirectory);\n return locationHasState(root, homeDirectory) ? canonicalPath(root) : undefined;\n}\n\nfunction readBootstrapState(repoRoot: string, homeDirectory: string = os.homedir()): BootstrapState | undefined {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n if (!registration) return undefined;\n const statePath = registration.statePath;\n const generatedDirectory = registration.generationRoot;\n const state = readSyncState(repoRoot, homeDirectory);\n if (!state) return undefined;\n const bootstrap = typeof state.bootstrap === 'string' ? state.bootstrap : undefined;\n if (bootstrap && !isInside(generatedDirectory, bootstrap)) {\n throw new Error(`Doom bootstrap must stay inside ${generatedDirectory}: ${bootstrap}`);\n }\n\n let precompile: BootstrapState['precompile'];\n if (state.precompile !== undefined) {\n const value = state.precompile;\n if (\n !isRecord(value) ||\n typeof value.version !== 'number' ||\n value.strategy !== BUNDLED_PRECOMPILE_STRATEGY ||\n typeof value.bootstrapEntry !== 'string' ||\n typeof value.bootstrapManifest !== 'string' ||\n !isRecord(value.bundleManifests) ||\n Object.values(value.bundleManifests).some((manifest) => typeof manifest !== 'string')\n ) {\n throw new Error(`Doom sync state at ${statePath} has an invalid precompile record`);\n }\n precompile = {\n version: value.version,\n strategy: value.strategy,\n bootstrapEntry: value.bootstrapEntry,\n bootstrapManifest: value.bootstrapManifest,\n bundleManifests: value.bundleManifests as Record<string, string>,\n };\n }\n\n return {\n statePath,\n generatedDirectory,\n bootstrap,\n bundles: stringRecord(state.bundles),\n precompile,\n };\n}\n\nfunction readCompilerManifest(manifestPath: string, generatedDirectory: string): CompilerManifest | undefined {\n if (!isInside(generatedDirectory, manifestPath)) return undefined;\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown;\n if (\n !isRecord(parsed) ||\n typeof parsed.output !== 'string' ||\n !Array.isArray(parsed.artifacts) ||\n parsed.artifacts.some((artifact) => typeof artifact !== 'string') ||\n !Array.isArray(parsed.entries) ||\n parsed.entries.some((entry) => typeof entry !== 'string') ||\n !Array.isArray(parsed.inputs)\n ) {\n return undefined;\n }\n const inputs: InputFingerprint[] = [];\n for (const input of parsed.inputs) {\n // A manifest written before digests were recorded cannot be confirmed by\n // content, so it reads as absent and the next sync rewrites it.\n const fingerprint = parseInputFingerprint(input);\n if (fingerprint === undefined) return undefined;\n inputs.push(fingerprint);\n }\n const artifacts = parsed.artifacts as string[];\n const entries = parsed.entries as string[];\n if (\n !isInside(generatedDirectory, parsed.output) ||\n artifacts.some((artifact) => !isInside(generatedDirectory, artifact))\n ) {\n return undefined;\n }\n return { output: parsed.output, artifacts, entries, inputs };\n } catch {\n return undefined;\n }\n}\n\nfunction compilerManifestIsFresh(manifest: CompilerManifest): boolean {\n if (!fs.existsSync(manifest.output) || manifest.artifacts.some((artifact) => !fs.existsSync(artifact))) return false;\n return inputsAreFresh(manifest.inputs);\n}\n\nfunction freshBootstrapRecord(state: BootstrapState, expectedBootstrapEntry: string): CompilerManifest | undefined {\n if (!state.bootstrap || !state.precompile) return undefined;\n if (\n state.precompile.version !== PRECOMPILE_STATE_VERSION ||\n canonicalPath(state.precompile.bootstrapEntry) !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n const record = readCompilerManifest(state.precompile.bootstrapManifest, state.generatedDirectory);\n if (\n !record ||\n !compilerManifestIsFresh(record) ||\n record.output !== state.bootstrap ||\n record.entries.length !== 1 ||\n canonicalPath(record.entries[0] ?? '') !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n return record;\n}\n\n/** Validates only the bootstrap graph needed before the generated bootstrap is imported. */\nexport function readStartupBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state) return { bootstrap: undefined, fresh: false };\n return {\n bootstrap: state.bootstrap,\n fresh: freshBootstrapRecord(state, expected) !== undefined,\n };\n}\n\n/** Validates one selected composition without touching any inactive bundle inputs. */\nexport function readBundleStatus(\n repoRoot: string,\n compositionFingerprint: string,\n homeDirectory: string = os.homedir(),\n): BundleStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const bundle = state?.bundles[compositionFingerprint];\n const manifestPath = state?.precompile?.bundleManifests[compositionFingerprint];\n if (!state || state.precompile?.version !== PRECOMPILE_STATE_VERSION || !bundle || !manifestPath) {\n return { bundle, fresh: false };\n }\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return {\n bundle,\n fresh: Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest)),\n };\n}\n\n/** Validates the bootstrap and every bundle for `doompi sync --check` diagnostics. */\nexport function readBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state?.bootstrap || !state.precompile || !freshBootstrapRecord(state, expected)) {\n return { bootstrap: state?.bootstrap, fresh: false };\n }\n const manifests = Object.values(state.precompile.bundleManifests);\n if (new Set(manifests).size !== manifests.length) return { bootstrap: state.bootstrap, fresh: false };\n const fresh = Object.entries(state.bundles).every(([fingerprint, bundle]) => {\n const manifestPath = state.precompile?.bundleManifests[fingerprint];\n if (!manifestPath) return false;\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest));\n });\n return { bootstrap: state.bootstrap, fresh };\n}\n\n/** Reads only the generated bootstrap pointer while retaining state validation. */\nexport function readBootstrapPointer(repoRoot: string, homeDirectory: string = os.homedir()): string | undefined {\n return readBootstrapState(repoRoot, homeDirectory)?.bootstrap;\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{findRepositoryRoot as e}from"./repository/repository.mjs";import{readSyncRegistration as t}from"./syncRegistration.mjs";import"./syncStateContract.mjs";import{readSyncState as n}from"./syncState.mjs";import{fileURLToPath as r}from"node:url";import i from"node:path";import a from"node:crypto";import o from"node:fs";import s from"node:os";const c=new Map;function l(e){try{return a.createHash(`sha256`).update(o.readFileSync(e)).digest(`hex`)}catch{return}}function u(e){let t;try{t=o.statSync(e)}catch{return}if(!t.isFile())return;let n=l(e);if(n!==void 0)return{path:e,size:t.size,mtimeMs:t.mtimeMs,sha256:n}}function d(e){if(p(e)&&typeof e.path==`string`&&typeof e.size==`number`&&typeof e.mtimeMs==`number`&&typeof e.sha256==`string`)return{path:e.path,size:e.size,mtimeMs:e.mtimeMs,sha256:e.sha256}}function f(e){let t=[],n=a.createHash(`sha256`);for(let r of e){let e;try{e=o.statSync(r.path)}catch{return!1}if(!e.isFile())return!1;n.update(`${r.path} ${String(e.size)} ${String(e.mtimeMs)} `),(e.size!==r.size||e.mtimeMs!==r.mtimeMs)&&t.push(r)}if(t.length===0)return!0;let r=n.digest(`hex`),i=c.get(r);if(i!==void 0)return i;let s=t.every(e=>l(e.path)===e.sha256);return c.set(r,s),s}function p(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function m(e){return p(e)?Object.fromEntries(Object.entries(e).filter(e=>typeof e[1]==`string`)):{}}function h(e){let t=i.resolve(e);try{return o.realpathSync(t)}catch{let e=i.dirname(t);return e===t?t:i.join(h(e),i.basename(t))}}function g(e,t){let n=i.relative(h(e),h(t));return n===``||!n.startsWith(`..`)&&!i.isAbsolute(n)}function _(e=import.meta.url){let t=e.endsWith(`.ts`)?`ts`:`mjs`,n=i.dirname(i.dirname(r(e)));return i.join(n,`extensions`,`entries`,`doom.${t}`)}function v(e,n){return t(e,n)!==void 0}function y(t,n=s.homedir()){try{if(v(t,n))return h(t);let r=e(t);return v(r,n)?r:void 0}catch{return}}function b(e,r=s.homedir()){let i=t(e,r);if(!i)return;let a=i.statePath,o=i.generationRoot,c=n(e,r);if(!c)return;let l=typeof c.bootstrap==`string`?c.bootstrap:void 0;if(l&&!g(o,l))throw Error(`Doom bootstrap must stay inside ${o}: ${l}`);let u;if(c.precompile!==void 0){let e=c.precompile;if(!p(e)||typeof e.version!=`number`||e.strategy!==`bundle`||typeof e.bootstrapEntry!=`string`||typeof e.bootstrapManifest!=`string`||!p(e.bundleManifests)||Object.values(e.bundleManifests).some(e=>typeof e!=`string`))throw Error(`Doom sync state at ${a} has an invalid precompile record`);u={version:e.version,strategy:e.strategy,bootstrapEntry:e.bootstrapEntry,bootstrapManifest:e.bootstrapManifest,bundleManifests:e.bundleManifests}}return{statePath:a,generatedDirectory:o,bootstrap:l,bundles:m(c.bundles),precompile:u}}function x(e,t){if(g(t,e))try{let n=JSON.parse(o.readFileSync(e,`utf8`));if(!p(n)||typeof n.output!=`string`||!Array.isArray(n.artifacts)||n.artifacts.some(e=>typeof e!=`string`)||!Array.isArray(n.entries)||n.entries.some(e=>typeof e!=`string`)||!Array.isArray(n.inputs))return;let r=[];for(let e of n.inputs){let t=d(e);if(t===void 0)return;r.push(t)}let i=n.artifacts,a=n.entries;return!g(t,n.output)||i.some(e=>!g(t,e))?void 0:{output:n.output,artifacts:i,entries:a,inputs:r}}catch{return}}function S(e){return!o.existsSync(e.output)||e.artifacts.some(e=>!o.existsSync(e))?!1:f(e.inputs)}function C(e,t){if(!e.bootstrap||!e.precompile||e.precompile.version!==5||h(e.precompile.bootstrapEntry)!==h(t))return;let n=x(e.precompile.bootstrapManifest,e.generatedDirectory);if(!(!n||!S(n)||n.output!==e.bootstrap||n.entries.length!==1||h(n.entries[0]??``)!==h(t)))return n}function w(e,t,n=s.homedir()){let r=b(e,n),i=t??_();return r?{bootstrap:r.bootstrap,fresh:C(r,i)!==void 0}:{bootstrap:void 0,fresh:!1}}function T(e,t,n=s.homedir()){let r=b(e,n),i=r?.bundles[t],a=r?.precompile?.bundleManifests[t];if(!r||r.precompile?.version!==5||!i||!a)return{bundle:i,fresh:!1};let o=x(a,r.generatedDirectory);return{bundle:i,fresh:!!(o&&o.output===i&&S(o))}}function E(e,t,n=s.homedir()){let r=b(e,n),i=t??_();if(!r?.bootstrap||!r.precompile||!C(r,i))return{bootstrap:r?.bootstrap,fresh:!1};let a=Object.values(r.precompile.bundleManifests);if(new Set(a).size!==a.length)return{bootstrap:r.bootstrap,fresh:!1};let o=Object.entries(r.bundles).every(([e,t])=>{let n=r.precompile?.bundleManifests[e];if(!n)return!1;let i=x(n,r.generatedDirectory);return!!(i&&i.output===t&&S(i))});return{bootstrap:r.bootstrap,fresh:o}}export{y as findSyncedRoot,u as fingerprintInput,f as inputsAreFresh,d as parseInputFingerprint,E as readBootstrapStatus,T as readBundleStatus,w as readStartupBootstrapStatus};
1
+ import{resolveDoomConfigurationRoot as e}from"./repository/repository.mjs";import{readSyncRegistration as t}from"./syncRegistration.mjs";import"./syncStateContract.mjs";import{readSyncState as n}from"./syncState.mjs";import{fileURLToPath as r}from"node:url";import i from"node:path";import a from"node:crypto";import o from"node:fs";import s from"node:os";const c=new Map;function l(e){try{return a.createHash(`sha256`).update(o.readFileSync(e)).digest(`hex`)}catch{return}}function u(e){let t;try{t=o.statSync(e)}catch{return}if(!t.isFile())return;let n=l(e);if(n!==void 0)return{path:e,size:t.size,mtimeMs:t.mtimeMs,sha256:n}}function d(e){if(p(e)&&typeof e.path==`string`&&typeof e.size==`number`&&typeof e.mtimeMs==`number`&&typeof e.sha256==`string`)return{path:e.path,size:e.size,mtimeMs:e.mtimeMs,sha256:e.sha256}}function f(e){let t=[],n=a.createHash(`sha256`);for(let r of e){let e;try{e=o.statSync(r.path)}catch{return!1}if(!e.isFile())return!1;n.update(`${r.path} ${String(e.size)} ${String(e.mtimeMs)} `),(e.size!==r.size||e.mtimeMs!==r.mtimeMs)&&t.push(r)}if(t.length===0)return!0;let r=n.digest(`hex`),i=c.get(r);if(i!==void 0)return i;let s=t.every(e=>l(e.path)===e.sha256);return c.set(r,s),s}function p(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function m(e){return p(e)?Object.fromEntries(Object.entries(e).filter(e=>typeof e[1]==`string`)):{}}function h(e){let t=i.resolve(e);try{return o.realpathSync(t)}catch{let e=i.dirname(t);return e===t?t:i.join(h(e),i.basename(t))}}function g(e,t){let n=i.relative(h(e),h(t));return n===``||!n.startsWith(`..`)&&!i.isAbsolute(n)}function _(e=import.meta.url){let t=e.endsWith(`.ts`)?`ts`:`mjs`,n=i.dirname(i.dirname(r(e)));return i.join(n,`extensions`,`entries`,`doom.${t}`)}function v(e,n){return t(e,n)!==void 0}function y(t,n=s.homedir()){if(v(t,n))return h(t);let r=e(t,n);return v(r,n)?h(r):void 0}function b(e,r=s.homedir()){let i=t(e,r);if(!i)return;let a=i.statePath,o=i.generationRoot,c=n(e,r);if(!c)return;let l=typeof c.bootstrap==`string`?c.bootstrap:void 0;if(l&&!g(o,l))throw Error(`Doom bootstrap must stay inside ${o}: ${l}`);let u;if(c.precompile!==void 0){let e=c.precompile;if(!p(e)||typeof e.version!=`number`||e.strategy!==`bundle`||typeof e.bootstrapEntry!=`string`||typeof e.bootstrapManifest!=`string`||!p(e.bundleManifests)||Object.values(e.bundleManifests).some(e=>typeof e!=`string`))throw Error(`Doom sync state at ${a} has an invalid precompile record`);u={version:e.version,strategy:e.strategy,bootstrapEntry:e.bootstrapEntry,bootstrapManifest:e.bootstrapManifest,bundleManifests:e.bundleManifests}}return{statePath:a,generatedDirectory:o,bootstrap:l,bundles:m(c.bundles),precompile:u}}function x(e,t){if(g(t,e))try{let n=JSON.parse(o.readFileSync(e,`utf8`));if(!p(n)||typeof n.output!=`string`||!Array.isArray(n.artifacts)||n.artifacts.some(e=>typeof e!=`string`)||!Array.isArray(n.entries)||n.entries.some(e=>typeof e!=`string`)||!Array.isArray(n.inputs))return;let r=[];for(let e of n.inputs){let t=d(e);if(t===void 0)return;r.push(t)}let i=n.artifacts,a=n.entries;return!g(t,n.output)||i.some(e=>!g(t,e))?void 0:{output:n.output,artifacts:i,entries:a,inputs:r}}catch{return}}function S(e){return!o.existsSync(e.output)||e.artifacts.some(e=>!o.existsSync(e))?!1:f(e.inputs)}function C(e,t){if(!e.bootstrap||!e.precompile||e.precompile.version!==5||h(e.precompile.bootstrapEntry)!==h(t))return;let n=x(e.precompile.bootstrapManifest,e.generatedDirectory);if(!(!n||!S(n)||n.output!==e.bootstrap||n.entries.length!==1||h(n.entries[0]??``)!==h(t)))return n}function w(e,t,n=s.homedir()){let r=b(e,n),i=t??_();return r?{bootstrap:r.bootstrap,fresh:C(r,i)!==void 0}:{bootstrap:void 0,fresh:!1}}function T(e,t,n=s.homedir()){let r=b(e,n),i=r?.bundles[t],a=r?.precompile?.bundleManifests[t];if(!r||r.precompile?.version!==5||!i||!a)return{bundle:i,fresh:!1};let o=x(a,r.generatedDirectory);return{bundle:i,fresh:!!(o&&o.output===i&&S(o))}}function E(e,t,n=s.homedir()){let r=b(e,n),i=t??_();if(!r?.bootstrap||!r.precompile||!C(r,i))return{bootstrap:r?.bootstrap,fresh:!1};let a=Object.values(r.precompile.bundleManifests);if(new Set(a).size!==a.length)return{bootstrap:r.bootstrap,fresh:!1};let o=Object.entries(r.bundles).every(([e,t])=>{let n=r.precompile?.bundleManifests[e];if(!n)return!1;let i=x(n,r.generatedDirectory);return!!(i&&i.output===t&&S(i))});return{bootstrap:r.bootstrap,fresh:o}}export{y as findSyncedRoot,u as fingerprintInput,f as inputsAreFresh,d as parseInputFingerprint,E as readBootstrapStatus,T as readBundleStatus,w as readStartupBootstrapStatus};
2
2
  //# sourceMappingURL=bootstrapLocator.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrapLocator.mjs","names":[],"sources":["../../../src/adapters/bootstrapLocator.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { findRepositoryRoot } from './repository/repository.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\nimport { readSyncState } from './syncState.ts';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from './syncStateContract.ts';\n\n/**\n * One recorded build input: the stat pair for speed, the digest for truth.\n *\n * Comparing only `size` and `mtimeMs` makes every rebuild look like a change,\n * including one that restored byte-identical output from a build cache, and a\n * sync triggered that way republishes a generation nobody asked for. Comparing\n * content alone is correct but reads every input on every check, and the\n * cockpit polls this. Recording both lets the stat pair answer the common\n * \"nothing was touched\" case without opening a file, while the digest stays the\n * authority for anything whose stat moved.\n */\nexport interface InputFingerprint {\n path: string;\n size: number;\n mtimeMs: number;\n sha256: string;\n}\n\n/**\n * Verdicts already reached, so a rebuild that rewrote every input without\n * changing it does not re-hash the whole graph on every poll.\n *\n * Keyed by the stats actually found on disk, so it can never answer for a set\n * it did not see. Bounded by the compiler manifests in one composition, which\n * is single digits.\n */\nconst freshnessVerdicts = new Map<string, boolean>();\n\nfunction digestOf(filePath: string): string | undefined {\n try {\n return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');\n } catch {\n // Unreadable is indistinguishable from absent here: either way the recorded\n // digest cannot be confirmed, so the caller must treat the build as stale.\n return undefined;\n }\n}\n\n/** Records one build input, or undefined when it is not a readable regular file. */\nexport function fingerprintInput(target: string): InputFingerprint | undefined {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(target);\n } catch {\n // A path that vanished between discovery and recording is simply not an\n // input; the compiler records the set it could read.\n return undefined;\n }\n if (!stat.isFile()) return undefined;\n const sha256 = digestOf(target);\n if (sha256 === undefined) return undefined;\n return { path: target, size: stat.size, mtimeMs: stat.mtimeMs, sha256 };\n}\n\n/** Parses one recorded input, rejecting a record written before digests existed. */\nexport function parseInputFingerprint(value: unknown): InputFingerprint | undefined {\n if (!isRecord(value)) return undefined;\n if (\n typeof value.path !== 'string' ||\n typeof value.size !== 'number' ||\n typeof value.mtimeMs !== 'number' ||\n typeof value.sha256 !== 'string'\n ) {\n return undefined;\n }\n return { path: value.path, size: value.size, mtimeMs: value.mtimeMs, sha256: value.sha256 };\n}\n\n/** Whether every recorded input still holds the bytes it was recorded with. */\nexport function inputsAreFresh(inputs: readonly InputFingerprint[]): boolean {\n const moved: InputFingerprint[] = [];\n const key = crypto.createHash('sha256');\n for (const input of inputs) {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(input.path);\n } catch {\n // A recorded input that is gone cannot be confirmed, and no digest will\n // bring it back; the build it describes is stale.\n return false;\n }\n if (!stat.isFile()) return false;\n key.update(`${input.path} ${String(stat.size)} ${String(stat.mtimeMs)} `);\n if (stat.size !== input.size || stat.mtimeMs !== input.mtimeMs) moved.push(input);\n }\n if (moved.length === 0) return true;\n\n const cacheKey = key.digest('hex');\n const remembered = freshnessVerdicts.get(cacheKey);\n if (remembered !== undefined) return remembered;\n const fresh = moved.every((input) => digestOf(input.path) === input.sha256);\n freshnessVerdicts.set(cacheKey, fresh);\n return fresh;\n}\n\n/** Test seam: the memo is process-local and must not leak between cases. */\nexport function resetInputFreshnessCache(): void {\n freshnessVerdicts.clear();\n}\n\ninterface CompilerManifest {\n output: string;\n artifacts: string[];\n entries: string[];\n inputs: InputFingerprint[];\n}\n\ninterface BootstrapState {\n statePath: string;\n generatedDirectory: string;\n bootstrap?: string;\n bundles: Record<string, string>;\n precompile?: {\n version: number;\n strategy: 'bundle';\n bootstrapEntry: string;\n bootstrapManifest: string;\n bundleManifests: Record<string, string>;\n };\n}\n\nexport interface BootstrapStatus {\n bootstrap?: string;\n fresh: boolean;\n}\n\nexport interface BundleStatus {\n bundle?: string;\n fresh: boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {};\n return Object.fromEntries(\n Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),\n );\n}\n\nfunction canonicalPath(target: string): string {\n const absolute = path.resolve(target);\n try {\n return fs.realpathSync(absolute);\n } catch {\n const parent = path.dirname(absolute);\n if (parent === absolute) return absolute;\n return path.join(canonicalPath(parent), path.basename(absolute));\n }\n}\n\nfunction isInside(directory: string, target: string): boolean {\n const relative = path.relative(canonicalPath(directory), canonicalPath(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction packagedDoomEntry(moduleUrl: string = import.meta.url): string {\n const extension = moduleUrl.endsWith('.ts') ? 'ts' : 'mjs';\n const sourceRoot = path.dirname(path.dirname(fileURLToPath(moduleUrl)));\n return path.join(sourceRoot, 'extensions', 'entries', `doom.${extension}`);\n}\n\nfunction locationHasState(repoRoot: string, homeDirectory: string): boolean {\n return readSyncRegistration(repoRoot, homeDirectory) !== undefined;\n}\n\n/** Finds the nearest configured repository with generated Doom sync state. */\nexport function findSyncedRoot(cwd: string, homeDirectory: string = os.homedir()): string | undefined {\n try {\n if (locationHasState(cwd, homeDirectory)) return canonicalPath(cwd);\n const root = findRepositoryRoot(cwd);\n return locationHasState(root, homeDirectory) ? root : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction readBootstrapState(repoRoot: string, homeDirectory: string = os.homedir()): BootstrapState | undefined {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n if (!registration) return undefined;\n const statePath = registration.statePath;\n const generatedDirectory = registration.generationRoot;\n const state = readSyncState(repoRoot, homeDirectory);\n if (!state) return undefined;\n const bootstrap = typeof state.bootstrap === 'string' ? state.bootstrap : undefined;\n if (bootstrap && !isInside(generatedDirectory, bootstrap)) {\n throw new Error(`Doom bootstrap must stay inside ${generatedDirectory}: ${bootstrap}`);\n }\n\n let precompile: BootstrapState['precompile'];\n if (state.precompile !== undefined) {\n const value = state.precompile;\n if (\n !isRecord(value) ||\n typeof value.version !== 'number' ||\n value.strategy !== BUNDLED_PRECOMPILE_STRATEGY ||\n typeof value.bootstrapEntry !== 'string' ||\n typeof value.bootstrapManifest !== 'string' ||\n !isRecord(value.bundleManifests) ||\n Object.values(value.bundleManifests).some((manifest) => typeof manifest !== 'string')\n ) {\n throw new Error(`Doom sync state at ${statePath} has an invalid precompile record`);\n }\n precompile = {\n version: value.version,\n strategy: value.strategy,\n bootstrapEntry: value.bootstrapEntry,\n bootstrapManifest: value.bootstrapManifest,\n bundleManifests: value.bundleManifests as Record<string, string>,\n };\n }\n\n return {\n statePath,\n generatedDirectory,\n bootstrap,\n bundles: stringRecord(state.bundles),\n precompile,\n };\n}\n\nfunction readCompilerManifest(manifestPath: string, generatedDirectory: string): CompilerManifest | undefined {\n if (!isInside(generatedDirectory, manifestPath)) return undefined;\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown;\n if (\n !isRecord(parsed) ||\n typeof parsed.output !== 'string' ||\n !Array.isArray(parsed.artifacts) ||\n parsed.artifacts.some((artifact) => typeof artifact !== 'string') ||\n !Array.isArray(parsed.entries) ||\n parsed.entries.some((entry) => typeof entry !== 'string') ||\n !Array.isArray(parsed.inputs)\n ) {\n return undefined;\n }\n const inputs: InputFingerprint[] = [];\n for (const input of parsed.inputs) {\n // A manifest written before digests were recorded cannot be confirmed by\n // content, so it reads as absent and the next sync rewrites it.\n const fingerprint = parseInputFingerprint(input);\n if (fingerprint === undefined) return undefined;\n inputs.push(fingerprint);\n }\n const artifacts = parsed.artifacts as string[];\n const entries = parsed.entries as string[];\n if (\n !isInside(generatedDirectory, parsed.output) ||\n artifacts.some((artifact) => !isInside(generatedDirectory, artifact))\n ) {\n return undefined;\n }\n return { output: parsed.output, artifacts, entries, inputs };\n } catch {\n return undefined;\n }\n}\n\nfunction compilerManifestIsFresh(manifest: CompilerManifest): boolean {\n if (!fs.existsSync(manifest.output) || manifest.artifacts.some((artifact) => !fs.existsSync(artifact))) return false;\n return inputsAreFresh(manifest.inputs);\n}\n\nfunction freshBootstrapRecord(state: BootstrapState, expectedBootstrapEntry: string): CompilerManifest | undefined {\n if (!state.bootstrap || !state.precompile) return undefined;\n if (\n state.precompile.version !== PRECOMPILE_STATE_VERSION ||\n canonicalPath(state.precompile.bootstrapEntry) !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n const record = readCompilerManifest(state.precompile.bootstrapManifest, state.generatedDirectory);\n if (\n !record ||\n !compilerManifestIsFresh(record) ||\n record.output !== state.bootstrap ||\n record.entries.length !== 1 ||\n canonicalPath(record.entries[0] ?? '') !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n return record;\n}\n\n/** Validates only the bootstrap graph needed before the generated bootstrap is imported. */\nexport function readStartupBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state) return { bootstrap: undefined, fresh: false };\n return {\n bootstrap: state.bootstrap,\n fresh: freshBootstrapRecord(state, expected) !== undefined,\n };\n}\n\n/** Validates one selected composition without touching any inactive bundle inputs. */\nexport function readBundleStatus(\n repoRoot: string,\n compositionFingerprint: string,\n homeDirectory: string = os.homedir(),\n): BundleStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const bundle = state?.bundles[compositionFingerprint];\n const manifestPath = state?.precompile?.bundleManifests[compositionFingerprint];\n if (!state || state.precompile?.version !== PRECOMPILE_STATE_VERSION || !bundle || !manifestPath) {\n return { bundle, fresh: false };\n }\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return {\n bundle,\n fresh: Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest)),\n };\n}\n\n/** Validates the bootstrap and every bundle for `doompi sync --check` diagnostics. */\nexport function readBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state?.bootstrap || !state.precompile || !freshBootstrapRecord(state, expected)) {\n return { bootstrap: state?.bootstrap, fresh: false };\n }\n const manifests = Object.values(state.precompile.bundleManifests);\n if (new Set(manifests).size !== manifests.length) return { bootstrap: state.bootstrap, fresh: false };\n const fresh = Object.entries(state.bundles).every(([fingerprint, bundle]) => {\n const manifestPath = state.precompile?.bundleManifests[fingerprint];\n if (!manifestPath) return false;\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest));\n });\n return { bootstrap: state.bootstrap, fresh };\n}\n\n/** Reads only the generated bootstrap pointer while retaining state validation. */\nexport function readBootstrapPointer(repoRoot: string, homeDirectory: string = os.homedir()): string | undefined {\n return readBootstrapState(repoRoot, homeDirectory)?.bootstrap;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bootstrapLocator.mjs","names":[],"sources":["../../../src/adapters/bootstrapLocator.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { resolveDoomConfigurationRoot } from './repository/repository.ts';\nimport { readSyncRegistration } from './syncRegistration.ts';\nimport { readSyncState } from './syncState.ts';\nimport { BUNDLED_PRECOMPILE_STRATEGY, PRECOMPILE_STATE_VERSION } from './syncStateContract.ts';\n\n/**\n * One recorded build input: the stat pair for speed, the digest for truth.\n *\n * Comparing only `size` and `mtimeMs` makes every rebuild look like a change,\n * including one that restored byte-identical output from a build cache, and a\n * sync triggered that way republishes a generation nobody asked for. Comparing\n * content alone is correct but reads every input on every check, and the\n * cockpit polls this. Recording both lets the stat pair answer the common\n * \"nothing was touched\" case without opening a file, while the digest stays the\n * authority for anything whose stat moved.\n */\nexport interface InputFingerprint {\n path: string;\n size: number;\n mtimeMs: number;\n sha256: string;\n}\n\n/**\n * Verdicts already reached, so a rebuild that rewrote every input without\n * changing it does not re-hash the whole graph on every poll.\n *\n * Keyed by the stats actually found on disk, so it can never answer for a set\n * it did not see. Bounded by the compiler manifests in one composition, which\n * is single digits.\n */\nconst freshnessVerdicts = new Map<string, boolean>();\n\nfunction digestOf(filePath: string): string | undefined {\n try {\n return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');\n } catch {\n // Unreadable is indistinguishable from absent here: either way the recorded\n // digest cannot be confirmed, so the caller must treat the build as stale.\n return undefined;\n }\n}\n\n/** Records one build input, or undefined when it is not a readable regular file. */\nexport function fingerprintInput(target: string): InputFingerprint | undefined {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(target);\n } catch {\n // A path that vanished between discovery and recording is simply not an\n // input; the compiler records the set it could read.\n return undefined;\n }\n if (!stat.isFile()) return undefined;\n const sha256 = digestOf(target);\n if (sha256 === undefined) return undefined;\n return { path: target, size: stat.size, mtimeMs: stat.mtimeMs, sha256 };\n}\n\n/** Parses one recorded input, rejecting a record written before digests existed. */\nexport function parseInputFingerprint(value: unknown): InputFingerprint | undefined {\n if (!isRecord(value)) return undefined;\n if (\n typeof value.path !== 'string' ||\n typeof value.size !== 'number' ||\n typeof value.mtimeMs !== 'number' ||\n typeof value.sha256 !== 'string'\n ) {\n return undefined;\n }\n return { path: value.path, size: value.size, mtimeMs: value.mtimeMs, sha256: value.sha256 };\n}\n\n/** Whether every recorded input still holds the bytes it was recorded with. */\nexport function inputsAreFresh(inputs: readonly InputFingerprint[]): boolean {\n const moved: InputFingerprint[] = [];\n const key = crypto.createHash('sha256');\n for (const input of inputs) {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(input.path);\n } catch {\n // A recorded input that is gone cannot be confirmed, and no digest will\n // bring it back; the build it describes is stale.\n return false;\n }\n if (!stat.isFile()) return false;\n key.update(`${input.path} ${String(stat.size)} ${String(stat.mtimeMs)} `);\n if (stat.size !== input.size || stat.mtimeMs !== input.mtimeMs) moved.push(input);\n }\n if (moved.length === 0) return true;\n\n const cacheKey = key.digest('hex');\n const remembered = freshnessVerdicts.get(cacheKey);\n if (remembered !== undefined) return remembered;\n const fresh = moved.every((input) => digestOf(input.path) === input.sha256);\n freshnessVerdicts.set(cacheKey, fresh);\n return fresh;\n}\n\n/** Test seam: the memo is process-local and must not leak between cases. */\nexport function resetInputFreshnessCache(): void {\n freshnessVerdicts.clear();\n}\n\ninterface CompilerManifest {\n output: string;\n artifacts: string[];\n entries: string[];\n inputs: InputFingerprint[];\n}\n\ninterface BootstrapState {\n statePath: string;\n generatedDirectory: string;\n bootstrap?: string;\n bundles: Record<string, string>;\n precompile?: {\n version: number;\n strategy: 'bundle';\n bootstrapEntry: string;\n bootstrapManifest: string;\n bundleManifests: Record<string, string>;\n };\n}\n\nexport interface BootstrapStatus {\n bootstrap?: string;\n fresh: boolean;\n}\n\nexport interface BundleStatus {\n bundle?: string;\n fresh: boolean;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction stringRecord(value: unknown): Record<string, string> {\n if (!isRecord(value)) return {};\n return Object.fromEntries(\n Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string'),\n );\n}\n\nfunction canonicalPath(target: string): string {\n const absolute = path.resolve(target);\n try {\n return fs.realpathSync(absolute);\n } catch {\n const parent = path.dirname(absolute);\n if (parent === absolute) return absolute;\n return path.join(canonicalPath(parent), path.basename(absolute));\n }\n}\n\nfunction isInside(directory: string, target: string): boolean {\n const relative = path.relative(canonicalPath(directory), canonicalPath(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction packagedDoomEntry(moduleUrl: string = import.meta.url): string {\n const extension = moduleUrl.endsWith('.ts') ? 'ts' : 'mjs';\n const sourceRoot = path.dirname(path.dirname(fileURLToPath(moduleUrl)));\n return path.join(sourceRoot, 'extensions', 'entries', `doom.${extension}`);\n}\n\nfunction locationHasState(repoRoot: string, homeDirectory: string): boolean {\n return readSyncRegistration(repoRoot, homeDirectory) !== undefined;\n}\n\n/** Finds the nearest configured repository with generated Doom sync state. */\nexport function findSyncedRoot(cwd: string, homeDirectory: string = os.homedir()): string | undefined {\n if (locationHasState(cwd, homeDirectory)) return canonicalPath(cwd);\n const root = resolveDoomConfigurationRoot(cwd, homeDirectory);\n return locationHasState(root, homeDirectory) ? canonicalPath(root) : undefined;\n}\n\nfunction readBootstrapState(repoRoot: string, homeDirectory: string = os.homedir()): BootstrapState | undefined {\n const registration = readSyncRegistration(repoRoot, homeDirectory);\n if (!registration) return undefined;\n const statePath = registration.statePath;\n const generatedDirectory = registration.generationRoot;\n const state = readSyncState(repoRoot, homeDirectory);\n if (!state) return undefined;\n const bootstrap = typeof state.bootstrap === 'string' ? state.bootstrap : undefined;\n if (bootstrap && !isInside(generatedDirectory, bootstrap)) {\n throw new Error(`Doom bootstrap must stay inside ${generatedDirectory}: ${bootstrap}`);\n }\n\n let precompile: BootstrapState['precompile'];\n if (state.precompile !== undefined) {\n const value = state.precompile;\n if (\n !isRecord(value) ||\n typeof value.version !== 'number' ||\n value.strategy !== BUNDLED_PRECOMPILE_STRATEGY ||\n typeof value.bootstrapEntry !== 'string' ||\n typeof value.bootstrapManifest !== 'string' ||\n !isRecord(value.bundleManifests) ||\n Object.values(value.bundleManifests).some((manifest) => typeof manifest !== 'string')\n ) {\n throw new Error(`Doom sync state at ${statePath} has an invalid precompile record`);\n }\n precompile = {\n version: value.version,\n strategy: value.strategy,\n bootstrapEntry: value.bootstrapEntry,\n bootstrapManifest: value.bootstrapManifest,\n bundleManifests: value.bundleManifests as Record<string, string>,\n };\n }\n\n return {\n statePath,\n generatedDirectory,\n bootstrap,\n bundles: stringRecord(state.bundles),\n precompile,\n };\n}\n\nfunction readCompilerManifest(manifestPath: string, generatedDirectory: string): CompilerManifest | undefined {\n if (!isInside(generatedDirectory, manifestPath)) return undefined;\n try {\n const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown;\n if (\n !isRecord(parsed) ||\n typeof parsed.output !== 'string' ||\n !Array.isArray(parsed.artifacts) ||\n parsed.artifacts.some((artifact) => typeof artifact !== 'string') ||\n !Array.isArray(parsed.entries) ||\n parsed.entries.some((entry) => typeof entry !== 'string') ||\n !Array.isArray(parsed.inputs)\n ) {\n return undefined;\n }\n const inputs: InputFingerprint[] = [];\n for (const input of parsed.inputs) {\n // A manifest written before digests were recorded cannot be confirmed by\n // content, so it reads as absent and the next sync rewrites it.\n const fingerprint = parseInputFingerprint(input);\n if (fingerprint === undefined) return undefined;\n inputs.push(fingerprint);\n }\n const artifacts = parsed.artifacts as string[];\n const entries = parsed.entries as string[];\n if (\n !isInside(generatedDirectory, parsed.output) ||\n artifacts.some((artifact) => !isInside(generatedDirectory, artifact))\n ) {\n return undefined;\n }\n return { output: parsed.output, artifacts, entries, inputs };\n } catch {\n return undefined;\n }\n}\n\nfunction compilerManifestIsFresh(manifest: CompilerManifest): boolean {\n if (!fs.existsSync(manifest.output) || manifest.artifacts.some((artifact) => !fs.existsSync(artifact))) return false;\n return inputsAreFresh(manifest.inputs);\n}\n\nfunction freshBootstrapRecord(state: BootstrapState, expectedBootstrapEntry: string): CompilerManifest | undefined {\n if (!state.bootstrap || !state.precompile) return undefined;\n if (\n state.precompile.version !== PRECOMPILE_STATE_VERSION ||\n canonicalPath(state.precompile.bootstrapEntry) !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n const record = readCompilerManifest(state.precompile.bootstrapManifest, state.generatedDirectory);\n if (\n !record ||\n !compilerManifestIsFresh(record) ||\n record.output !== state.bootstrap ||\n record.entries.length !== 1 ||\n canonicalPath(record.entries[0] ?? '') !== canonicalPath(expectedBootstrapEntry)\n ) {\n return undefined;\n }\n return record;\n}\n\n/** Validates only the bootstrap graph needed before the generated bootstrap is imported. */\nexport function readStartupBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state) return { bootstrap: undefined, fresh: false };\n return {\n bootstrap: state.bootstrap,\n fresh: freshBootstrapRecord(state, expected) !== undefined,\n };\n}\n\n/** Validates one selected composition without touching any inactive bundle inputs. */\nexport function readBundleStatus(\n repoRoot: string,\n compositionFingerprint: string,\n homeDirectory: string = os.homedir(),\n): BundleStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const bundle = state?.bundles[compositionFingerprint];\n const manifestPath = state?.precompile?.bundleManifests[compositionFingerprint];\n if (!state || state.precompile?.version !== PRECOMPILE_STATE_VERSION || !bundle || !manifestPath) {\n return { bundle, fresh: false };\n }\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return {\n bundle,\n fresh: Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest)),\n };\n}\n\n/** Validates the bootstrap and every bundle for `doompi sync --check` diagnostics. */\nexport function readBootstrapStatus(\n repoRoot: string,\n expectedBootstrapEntry?: string,\n homeDirectory: string = os.homedir(),\n): BootstrapStatus {\n const state = readBootstrapState(repoRoot, homeDirectory);\n const expected = expectedBootstrapEntry ?? packagedDoomEntry();\n if (!state?.bootstrap || !state.precompile || !freshBootstrapRecord(state, expected)) {\n return { bootstrap: state?.bootstrap, fresh: false };\n }\n const manifests = Object.values(state.precompile.bundleManifests);\n if (new Set(manifests).size !== manifests.length) return { bootstrap: state.bootstrap, fresh: false };\n const fresh = Object.entries(state.bundles).every(([fingerprint, bundle]) => {\n const manifestPath = state.precompile?.bundleManifests[fingerprint];\n if (!manifestPath) return false;\n const manifest = readCompilerManifest(manifestPath, state.generatedDirectory);\n return Boolean(manifest && manifest.output === bundle && compilerManifestIsFresh(manifest));\n });\n return { bootstrap: state.bootstrap, fresh };\n}\n\n/** Reads only the generated bootstrap pointer while retaining state validation. */\nexport function readBootstrapPointer(repoRoot: string, homeDirectory: string = os.homedir()): string | undefined {\n return readBootstrapState(repoRoot, homeDirectory)?.bootstrap;\n}\n"],"mappings":"oWAoCA,MAAM,EAAoB,IAAI,IAE9B,SAAS,EAAS,EAAsC,CACtD,GAAI,CACF,OAAO,EAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,EAAG,aAAa,CAAQ,CAAC,CAAC,CAAC,OAAO,KAAK,CACnF,MAAQ,CAGN,MACF,CACF,CAGA,SAAgB,EAAiB,EAA8C,CAC7E,IAAI,EACJ,GAAI,CACF,EAAO,EAAG,SAAS,CAAM,CAC3B,MAAQ,CAGN,MACF,CACA,GAAI,CAAC,EAAK,OAAO,EAAG,OACpB,IAAM,EAAS,EAAS,CAAM,EAC1B,OAAW,IAAA,GACf,MAAO,CAAE,KAAM,EAAQ,KAAM,EAAK,KAAM,QAAS,EAAK,QAAS,QAAO,CACxE,CAGA,SAAgB,EAAsB,EAA8C,CAC7E,KAAS,CAAK,GAEjB,OAAO,EAAM,MAAS,UACtB,OAAO,EAAM,MAAS,UACtB,OAAO,EAAM,SAAY,UACzB,OAAO,EAAM,QAAW,SAI1B,MAAO,CAAE,KAAM,EAAM,KAAM,KAAM,EAAM,KAAM,QAAS,EAAM,QAAS,OAAQ,EAAM,MAAO,CAC5F,CAGA,SAAgB,EAAe,EAA8C,CAC3E,IAAM,EAA4B,CAAC,EAC7B,EAAM,EAAO,WAAW,QAAQ,EACtC,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAI,EACJ,GAAI,CACF,EAAO,EAAG,SAAS,EAAM,IAAI,CAC/B,MAAQ,CAGN,MAAO,EACT,CACA,GAAI,CAAC,EAAK,OAAO,EAAG,MAAO,GAC3B,EAAI,OAAO,GAAG,EAAM,KAAK,GAAG,OAAO,EAAK,IAAI,EAAE,GAAG,OAAO,EAAK,OAAO,EAAE,EAAE,GACpE,EAAK,OAAS,EAAM,MAAQ,EAAK,UAAY,EAAM,UAAS,EAAM,KAAK,CAAK,CAClF,CACA,GAAI,EAAM,SAAW,EAAG,MAAO,GAE/B,IAAM,EAAW,EAAI,OAAO,KAAK,EAC3B,EAAa,EAAkB,IAAI,CAAQ,EACjD,GAAI,IAAe,IAAA,GAAW,OAAO,EACrC,IAAM,EAAQ,EAAM,MAAO,GAAU,EAAS,EAAM,IAAI,IAAM,EAAM,MAAM,EAE1E,OADA,EAAkB,IAAI,EAAU,CAAK,EAC9B,CACT,CAsCA,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,SAAS,EAAa,EAAwC,CAE5D,OADK,EAAS,CAAK,EACZ,OAAO,YACZ,OAAO,QAAQ,CAAK,CAAC,CAAC,OAAQ,GAAqC,OAAO,EAAM,IAAO,QAAQ,CACjG,EAH6B,CAAC,CAIhC,CAEA,SAAS,EAAc,EAAwB,CAC7C,IAAM,EAAW,EAAK,QAAQ,CAAM,EACpC,GAAI,CACF,OAAO,EAAG,aAAa,CAAQ,CACjC,MAAQ,CACN,IAAM,EAAS,EAAK,QAAQ,CAAQ,EAEpC,OADI,IAAW,EAAiB,EACzB,EAAK,KAAK,EAAc,CAAM,EAAG,EAAK,SAAS,CAAQ,CAAC,CACjE,CACF,CAEA,SAAS,EAAS,EAAmB,EAAyB,CAC5D,IAAM,EAAW,EAAK,SAAS,EAAc,CAAS,EAAG,EAAc,CAAM,CAAC,EAC9E,OAAO,IAAa,IAAO,CAAC,EAAS,WAAW,IAAI,GAAK,CAAC,EAAK,WAAW,CAAQ,CACpF,CAEA,SAAS,EAAkB,EAAoB,YAAY,IAAa,CACtE,IAAM,EAAY,EAAU,SAAS,KAAK,EAAI,KAAO,MAC/C,EAAa,EAAK,QAAQ,EAAK,QAAQ,EAAc,CAAS,CAAC,CAAC,EACtE,OAAO,EAAK,KAAK,EAAY,aAAc,UAAW,QAAQ,GAAW,CAC3E,CAEA,SAAS,EAAiB,EAAkB,EAAgC,CAC1E,OAAO,EAAqB,EAAU,CAAa,IAAM,IAAA,EAC3D,CAGA,SAAgB,EAAe,EAAa,EAAwB,EAAG,QAAQ,EAAuB,CACpG,GAAI,EAAiB,EAAK,CAAa,EAAG,OAAO,EAAc,CAAG,EAClE,IAAM,EAAO,EAA6B,EAAK,CAAa,EAC5D,OAAO,EAAiB,EAAM,CAAa,EAAI,EAAc,CAAI,EAAI,IAAA,EACvE,CAEA,SAAS,EAAmB,EAAkB,EAAwB,EAAG,QAAQ,EAA+B,CAC9G,IAAM,EAAe,EAAqB,EAAU,CAAa,EACjE,GAAI,CAAC,EAAc,OACnB,IAAM,EAAY,EAAa,UACzB,EAAqB,EAAa,eAClC,EAAQ,EAAc,EAAU,CAAa,EACnD,GAAI,CAAC,EAAO,OACZ,IAAM,EAAY,OAAO,EAAM,WAAc,SAAW,EAAM,UAAY,IAAA,GAC1E,GAAI,GAAa,CAAC,EAAS,EAAoB,CAAS,EACtD,MAAU,MAAM,mCAAmC,EAAmB,IAAI,GAAW,EAGvF,IAAI,EACJ,GAAI,EAAM,aAAe,IAAA,GAAW,CAClC,IAAM,EAAQ,EAAM,WACpB,GACE,CAAC,EAAS,CAAK,GACf,OAAO,EAAM,SAAY,UACzB,EAAM,WAAA,UACN,OAAO,EAAM,gBAAmB,UAChC,OAAO,EAAM,mBAAsB,UACnC,CAAC,EAAS,EAAM,eAAe,GAC/B,OAAO,OAAO,EAAM,eAAe,CAAC,CAAC,KAAM,GAAa,OAAO,GAAa,QAAQ,EAEpF,MAAU,MAAM,sBAAsB,EAAU,kCAAkC,EAEpF,EAAa,CACX,QAAS,EAAM,QACf,SAAU,EAAM,SAChB,eAAgB,EAAM,eACtB,kBAAmB,EAAM,kBACzB,gBAAiB,EAAM,eACzB,CACF,CAEA,MAAO,CACL,YACA,qBACA,YACA,QAAS,EAAa,EAAM,OAAO,EACnC,YACF,CACF,CAEA,SAAS,EAAqB,EAAsB,EAA0D,CACvG,KAAS,EAAoB,CAAY,EAC9C,GAAI,CACF,IAAM,EAAS,KAAK,MAAM,EAAG,aAAa,EAAc,MAAM,CAAC,EAC/D,GACE,CAAC,EAAS,CAAM,GAChB,OAAO,EAAO,QAAW,UACzB,CAAC,MAAM,QAAQ,EAAO,SAAS,GAC/B,EAAO,UAAU,KAAM,GAAa,OAAO,GAAa,QAAQ,GAChE,CAAC,MAAM,QAAQ,EAAO,OAAO,GAC7B,EAAO,QAAQ,KAAM,GAAU,OAAO,GAAU,QAAQ,GACxD,CAAC,MAAM,QAAQ,EAAO,MAAM,EAE5B,OAEF,IAAM,EAA6B,CAAC,EACpC,IAAK,IAAM,KAAS,EAAO,OAAQ,CAGjC,IAAM,EAAc,EAAsB,CAAK,EAC/C,GAAI,IAAgB,IAAA,GAAW,OAC/B,EAAO,KAAK,CAAW,CACzB,CACA,IAAM,EAAY,EAAO,UACnB,EAAU,EAAO,QAOvB,MALE,CAAC,EAAS,EAAoB,EAAO,MAAM,GAC3C,EAAU,KAAM,GAAa,CAAC,EAAS,EAAoB,CAAQ,CAAC,EAEpE,OAEK,CAAE,OAAQ,EAAO,OAAQ,YAAW,UAAS,QAAO,CAC7D,MAAQ,CACN,MACF,CACF,CAEA,SAAS,EAAwB,EAAqC,CAEpE,MADI,CAAC,EAAG,WAAW,EAAS,MAAM,GAAK,EAAS,UAAU,KAAM,GAAa,CAAC,EAAG,WAAW,CAAQ,CAAC,EAAU,GACxG,EAAe,EAAS,MAAM,CACvC,CAEA,SAAS,EAAqB,EAAuB,EAA8D,CAEjH,GADI,CAAC,EAAM,WAAa,CAAC,EAAM,YAE7B,EAAM,WAAW,UAAA,GACjB,EAAc,EAAM,WAAW,cAAc,IAAM,EAAc,CAAsB,EAEvF,OAEF,IAAM,EAAS,EAAqB,EAAM,WAAW,kBAAmB,EAAM,kBAAkB,EAE9F,MAAC,GACD,CAAC,EAAwB,CAAM,GAC/B,EAAO,SAAW,EAAM,WACxB,EAAO,QAAQ,SAAW,GAC1B,EAAc,EAAO,QAAQ,IAAM,EAAE,IAAM,EAAc,CAAsB,GAIjF,OAAO,CACT,CAGA,SAAgB,EACd,EACA,EACA,EAAwB,EAAG,QAAQ,EAClB,CACjB,IAAM,EAAQ,EAAmB,EAAU,CAAa,EAClD,EAAW,GAA0B,EAAkB,EAE7D,OADK,EACE,CACL,UAAW,EAAM,UACjB,MAAO,EAAqB,EAAO,CAAQ,IAAM,IAAA,EACnD,EAJmB,CAAE,UAAW,IAAA,GAAW,MAAO,EAAM,CAK1D,CAGA,SAAgB,EACd,EACA,EACA,EAAwB,EAAG,QAAQ,EACrB,CACd,IAAM,EAAQ,EAAmB,EAAU,CAAa,EAClD,EAAS,GAAO,QAAQ,GACxB,EAAe,GAAO,YAAY,gBAAgB,GACxD,GAAI,CAAC,GAAS,EAAM,YAAY,UAAA,GAAwC,CAAC,GAAU,CAAC,EAClF,MAAO,CAAE,SAAQ,MAAO,EAAM,EAEhC,IAAM,EAAW,EAAqB,EAAc,EAAM,kBAAkB,EAC5E,MAAO,CACL,SACA,MAAO,GAAQ,GAAY,EAAS,SAAW,GAAU,EAAwB,CAAQ,EAC3F,CACF,CAGA,SAAgB,EACd,EACA,EACA,EAAwB,EAAG,QAAQ,EAClB,CACjB,IAAM,EAAQ,EAAmB,EAAU,CAAa,EAClD,EAAW,GAA0B,EAAkB,EAC7D,GAAI,CAAC,GAAO,WAAa,CAAC,EAAM,YAAc,CAAC,EAAqB,EAAO,CAAQ,EACjF,MAAO,CAAE,UAAW,GAAO,UAAW,MAAO,EAAM,EAErD,IAAM,EAAY,OAAO,OAAO,EAAM,WAAW,eAAe,EAChE,GAAI,IAAI,IAAI,CAAS,CAAC,CAAC,OAAS,EAAU,OAAQ,MAAO,CAAE,UAAW,EAAM,UAAW,MAAO,EAAM,EACpG,IAAM,EAAQ,OAAO,QAAQ,EAAM,OAAO,CAAC,CAAC,OAAO,CAAC,EAAa,KAAY,CAC3E,IAAM,EAAe,EAAM,YAAY,gBAAgB,GACvD,GAAI,CAAC,EAAc,MAAO,GAC1B,IAAM,EAAW,EAAqB,EAAc,EAAM,kBAAkB,EAC5E,MAAO,GAAQ,GAAY,EAAS,SAAW,GAAU,EAAwB,CAAQ,EAC3F,CAAC,EACD,MAAO,CAAE,UAAW,EAAM,UAAW,OAAM,CAC7C"}
@@ -1,2 +1,2 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../services/extensionAssembler.cjs"),n=require("./syncState.cjs"),r=require("./bootstrapLocator.cjs"),i=require("./config/harnessState.cjs"),a=require("./harnessContext.cjs"),o=require("./compositionState.cjs");let s=require("node:url"),c=require("@agimon-ai/doompi-extension-contracts/child-process"),l=require("node:path");l=e.__toESM(l,1);let u=require("node:fs");u=e.__toESM(u,1);let d=require("node:os");d=e.__toESM(d,1);let f=require("@agimon-ai/doompi-config/majorModes"),p=require("@agimon-ai/doompi-extension-contracts/cordis-host"),m=require("@earendil-works/pi-coding-agent"),h=require("@agimon-ai/doompi-ui/extensionName"),g=require("@agimon-ai/doompi-config/harnessStore");const _=`@agimon-ai/doompi-config/selectionSwitch`,v=`doompi is configured for Pi but this repository was never synced. Run doompi sync.`,y=`doompi could not read its synchronized state. Run doompi sync.`,b=process.env.DOOMPI_TIMING===`1`||process.env.PI_TIMING===`1`,x={majorMode:`major-mode`,domains:`domains`,profile:`profile`,mute:`mute`},S=`layer`;function C(e){return e instanceof Error?e.message:String(e)}function w(e){try{return u.default.realpathSync(e)}catch{return l.default.resolve(e)}}function T(e,t){let{extensions:n}=(0,m.parseArgs)(e);if(!n||n.length===0)return!1;let r=new Set(Object.values(t).map(w));return n.some(e=>r.has(w(e)))}async function E(e,t,n=process.env){let r=await A(t,n),a={...i.readHarnessState({...e.env,...n}),...e.fileState,mcpProjection:{...e.fileState.mcpProjection,stagingDirectory:r},temporaryDirectory:r};(0,g.createHarnessSession)(a,{directory:r,environment:n});for(let[e,t]of Object.entries(a.profileEnvironment))n[e]===void 0&&(n[e]=t);return a}function D(e){let{unknownFlags:t}=(0,m.parseArgs)(e),n=e=>{let n=t.get(e);return typeof n==`string`&&n.trim()?n.trim():void 0},r=n(x.domains);return{majorMode:n(x.majorMode),domains:r?.split(`,`).map(e=>e.trim()).filter(Boolean),profile:n(x.profile),mute:t.get(x.mute)!==void 0,removedLayer:t.get(S)!==void 0}}function O(e){e.registerFlag(x.majorMode,{type:`string`,description:`Named major mode from .doom/modes.yaml`}),e.registerFlag(x.domains,{type:`string`,description:`Comma-separated content domains`}),e.registerFlag(x.profile,{type:`string`,description:`Persona and env from .doom/profiles.yaml`}),e.registerFlag(x.mute,{type:`boolean`,description:`Disable the notification extension`})}async function k(e,t,n,r){if(e.removedLayer&&r.push(`--${S} was replaced by --${x.majorMode}`),e.majorMode)try{let{applyMajorMode:n}=await import(_);n(t,e.majorMode,i.getHarnessState())}catch(t){r.push(`--${x.majorMode} ${e.majorMode}: ${C(t)}`)}if(e.domains&&e.domains.length>0)try{let{applyDomains:t}=await import(`@agimon-ai/doompi-domain/apply`);await t(e.domains,i.getHarnessState())}catch(t){r.push(`--domains ${e.domains.join(`,`)}: ${C(t)}`)}if(e.profile)try{let[{applyProfile:t},{resolveProfile:r}]=await Promise.all([import(_),import(`@agimon-ai/doompi-config/profiles`)]);await t(r(n,e.profile),i.getHarnessState())}catch(t){r.push(`--profile ${e.profile}: ${C(t)}`)}}async function A(e,t=process.env){let r=n.runDirectory(e,process.pid,t.HOME??d.default.homedir());return await u.default.promises.mkdir(r,{mode:448,recursive:!0}),i.updateHarnessState({temporaryDirectory:r},t),r}async function j(e,t=process.env){await u.default.promises.rm(n.runDirectory(e,process.pid,t.HOME??d.default.homedir()),{recursive:!0,force:!0})}async function M(e,r=i.getHarnessState(),a=process.env){let s=r.root??e.root,c=n.createMapResolvers(e.resolved,e.compiled),u={agents:r.agents,autoStop:!1,preset:e.selection.preset,personaEntry:c.packageEntry(t.PERSONA_ENTRY),majorMode:r.majorMode,layers:[...r.layers],majorModesConfig:(0,f.loadMajorModesConfig)(s),resolvers:c},d=t.resolveExtensionComposition({...u,mute:a[o.MUTE_ENV]===`1`});a[p.DOOM_CORDIS_HOST_REQUIRED_ENV]=`1`,i.updateHarnessState({childExtensions:[...d.childActivation],compositionFingerprint:d.fingerprint},a),r.agents&&(a.PI_SUBAGENT_EXTRA_AGENT_DIRS=r.agentDirectories.join(l.default.delimiter),a.PI_SUBAGENT_EXTRA_SKILL_DIRS=r.skillDirectories.join(l.default.delimiter));let m=[...d.parentActivation],h=e.bundles?.[d.fingerprint];return{entries:h?[h]:m,fingerprint:d.fingerprint}}async function N(e,t=i.getHarnessState(),n=process.env){return(await M(e,t,n)).entries}async function P(e,t,n){let r=[];for(let i of t){let t=performance.now(),a;try{a=await import((0,s.pathToFileURL)(i).href),b&&process.stderr.write(`[doompi:timing] import ${(performance.now()-t).toFixed(1)}ms ${i}\n`)}catch(e){n.push(`${i}: ${C(e)}`);continue}if(typeof a.default!=`function`){n.push(`${i}: does not export an extension factory`);continue}let o=performance.now();try{await a.default((0,h.withExtensionSource)(e,i)),b&&process.stderr.write(`[doompi:timing] factory ${(performance.now()-o).toFixed(1)}ms ${i}\n`),r.push(i)}catch(e){n.push(`${i}: ${C(e)}`)}}return r}async function F(e,t={}){let i=t.environment??process.env,s=[],l=i.HOME??d.default.homedir(),u=r.findSyncedRoot(t.cwd??process.cwd(),l);if(!u)return{problems:[v],stale:!1,loaded:[]};let p;try{p=n.readSyncState(u,l)}catch(e){return{problems:[C(e)],stale:!1,loaded:[]}}if(!p)return{problems:[v],stale:!1,loaded:[]};let m=t.argv??process.argv.slice(2);if(i[o.EXTERNAL_EXTENSIONS_ENV]===`1`||T(m,{...p.resolved,...p.bundles}))return{problems:[],stale:!1,loaded:[]};if(a.configurePreset({preset:p.selection.preset,piArgs:[]},i),!(0,c.alreadyComposed)(i)){i[o.COMPOSED_ENV]=`1`,await E(p,u,i);let e=D(m);e.mute&&(i[o.MUTE_ENV]=`1`),await k(e,(0,f.loadMajorModesConfig)(u),u,s)}let h;try{h=await M(p,(0,g.loadHarnessState)(i).state,i)}catch(e){return{problems:[...s,C(e)],stale:!1,loaded:[]}}let _=p.bundles?.[h.fingerprint];if(_)try{let e=r.readBundleStatus(u,h.fingerprint,l);if(!e.fresh||e.bundle!==_)return{problems:[...s,y],stale:!1,loaded:[]}}catch{return{problems:[...s,y],stale:!1,loaded:[]}}let b=await P(e,h.entries,s);return{problems:s,stale:n.computeInputsHash(u,p.selection,l)!==p.inputsHash||(0,g.loadHarnessState)(i).state.majorMode===p.selection.majorMode&&h.fingerprint!==p.compositionFingerprint,loaded:b}}exports.COMPOSED_ENV=o.COMPOSED_ENV,exports.DOOM_FLAGS=x,exports.EXTERNAL_EXTENSIONS_ENV=o.EXTERNAL_EXTENSIONS_ENV,exports.MUTE_ENV=o.MUTE_ENV,exports.REMOVED_LAYER_FLAG=S,Object.defineProperty(exports,"alreadyComposed",{enumerable:!0,get:function(){return c.alreadyComposed}}),exports.applyStartupFlags=k,exports.cleanupRunDirectory=j,exports.composeDoomSession=F,exports.composeLoadOrder=N,exports.composeRuntimeLoadPlan=M,exports.extensionsProvidedExternally=T,exports.findSyncedRoot=r.findSyncedRoot,exports.loadComposedExtensions=P,exports.prepareRunDirectory=A,exports.readStartupFlags=D,exports.registerDoomFlags=O,exports.startSyncedSession=E;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../services/extensionAssembler.cjs"),n=require("./syncState.cjs"),r=require("./bootstrapLocator.cjs"),i=require("./config/harnessState.cjs"),a=require("./harnessContext.cjs"),o=require("./compositionState.cjs");let s=require("node:url"),c=require("@agimon-ai/doompi-extension-contracts/child-process"),l=require("node:path");l=e.__toESM(l,1);let u=require("node:fs");u=e.__toESM(u,1);let d=require("node:os");d=e.__toESM(d,1);let f=require("@agimon-ai/doompi-config/majorModes"),p=require("@agimon-ai/doompi-ui/extensionName"),m=require("@agimon-ai/doompi-extension-contracts/cordis-host"),h=require("@earendil-works/pi-coding-agent"),g=require("@agimon-ai/doompi-config/harnessStore");const _=`@agimon-ai/doompi-config/selectionSwitch`,v=`doompi is configured for Pi but this repository was never synced. Run doompi sync.`,y=`doompi could not read its synchronized state. Run doompi sync.`,b=process.env.DOOMPI_TIMING===`1`||process.env.PI_TIMING===`1`,x={majorMode:`major-mode`,domains:`domains`,profile:`profile`,mute:`mute`},S=`layer`;function C(e){return e instanceof Error?e.message:String(e)}function w(e){try{return u.default.realpathSync(e)}catch{return l.default.resolve(e)}}function T(e,t){let{extensions:n}=(0,h.parseArgs)(e);if(!n||n.length===0)return!1;let r=new Set(Object.values(t).map(w));return n.some(e=>r.has(w(e)))}async function E(e,t,n=process.env){let r=await A(t,n),a={...i.readHarnessState({...e.env,...n}),...e.fileState,mcpProjection:{...e.fileState.mcpProjection,stagingDirectory:r},temporaryDirectory:r};(0,g.createHarnessSession)(a,{directory:r,environment:n});for(let[e,t]of Object.entries(a.profileEnvironment))n[e]===void 0&&(n[e]=t);return a}function D(e){let{unknownFlags:t}=(0,h.parseArgs)(e),n=e=>{let n=t.get(e);return typeof n==`string`&&n.trim()?n.trim():void 0},r=n(x.domains);return{majorMode:n(x.majorMode),domains:r?.split(`,`).map(e=>e.trim()).filter(Boolean),profile:n(x.profile),mute:t.get(x.mute)!==void 0,removedLayer:t.get(S)!==void 0}}function O(e){e.registerFlag(x.majorMode,{type:`string`,description:`Named major mode from .doom/modes.yaml`}),e.registerFlag(x.domains,{type:`string`,description:`Comma-separated content domains`}),e.registerFlag(x.profile,{type:`string`,description:`Persona and env from .doom/profiles.yaml`}),e.registerFlag(x.mute,{type:`boolean`,description:`Disable the notification extension`})}async function k(e,t,n,r){if(e.removedLayer&&r.push(`--${S} was replaced by --${x.majorMode}`),e.majorMode)try{let{applyMajorMode:n}=await import(_);n(t,e.majorMode,i.getHarnessState())}catch(t){r.push(`--${x.majorMode} ${e.majorMode}: ${C(t)}`)}if(e.domains&&e.domains.length>0)try{let{applyDomains:t}=await import(`@agimon-ai/doompi-domain/apply`);await t(e.domains,i.getHarnessState())}catch(t){r.push(`--domains ${e.domains.join(`,`)}: ${C(t)}`)}if(e.profile)try{let[{applyProfile:t},{resolveProfile:r}]=await Promise.all([import(_),import(`@agimon-ai/doompi-config/profiles`)]);await t(r(n,e.profile),i.getHarnessState())}catch(t){r.push(`--profile ${e.profile}: ${C(t)}`)}}async function A(e,t=process.env){let r=n.runDirectory(e,process.pid,t.HOME??d.default.homedir());return await u.default.promises.mkdir(r,{mode:448,recursive:!0}),i.updateHarnessState({temporaryDirectory:r},t),r}async function j(e,t=process.env){await u.default.promises.rm(n.runDirectory(e,process.pid,t.HOME??d.default.homedir()),{recursive:!0,force:!0})}async function M(e,r=i.getHarnessState(),a=process.env){let s=r.root??e.root,c=n.createMapResolvers(e.resolved,e.compiled),u={agents:r.agents,autoStop:!1,preset:e.selection.preset,personaEntry:c.packageEntry(t.PERSONA_ENTRY),majorMode:r.majorMode,layers:[...r.layers],majorModesConfig:(0,f.loadMajorModesConfig)(s),resolvers:c},d=t.resolveExtensionComposition({...u,mute:a[o.MUTE_ENV]===`1`});a[m.DOOM_CORDIS_HOST_REQUIRED_ENV]=`1`,i.updateHarnessState({childExtensions:[...d.childActivation],compositionFingerprint:d.fingerprint,packageAttribution:t.packageAttribution(d)},a),r.agents&&(a.PI_SUBAGENT_EXTRA_AGENT_DIRS=r.agentDirectories.join(l.default.delimiter),a.PI_SUBAGENT_EXTRA_SKILL_DIRS=r.skillDirectories.join(l.default.delimiter));let p=[...d.parentActivation],h=e.bundles?.[d.fingerprint];return{entries:h?[h]:p,fingerprint:d.fingerprint}}async function N(e,t=i.getHarnessState(),n=process.env){return(await M(e,t,n)).entries}async function P(e,t,n){let r=[];for(let i of t){let t=performance.now(),a;try{a=await import((0,s.pathToFileURL)(i).href),b&&process.stderr.write(`[doompi:timing] import ${(performance.now()-t).toFixed(1)}ms ${i}\n`)}catch(e){n.push(`${i}: ${C(e)}`);continue}if(typeof a.default!=`function`){n.push(`${i}: does not export an extension factory`);continue}let o=performance.now();try{await a.default((0,p.withExtensionSource)(e,i)),b&&process.stderr.write(`[doompi:timing] factory ${(performance.now()-o).toFixed(1)}ms ${i}\n`),r.push(i)}catch(e){n.push(`${i}: ${C(e)}`)}}return r}async function F(e,t={}){let i=t.environment??process.env,s=[],l=i.HOME??d.default.homedir(),u=r.findSyncedRoot(t.cwd??process.cwd(),l);if(!u)return{problems:[v],stale:!1,loaded:[]};let p;try{p=n.readSyncState(u,l)}catch(e){return{problems:[C(e)],stale:!1,loaded:[]}}if(!p)return{problems:[v],stale:!1,loaded:[]};let m=t.argv??process.argv.slice(2);if(i[o.EXTERNAL_EXTENSIONS_ENV]===`1`||T(m,{...p.resolved,...p.bundles}))return{problems:[],stale:!1,loaded:[]};if(a.configurePreset({preset:p.selection.preset,piArgs:[]},i),!(0,c.alreadyComposed)(i)){i[o.COMPOSED_ENV]=`1`,await E(p,u,i);let e=D(m);e.mute&&(i[o.MUTE_ENV]=`1`),await k(e,(0,f.loadMajorModesConfig)(u),u,s)}let h;try{h=await M(p,(0,g.loadHarnessState)(i).state,i)}catch(e){return{problems:[...s,C(e)],stale:!1,loaded:[]}}let _=p.bundles?.[h.fingerprint];if(_)try{let e=r.readBundleStatus(u,h.fingerprint,l);if(!e.fresh||e.bundle!==_)return{problems:[...s,y],stale:!1,loaded:[]}}catch{return{problems:[...s,y],stale:!1,loaded:[]}}let b=await P(e,h.entries,s);return{problems:s,stale:n.computeInputsHash(u,p.selection,l)!==p.inputsHash||(0,g.loadHarnessState)(i).state.majorMode===p.selection.majorMode&&h.fingerprint!==p.compositionFingerprint,loaded:b}}exports.COMPOSED_ENV=o.COMPOSED_ENV,exports.DOOM_FLAGS=x,exports.EXTERNAL_EXTENSIONS_ENV=o.EXTERNAL_EXTENSIONS_ENV,exports.MUTE_ENV=o.MUTE_ENV,exports.REMOVED_LAYER_FLAG=S,Object.defineProperty(exports,"alreadyComposed",{enumerable:!0,get:function(){return c.alreadyComposed}}),exports.applyStartupFlags=k,exports.cleanupRunDirectory=j,exports.composeDoomSession=F,exports.composeLoadOrder=N,exports.composeRuntimeLoadPlan=M,exports.extensionsProvidedExternally=T,exports.findSyncedRoot=r.findSyncedRoot,exports.loadComposedExtensions=P,exports.prepareRunDirectory=A,exports.readStartupFlags=D,exports.registerDoomFlags=O,exports.startSyncedSession=E;
2
2
  //# sourceMappingURL=composer.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"composer.cjs","names":["fs","path","parseArgs","readHarnessState","getHarnessState","runDirectory","os","createMapResolvers","PERSONA_ENTRY","loadMajorModesConfig","resolveExtensionComposition","MUTE_ENV","DOOM_CORDIS_HOST_REQUIRED_ENV","pathToFileURL","withExtensionSource","findSyncedRoot","readSyncState","EXTERNAL_EXTENSIONS_ENV","alreadyComposed","COMPOSED_ENV","loadHarnessState","readBundleStatus","computeInputsHash"],"sources":["../../../src/adapters/composer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { loadMajorModesConfig, type MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { withExtensionSource } from '@agimon-ai/doompi-ui/extensionName';\nimport { type ExtensionAPI, parseArgs } from '@earendil-works/pi-coding-agent';\nimport { PERSONA_ENTRY, resolveExtensionComposition } from '../services/extensionAssembler.ts';\nimport type { HarnessPreset } from '../types/interfaces/harness';\nimport { findSyncedRoot, readBundleStatus } from './bootstrapLocator.ts';\nimport { alreadyComposed } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport { DOOM_CORDIS_HOST_REQUIRED_ENV } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { COMPOSED_ENV, EXTERNAL_EXTENSIONS_ENV, MUTE_ENV } from './compositionState.ts';\nimport {\n createHarnessSession,\n getHarnessState,\n type HarnessState,\n loadHarnessState,\n readHarnessState,\n updateHarnessState,\n} from './config/harnessState.ts';\nimport { configurePreset } from './harnessContext.ts';\nimport { computeInputsHash, createMapResolvers, readSyncState, runDirectory, type SyncState } from './syncState.ts';\n\nexport { findSyncedRoot } from './bootstrapLocator.ts';\nexport { alreadyComposed } from '@agimon-ai/doompi-extension-contracts/child-process';\nexport { COMPOSED_ENV, EXTERNAL_EXTENSIONS_ENV, MUTE_ENV } from './compositionState.ts';\n\n/**\n * The runtime half of `doom-pi sync`.\n *\n * Pi's own config carries a single entry, the doom-pi extension, and this is\n * what that entry runs: hydrate the harness environment, apply the startup\n * flags, then load the extension set the matrix selects. It runs again on every\n * `/reload`, which is what lets `/mode` swap the set without a restart,\n * since Pi rebuilds its extension runner from whatever a reload produced.\n */\n\nconst ENABLED_FLAG = '1';\nconst SUBAGENT_AGENT_DIRS_ENV = 'PI_SUBAGENT_EXTRA_AGENT_DIRS';\nconst SUBAGENT_SKILL_DIRS_ENV = 'PI_SUBAGENT_EXTRA_SKILL_DIRS';\nconst DOMAIN_SEPARATOR = ',';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst DOMAIN_APPLY_MODULE = '@agimon-ai/doompi-domain/apply';\nconst SELECTION_SWITCH_MODULE = '@agimon-ai/doompi-config/selectionSwitch';\nconst NOT_SYNCED = 'doompi is configured for Pi but this repository was never synced. Run doompi sync.';\nconst UNUSABLE_STATE_MESSAGE = 'doompi could not read its synchronized state. Run doompi sync.';\nconst TIMING_ENABLED = process.env.DOOMPI_TIMING === ENABLED_FLAG || process.env.PI_TIMING === ENABLED_FLAG;\n\nexport const DOOM_FLAGS = {\n majorMode: 'major-mode',\n domains: 'domains',\n profile: 'profile',\n mute: 'mute',\n} as const;\n\n/** Removed flag, reported rather than ignored when a synced session passes it. */\nexport const REMOVED_LAYER_FLAG = 'layer';\n\nexport interface StartupFlags {\n majorMode?: string;\n domains?: string[];\n profile?: string;\n mute: boolean;\n /** True when the caller passed the removed `--layer` flag. */\n removedLayer: boolean;\n}\n\nexport interface ComposeOutcome {\n /** Everything that went wrong, reported once the session can show messages. */\n problems: string[];\n /** Config changed since the last sync, which is a prompt to re-sync. */\n stale: boolean;\n loaded: string[];\n}\n\nfunction message(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction realPath(target: string): string {\n try {\n return fs.realpathSync(target);\n } catch {\n return path.resolve(target);\n }\n}\n\n/**\n * True when this process was handed the doom extension set on the command line.\n *\n * The launcher and detached Doom Team children both pass the composed list as\n * `--extension` arguments, and Pi merges those with whatever project settings\n * declare. Composing again here would load every extension twice, which Pi\n * reports as each tool and flag conflicting with itself.\n *\n * Matching against the synced paths rather than the mere presence of\n * `--extension` keeps an unrelated one-off, `pi -e ./debug.ts`, from silently\n * costing the user their whole setup.\n */\nexport function extensionsProvidedExternally(argv: string[], resolved: Record<string, string>): boolean {\n const { extensions } = parseArgs(argv);\n if (!extensions || extensions.length === 0) return false;\n const composed = new Set(Object.values(resolved).map(realPath));\n return extensions.some((entry) => composed.has(realPath(entry)));\n}\n\n/**\n * Opens this process's session from what sync pinned.\n *\n * The synced record is decoded underneath the live environment, so an inherited\n * value still wins: a launcher run, a nested session, or a CI override is more\n * specific than anything sync wrote. The result becomes the session's state\n * file, which is the authority from here on; the environment it publishes is\n * for the bash hooks and the packages that can only read one.\n */\nexport async function startSyncedSession(\n state: SyncState,\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<HarnessState> {\n const directory = await prepareRunDirectory(repoRoot, environment);\n const harness: HarnessState = {\n ...readHarnessState({ ...state.env, ...environment }),\n // Recorded separately because no variable carries them.\n ...state.fileState,\n // Every synced process gets private mutable staging. The source content\n // identity is unchanged because the projection fingerprint excludes it.\n mcpProjection: {\n ...state.fileState.mcpProjection,\n stagingDirectory: directory,\n },\n temporaryDirectory: directory,\n };\n createHarnessSession(harness, { directory, environment });\n // The profile's own defaults, which the launcher applies at spawn. They are\n // recorded as a map rather than as variables so /profile can tell which ones\n // it owns when it swaps them out later.\n for (const [key, value] of Object.entries(harness.profileEnvironment)) {\n if (environment[key] === undefined) environment[key] = value;\n }\n return harness;\n}\n\n/**\n * Reads the doom flags off the command line.\n *\n * Pi collects unrecognized flags for extensions but hands their values back\n * only after every extension has loaded, which is too late to decide what to\n * load. Parsing argv with Pi's own parser keeps the value rules identical.\n */\nexport function readStartupFlags(argv: string[]): StartupFlags {\n const { unknownFlags } = parseArgs(argv);\n const text = (name: string): string | undefined => {\n const value = unknownFlags.get(name);\n return typeof value === 'string' && value.trim() ? value.trim() : undefined;\n };\n const domains = text(DOOM_FLAGS.domains);\n return {\n majorMode: text(DOOM_FLAGS.majorMode),\n domains: domains\n ?.split(DOMAIN_SEPARATOR)\n .map((name) => name.trim())\n .filter(Boolean),\n profile: text(DOOM_FLAGS.profile),\n mute: unknownFlags.get(DOOM_FLAGS.mute) !== undefined,\n removedLayer: unknownFlags.get(REMOVED_LAYER_FLAG) !== undefined,\n };\n}\n\n/** Registers the flags so Pi accepts them and lists them in `pi --help`. */\nexport function registerDoomFlags(pi: Pick<ExtensionAPI, 'registerFlag'>): void {\n pi.registerFlag(DOOM_FLAGS.majorMode, {\n type: 'string',\n description: 'Named major mode from .doom/modes.yaml',\n });\n pi.registerFlag(DOOM_FLAGS.domains, { type: 'string', description: 'Comma-separated content domains' });\n pi.registerFlag(DOOM_FLAGS.profile, { type: 'string', description: 'Persona and env from .doom/profiles.yaml' });\n pi.registerFlag(DOOM_FLAGS.mute, { type: 'boolean', description: 'Disable the notification extension' });\n}\n\n/**\n * Applies the startup flags through the same switchers the slash commands use.\n *\n * A bad value is collected rather than thrown: a factory that throws takes the\n * whole doom setup down with it, which is far worse than one unapplied flag.\n */\nexport async function applyStartupFlags(\n flags: StartupFlags,\n majorModesConfig: MajorModesConfig,\n repoRoot: string,\n problems: string[],\n): Promise<void> {\n // Reported rather than ignored: Pi hands unrecognized flags to extensions, so\n // a stale `--layer dev` would otherwise start a session on the wrong mode\n // without saying anything.\n if (flags.removedLayer) problems.push(`--${REMOVED_LAYER_FLAG} was replaced by --${DOOM_FLAGS.majorMode}`);\n if (flags.majorMode) {\n try {\n const { applyMajorMode } = await import(SELECTION_SWITCH_MODULE);\n applyMajorMode(majorModesConfig, flags.majorMode, getHarnessState());\n } catch (error) {\n problems.push(`--${DOOM_FLAGS.majorMode} ${flags.majorMode}: ${message(error)}`);\n }\n }\n if (flags.domains && flags.domains.length > 0) {\n try {\n const { applyDomains } = await import(DOMAIN_APPLY_MODULE);\n await applyDomains(flags.domains, getHarnessState());\n } catch (error) {\n problems.push(`--domains ${flags.domains.join(DOMAIN_SEPARATOR)}: ${message(error)}`);\n }\n }\n if (flags.profile) {\n try {\n const [{ applyProfile }, { resolveProfile }] = await Promise.all([\n import(SELECTION_SWITCH_MODULE),\n import('@agimon-ai/doompi-config/profiles'),\n ]);\n await applyProfile(resolveProfile(repoRoot, flags.profile), getHarnessState());\n } catch (error) {\n problems.push(`--profile ${flags.profile}: ${message(error)}`);\n }\n }\n}\n\n/**\n * Points harness state at a per-process directory before anything writes there.\n *\n * The synced directory is a baseline that every session reads; live switches\n * materialize into this one instead, so a second session in the same repository\n * cannot overwrite what the first is using.\n */\nexport async function prepareRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string> {\n const directory = runDirectory(repoRoot, process.pid, environment.HOME ?? os.homedir());\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n // Through the store rather than the environment: the state file is the\n // authority, and a switcher reading a stale directory would write a live\n // session's resources into the wrong place.\n updateHarnessState({ temporaryDirectory: directory }, environment);\n return directory;\n}\n\n/** Removes this session's scratch directory. */\nexport async function cleanupRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<void> {\n await fs.promises.rm(runDirectory(repoRoot, process.pid, environment.HOME ?? os.homedir()), {\n recursive: true,\n force: true,\n });\n}\n\n/**\n * Builds the ordered list of extensions to load for the current state.\n *\n * Returns entries in actual Pi factory activation order. CLI argument adaptation\n * remains a launcher concern and synchronized bundles preserve this same order.\n */\nexport interface ComposedRuntimeLoadPlan {\n readonly entries: string[];\n readonly fingerprint: string;\n}\n\n/** Builds the canonical synchronized activation plan. */\nexport async function composeRuntimeLoadPlan(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<ComposedRuntimeLoadPlan> {\n const repoRoot = harness.root ?? state.root;\n const resolvers = createMapResolvers(state.resolved, state.compiled);\n const context = {\n agents: harness.agents,\n // Exiting when the agent settles is a launcher decision with no synced\n // equivalent: a session started by hand is meant to stay open.\n autoStop: false,\n preset: state.selection.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorMode: harness.majorMode,\n layers: [...harness.layers],\n majorModesConfig: loadMajorModesConfig(repoRoot),\n resolvers,\n };\n\n // A state field, so it goes through the store: Doom Team reads it from the\n // environment the store publishes when it builds a child's spawn snapshot.\n const composition = resolveExtensionComposition({\n ...context,\n mute: environment[MUTE_ENV] === ENABLED_FLAG,\n });\n environment[DOOM_CORDIS_HOST_REQUIRED_ENV] = ENABLED_FLAG;\n updateHarnessState(\n {\n childExtensions: [...composition.childActivation],\n compositionFingerprint: composition.fingerprint,\n },\n environment,\n );\n if (harness.agents) {\n environment[SUBAGENT_AGENT_DIRS_ENV] = harness.agentDirectories.join(path.delimiter);\n environment[SUBAGENT_SKILL_DIRS_ENV] = harness.skillDirectories.join(path.delimiter);\n }\n const entries = [...composition.parentActivation];\n const bundle = state.bundles?.[composition.fingerprint];\n return {\n entries: bundle ? [bundle] : entries,\n fingerprint: composition.fingerprint,\n };\n}\n\n/** Compatibility view for callers that only need Pi extension entries. */\nexport async function composeLoadOrder(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string[]> {\n return (await composeRuntimeLoadPlan(state, harness, environment)).entries;\n}\n\n/**\n * Imports each extension and hands it a source-aware view of the Pi API.\n *\n * Every Pi extension entry is a factory of the same shape, so one extension can\n * activate the rest. The view delegates to the same host API while retaining the\n * entry path for tools registered through a composed loader. Failures are per\n * entry: one unloadable module must not cost the session everything after it.\n */\nexport async function loadComposedExtensions(\n pi: ExtensionAPI,\n entries: string[],\n problems: string[],\n): Promise<string[]> {\n const loaded: string[] = [];\n for (const entry of entries) {\n const importStartedAt = performance.now();\n let module: { default?: unknown };\n try {\n module = (await import(pathToFileURL(entry).href)) as { default?: unknown };\n if (TIMING_ENABLED) {\n process.stderr.write(`[doompi:timing] import ${(performance.now() - importStartedAt).toFixed(1)}ms ${entry}\\n`);\n }\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n continue;\n }\n if (typeof module.default !== 'function') {\n problems.push(`${entry}: does not export an extension factory`);\n continue;\n }\n const factoryStartedAt = performance.now();\n try {\n await (module.default as (api: ExtensionAPI) => void | Promise<void>)(withExtensionSource(pi, entry));\n if (TIMING_ENABLED) {\n process.stderr.write(\n `[doompi:timing] factory ${(performance.now() - factoryStartedAt).toFixed(1)}ms ${entry}\\n`,\n );\n }\n loaded.push(entry);\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n }\n }\n return loaded;\n}\n\nexport interface ComposeOptions {\n cwd?: string;\n argv?: string[];\n environment?: NodeJS.ProcessEnv;\n}\n\n/**\n * Runs the whole synced startup for one Pi session.\n *\n * Never throws: whatever fails is reported through the outcome so the session\n * still starts and can say what is wrong.\n */\nexport async function composeDoomSession(pi: ExtensionAPI, options: ComposeOptions = {}): Promise<ComposeOutcome> {\n const environment = options.environment ?? process.env;\n const problems: string[] = [];\n const homeDirectory = environment.HOME ?? os.homedir();\n const repoRoot = findSyncedRoot(options.cwd ?? process.cwd(), homeDirectory);\n if (!repoRoot) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n let state: SyncState | undefined;\n try {\n state = readSyncState(repoRoot, homeDirectory);\n } catch (error) {\n return { problems: [message(error)], stale: false, loaded: [] };\n }\n if (!state) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n const argv = options.argv ?? process.argv.slice(2);\n // The launcher and Doom Team children compose the set themselves and pass it\n // in, so this entry has nothing left to do but stay out of the way.\n if (\n environment[EXTERNAL_EXTENSIONS_ENV] === ENABLED_FLAG ||\n extensionsProvidedExternally(argv, { ...state.resolved, ...state.bundles })\n ) {\n return { problems: [], stale: false, loaded: [] };\n }\n\n configurePreset({ preset: state.selection.preset as HarnessPreset, piArgs: [] }, environment);\n\n // Only the first load opens a session. A reload finds the pointer this set\n // and reads the file back, which is what carries a live /domains or /profile\n // switch across the reload instead of resetting it to what sync pinned.\n if (!alreadyComposed(environment)) {\n environment[COMPOSED_ENV] = ENABLED_FLAG;\n await startSyncedSession(state, repoRoot, environment);\n const flags = readStartupFlags(argv);\n if (flags.mute) environment[MUTE_ENV] = ENABLED_FLAG;\n await applyStartupFlags(flags, loadMajorModesConfig(repoRoot), repoRoot, problems);\n }\n\n let loadPlan: ComposedRuntimeLoadPlan;\n try {\n // Read back through the store rather than from this process: the startup\n // flags may have switched the layer or the domains a moment ago, and the\n // environment being managed is not always this process's own.\n loadPlan = await composeRuntimeLoadPlan(state, loadHarnessState(environment).state, environment);\n } catch (error) {\n return { problems: [...problems, message(error)], stale: false, loaded: [] };\n }\n\n const selectedBundle = state.bundles?.[loadPlan.fingerprint];\n if (selectedBundle) {\n try {\n const status = readBundleStatus(repoRoot, loadPlan.fingerprint, homeDirectory);\n if (!status.fresh || status.bundle !== selectedBundle) {\n return { problems: [...problems, UNUSABLE_STATE_MESSAGE], stale: false, loaded: [] };\n }\n } catch {\n return { problems: [...problems, UNUSABLE_STATE_MESSAGE], stale: false, loaded: [] };\n }\n }\n\n const loaded = await loadComposedExtensions(pi, loadPlan.entries, problems);\n return {\n problems,\n stale:\n computeInputsHash(repoRoot, state.selection, homeDirectory) !== state.inputsHash ||\n (loadHarnessState(environment).state.majorMode === state.selection.majorMode &&\n loadPlan.fingerprint !== state.compositionFingerprint),\n loaded,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"composer.cjs","names":["fs","path","parseArgs","readHarnessState","getHarnessState","runDirectory","os","createMapResolvers","PERSONA_ENTRY","loadMajorModesConfig","resolveExtensionComposition","MUTE_ENV","DOOM_CORDIS_HOST_REQUIRED_ENV","packageAttribution","pathToFileURL","withExtensionSource","findSyncedRoot","readSyncState","EXTERNAL_EXTENSIONS_ENV","alreadyComposed","COMPOSED_ENV","loadHarnessState","readBundleStatus","computeInputsHash"],"sources":["../../../src/adapters/composer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { loadMajorModesConfig, type MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport { withExtensionSource } from '@agimon-ai/doompi-ui/extensionName';\nimport { type ExtensionAPI, parseArgs } from '@earendil-works/pi-coding-agent';\nimport { PERSONA_ENTRY, packageAttribution, resolveExtensionComposition } from '../services/extensionAssembler.ts';\nimport type { HarnessPreset } from '../types/interfaces/harness';\nimport { findSyncedRoot, readBundleStatus } from './bootstrapLocator.ts';\nimport { alreadyComposed } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport { DOOM_CORDIS_HOST_REQUIRED_ENV } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { COMPOSED_ENV, EXTERNAL_EXTENSIONS_ENV, MUTE_ENV } from './compositionState.ts';\nimport {\n createHarnessSession,\n getHarnessState,\n type HarnessState,\n loadHarnessState,\n readHarnessState,\n updateHarnessState,\n} from './config/harnessState.ts';\nimport { configurePreset } from './harnessContext.ts';\nimport { computeInputsHash, createMapResolvers, readSyncState, runDirectory, type SyncState } from './syncState.ts';\n\nexport { findSyncedRoot } from './bootstrapLocator.ts';\nexport { alreadyComposed } from '@agimon-ai/doompi-extension-contracts/child-process';\nexport { COMPOSED_ENV, EXTERNAL_EXTENSIONS_ENV, MUTE_ENV } from './compositionState.ts';\n\n/**\n * The runtime half of `doom-pi sync`.\n *\n * Pi's own config carries a single entry, the doom-pi extension, and this is\n * what that entry runs: hydrate the harness environment, apply the startup\n * flags, then load the extension set the matrix selects. It runs again on every\n * `/reload`, which is what lets `/mode` swap the set without a restart,\n * since Pi rebuilds its extension runner from whatever a reload produced.\n */\n\nconst ENABLED_FLAG = '1';\nconst SUBAGENT_AGENT_DIRS_ENV = 'PI_SUBAGENT_EXTRA_AGENT_DIRS';\nconst SUBAGENT_SKILL_DIRS_ENV = 'PI_SUBAGENT_EXTRA_SKILL_DIRS';\nconst DOMAIN_SEPARATOR = ',';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst DOMAIN_APPLY_MODULE = '@agimon-ai/doompi-domain/apply';\nconst SELECTION_SWITCH_MODULE = '@agimon-ai/doompi-config/selectionSwitch';\nconst NOT_SYNCED = 'doompi is configured for Pi but this repository was never synced. Run doompi sync.';\nconst UNUSABLE_STATE_MESSAGE = 'doompi could not read its synchronized state. Run doompi sync.';\nconst TIMING_ENABLED = process.env.DOOMPI_TIMING === ENABLED_FLAG || process.env.PI_TIMING === ENABLED_FLAG;\n\nexport const DOOM_FLAGS = {\n majorMode: 'major-mode',\n domains: 'domains',\n profile: 'profile',\n mute: 'mute',\n} as const;\n\n/** Removed flag, reported rather than ignored when a synced session passes it. */\nexport const REMOVED_LAYER_FLAG = 'layer';\n\nexport interface StartupFlags {\n majorMode?: string;\n domains?: string[];\n profile?: string;\n mute: boolean;\n /** True when the caller passed the removed `--layer` flag. */\n removedLayer: boolean;\n}\n\nexport interface ComposeOutcome {\n /** Everything that went wrong, reported once the session can show messages. */\n problems: string[];\n /** Config changed since the last sync, which is a prompt to re-sync. */\n stale: boolean;\n loaded: string[];\n}\n\nfunction message(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction realPath(target: string): string {\n try {\n return fs.realpathSync(target);\n } catch {\n return path.resolve(target);\n }\n}\n\n/**\n * True when this process was handed the doom extension set on the command line.\n *\n * The launcher and detached Doom Team children both pass the composed list as\n * `--extension` arguments, and Pi merges those with whatever project settings\n * declare. Composing again here would load every extension twice, which Pi\n * reports as each tool and flag conflicting with itself.\n *\n * Matching against the synced paths rather than the mere presence of\n * `--extension` keeps an unrelated one-off, `pi -e ./debug.ts`, from silently\n * costing the user their whole setup.\n */\nexport function extensionsProvidedExternally(argv: string[], resolved: Record<string, string>): boolean {\n const { extensions } = parseArgs(argv);\n if (!extensions || extensions.length === 0) return false;\n const composed = new Set(Object.values(resolved).map(realPath));\n return extensions.some((entry) => composed.has(realPath(entry)));\n}\n\n/**\n * Opens this process's session from what sync pinned.\n *\n * The synced record is decoded underneath the live environment, so an inherited\n * value still wins: a launcher run, a nested session, or a CI override is more\n * specific than anything sync wrote. The result becomes the session's state\n * file, which is the authority from here on; the environment it publishes is\n * for the bash hooks and the packages that can only read one.\n */\nexport async function startSyncedSession(\n state: SyncState,\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<HarnessState> {\n const directory = await prepareRunDirectory(repoRoot, environment);\n const harness: HarnessState = {\n ...readHarnessState({ ...state.env, ...environment }),\n // Recorded separately because no variable carries them.\n ...state.fileState,\n // Every synced process gets private mutable staging. The source content\n // identity is unchanged because the projection fingerprint excludes it.\n mcpProjection: {\n ...state.fileState.mcpProjection,\n stagingDirectory: directory,\n },\n temporaryDirectory: directory,\n };\n createHarnessSession(harness, { directory, environment });\n // The profile's own defaults, which the launcher applies at spawn. They are\n // recorded as a map rather than as variables so /profile can tell which ones\n // it owns when it swaps them out later.\n for (const [key, value] of Object.entries(harness.profileEnvironment)) {\n if (environment[key] === undefined) environment[key] = value;\n }\n return harness;\n}\n\n/**\n * Reads the doom flags off the command line.\n *\n * Pi collects unrecognized flags for extensions but hands their values back\n * only after every extension has loaded, which is too late to decide what to\n * load. Parsing argv with Pi's own parser keeps the value rules identical.\n */\nexport function readStartupFlags(argv: string[]): StartupFlags {\n const { unknownFlags } = parseArgs(argv);\n const text = (name: string): string | undefined => {\n const value = unknownFlags.get(name);\n return typeof value === 'string' && value.trim() ? value.trim() : undefined;\n };\n const domains = text(DOOM_FLAGS.domains);\n return {\n majorMode: text(DOOM_FLAGS.majorMode),\n domains: domains\n ?.split(DOMAIN_SEPARATOR)\n .map((name) => name.trim())\n .filter(Boolean),\n profile: text(DOOM_FLAGS.profile),\n mute: unknownFlags.get(DOOM_FLAGS.mute) !== undefined,\n removedLayer: unknownFlags.get(REMOVED_LAYER_FLAG) !== undefined,\n };\n}\n\n/** Registers the flags so Pi accepts them and lists them in `pi --help`. */\nexport function registerDoomFlags(pi: Pick<ExtensionAPI, 'registerFlag'>): void {\n pi.registerFlag(DOOM_FLAGS.majorMode, {\n type: 'string',\n description: 'Named major mode from .doom/modes.yaml',\n });\n pi.registerFlag(DOOM_FLAGS.domains, { type: 'string', description: 'Comma-separated content domains' });\n pi.registerFlag(DOOM_FLAGS.profile, { type: 'string', description: 'Persona and env from .doom/profiles.yaml' });\n pi.registerFlag(DOOM_FLAGS.mute, { type: 'boolean', description: 'Disable the notification extension' });\n}\n\n/**\n * Applies the startup flags through the same switchers the slash commands use.\n *\n * A bad value is collected rather than thrown: a factory that throws takes the\n * whole doom setup down with it, which is far worse than one unapplied flag.\n */\nexport async function applyStartupFlags(\n flags: StartupFlags,\n majorModesConfig: MajorModesConfig,\n repoRoot: string,\n problems: string[],\n): Promise<void> {\n // Reported rather than ignored: Pi hands unrecognized flags to extensions, so\n // a stale `--layer dev` would otherwise start a session on the wrong mode\n // without saying anything.\n if (flags.removedLayer) problems.push(`--${REMOVED_LAYER_FLAG} was replaced by --${DOOM_FLAGS.majorMode}`);\n if (flags.majorMode) {\n try {\n const { applyMajorMode } = await import(SELECTION_SWITCH_MODULE);\n applyMajorMode(majorModesConfig, flags.majorMode, getHarnessState());\n } catch (error) {\n problems.push(`--${DOOM_FLAGS.majorMode} ${flags.majorMode}: ${message(error)}`);\n }\n }\n if (flags.domains && flags.domains.length > 0) {\n try {\n const { applyDomains } = await import(DOMAIN_APPLY_MODULE);\n await applyDomains(flags.domains, getHarnessState());\n } catch (error) {\n problems.push(`--domains ${flags.domains.join(DOMAIN_SEPARATOR)}: ${message(error)}`);\n }\n }\n if (flags.profile) {\n try {\n const [{ applyProfile }, { resolveProfile }] = await Promise.all([\n import(SELECTION_SWITCH_MODULE),\n import('@agimon-ai/doompi-config/profiles'),\n ]);\n await applyProfile(resolveProfile(repoRoot, flags.profile), getHarnessState());\n } catch (error) {\n problems.push(`--profile ${flags.profile}: ${message(error)}`);\n }\n }\n}\n\n/**\n * Points harness state at a per-process directory before anything writes there.\n *\n * The synced directory is a baseline that every session reads; live switches\n * materialize into this one instead, so a second session in the same repository\n * cannot overwrite what the first is using.\n */\nexport async function prepareRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string> {\n const directory = runDirectory(repoRoot, process.pid, environment.HOME ?? os.homedir());\n await fs.promises.mkdir(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n // Through the store rather than the environment: the state file is the\n // authority, and a switcher reading a stale directory would write a live\n // session's resources into the wrong place.\n updateHarnessState({ temporaryDirectory: directory }, environment);\n return directory;\n}\n\n/** Removes this session's scratch directory. */\nexport async function cleanupRunDirectory(\n repoRoot: string,\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<void> {\n await fs.promises.rm(runDirectory(repoRoot, process.pid, environment.HOME ?? os.homedir()), {\n recursive: true,\n force: true,\n });\n}\n\n/**\n * Builds the ordered list of extensions to load for the current state.\n *\n * Returns entries in actual Pi factory activation order. CLI argument adaptation\n * remains a launcher concern and synchronized bundles preserve this same order.\n */\nexport interface ComposedRuntimeLoadPlan {\n readonly entries: string[];\n readonly fingerprint: string;\n}\n\n/** Builds the canonical synchronized activation plan. */\nexport async function composeRuntimeLoadPlan(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<ComposedRuntimeLoadPlan> {\n const repoRoot = harness.root ?? state.root;\n const resolvers = createMapResolvers(state.resolved, state.compiled);\n const context = {\n agents: harness.agents,\n // Exiting when the agent settles is a launcher decision with no synced\n // equivalent: a session started by hand is meant to stay open.\n autoStop: false,\n preset: state.selection.preset,\n personaEntry: resolvers.packageEntry(PERSONA_ENTRY),\n majorMode: harness.majorMode,\n layers: [...harness.layers],\n majorModesConfig: loadMajorModesConfig(repoRoot),\n resolvers,\n };\n\n // A state field, so it goes through the store: Doom Team reads it from the\n // environment the store publishes when it builds a child's spawn snapshot.\n const composition = resolveExtensionComposition({\n ...context,\n mute: environment[MUTE_ENV] === ENABLED_FLAG,\n });\n environment[DOOM_CORDIS_HOST_REQUIRED_ENV] = ENABLED_FLAG;\n updateHarnessState(\n {\n childExtensions: [...composition.childActivation],\n compositionFingerprint: composition.fingerprint,\n packageAttribution: packageAttribution(composition),\n },\n environment,\n );\n if (harness.agents) {\n environment[SUBAGENT_AGENT_DIRS_ENV] = harness.agentDirectories.join(path.delimiter);\n environment[SUBAGENT_SKILL_DIRS_ENV] = harness.skillDirectories.join(path.delimiter);\n }\n const entries = [...composition.parentActivation];\n const bundle = state.bundles?.[composition.fingerprint];\n return {\n entries: bundle ? [bundle] : entries,\n fingerprint: composition.fingerprint,\n };\n}\n\n/** Compatibility view for callers that only need Pi extension entries. */\nexport async function composeLoadOrder(\n state: SyncState,\n harness: HarnessState = getHarnessState(),\n environment: NodeJS.ProcessEnv = process.env,\n): Promise<string[]> {\n return (await composeRuntimeLoadPlan(state, harness, environment)).entries;\n}\n\n/**\n * Imports each extension and hands it a source-aware view of the Pi API.\n *\n * Every Pi extension entry is a factory of the same shape, so one extension can\n * activate the rest. The view delegates to the same host API while retaining the\n * entry path for tools registered through a composed loader. Failures are per\n * entry: one unloadable module must not cost the session everything after it.\n */\nexport async function loadComposedExtensions(\n pi: ExtensionAPI,\n entries: string[],\n problems: string[],\n): Promise<string[]> {\n const loaded: string[] = [];\n for (const entry of entries) {\n const importStartedAt = performance.now();\n let module: { default?: unknown };\n try {\n module = (await import(pathToFileURL(entry).href)) as { default?: unknown };\n if (TIMING_ENABLED) {\n process.stderr.write(`[doompi:timing] import ${(performance.now() - importStartedAt).toFixed(1)}ms ${entry}\\n`);\n }\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n continue;\n }\n if (typeof module.default !== 'function') {\n problems.push(`${entry}: does not export an extension factory`);\n continue;\n }\n const factoryStartedAt = performance.now();\n try {\n await (module.default as (api: ExtensionAPI) => void | Promise<void>)(withExtensionSource(pi, entry));\n if (TIMING_ENABLED) {\n process.stderr.write(\n `[doompi:timing] factory ${(performance.now() - factoryStartedAt).toFixed(1)}ms ${entry}\\n`,\n );\n }\n loaded.push(entry);\n } catch (error) {\n problems.push(`${entry}: ${message(error)}`);\n }\n }\n return loaded;\n}\n\nexport interface ComposeOptions {\n cwd?: string;\n argv?: string[];\n environment?: NodeJS.ProcessEnv;\n}\n\n/**\n * Runs the whole synced startup for one Pi session.\n *\n * Never throws: whatever fails is reported through the outcome so the session\n * still starts and can say what is wrong.\n */\nexport async function composeDoomSession(pi: ExtensionAPI, options: ComposeOptions = {}): Promise<ComposeOutcome> {\n const environment = options.environment ?? process.env;\n const problems: string[] = [];\n const homeDirectory = environment.HOME ?? os.homedir();\n const repoRoot = findSyncedRoot(options.cwd ?? process.cwd(), homeDirectory);\n if (!repoRoot) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n let state: SyncState | undefined;\n try {\n state = readSyncState(repoRoot, homeDirectory);\n } catch (error) {\n return { problems: [message(error)], stale: false, loaded: [] };\n }\n if (!state) return { problems: [NOT_SYNCED], stale: false, loaded: [] };\n\n const argv = options.argv ?? process.argv.slice(2);\n // The launcher and Doom Team children compose the set themselves and pass it\n // in, so this entry has nothing left to do but stay out of the way.\n if (\n environment[EXTERNAL_EXTENSIONS_ENV] === ENABLED_FLAG ||\n extensionsProvidedExternally(argv, { ...state.resolved, ...state.bundles })\n ) {\n return { problems: [], stale: false, loaded: [] };\n }\n\n configurePreset({ preset: state.selection.preset as HarnessPreset, piArgs: [] }, environment);\n\n // Only the first load opens a session. A reload finds the pointer this set\n // and reads the file back, which is what carries a live /domains or /profile\n // switch across the reload instead of resetting it to what sync pinned.\n if (!alreadyComposed(environment)) {\n environment[COMPOSED_ENV] = ENABLED_FLAG;\n await startSyncedSession(state, repoRoot, environment);\n const flags = readStartupFlags(argv);\n if (flags.mute) environment[MUTE_ENV] = ENABLED_FLAG;\n await applyStartupFlags(flags, loadMajorModesConfig(repoRoot), repoRoot, problems);\n }\n\n let loadPlan: ComposedRuntimeLoadPlan;\n try {\n // Read back through the store rather than from this process: the startup\n // flags may have switched the layer or the domains a moment ago, and the\n // environment being managed is not always this process's own.\n loadPlan = await composeRuntimeLoadPlan(state, loadHarnessState(environment).state, environment);\n } catch (error) {\n return { problems: [...problems, message(error)], stale: false, loaded: [] };\n }\n\n const selectedBundle = state.bundles?.[loadPlan.fingerprint];\n if (selectedBundle) {\n try {\n const status = readBundleStatus(repoRoot, loadPlan.fingerprint, homeDirectory);\n if (!status.fresh || status.bundle !== selectedBundle) {\n return { problems: [...problems, UNUSABLE_STATE_MESSAGE], stale: false, loaded: [] };\n }\n } catch {\n return { problems: [...problems, UNUSABLE_STATE_MESSAGE], stale: false, loaded: [] };\n }\n }\n\n const loaded = await loadComposedExtensions(pi, loadPlan.entries, problems);\n return {\n problems,\n stale:\n computeInputsHash(repoRoot, state.selection, homeDirectory) !== state.inputsHash ||\n (loadHarnessState(environment).state.majorMode === state.selection.majorMode &&\n loadPlan.fingerprint !== state.compositionFingerprint),\n loaded,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"composer.d.cts","names":[],"sources":["../../../src/adapters/composer.ts"],"mappings":";;;;;;;;cAiDa;WACX;WACA;WACA;WACA;;;cAIW;UAEI;EACf;EACA;EACA;EACA;;EAEA;;UAGe;;EAEf;;EAEA;EACA;;;;;;;;;;;;;;iBA2Bc,6BAA6B,gBAAgB,UAAU;;;;;;;;;;iBAgBjD,mBACpB,OAAO,WACP,kBACA,cAAa,OAAO,aACnB,QAAQ;;;;;;;;iBA+BK,iBAAiB,iBAAiB;;iBAoBlC,kBAAkB,IAAI,KAAK;;;;;;;iBAgBrB,kBACpB,OAAO,cACP,kBAAkB,kBAClB,kBACA,qBACC;;;;;;;;iBAyCmB,oBACpB,kBACA,cAAa,OAAO,aACnB;;iBAWmB,oBACpB,kBACA,cAAa,OAAO,aACnB;;;;;;;UAac;WACN;WACA;;;iBAIW,uBACpB,OAAO,WACP,UAAS,cACT,cAAa,OAAO,aACnB,QAAQ;;iBA2CW,iBACpB,OAAO,WACP,UAAS,cACT,cAAa,OAAO,aACnB;;;;;;;;;iBAYmB,uBACpB,IAAI,cACJ,mBACA,qBACC;UAkCc;EACf;EACA;EACA,cAAc,OAAO;;;;;;;;iBASD,mBAAmB,IAAI,cAAc,UAAS,iBAAsB,QAAQ"}
1
+ {"version":3,"file":"composer.d.cts","names":[],"sources":["../../../src/adapters/composer.ts"],"mappings":";;;;;;;;cAiDa;WACX;WACA;WACA;WACA;;;cAIW;UAEI;EACf;EACA;EACA;EACA;;EAEA;;UAGe;;EAEf;;EAEA;EACA;;;;;;;;;;;;;;iBA2Bc,6BAA6B,gBAAgB,UAAU;;;;;;;;;;iBAgBjD,mBACpB,OAAO,WACP,kBACA,cAAa,OAAO,aACnB,QAAQ;;;;;;;;iBA+BK,iBAAiB,iBAAiB;;iBAoBlC,kBAAkB,IAAI,KAAK;;;;;;;iBAgBrB,kBACpB,OAAO,cACP,kBAAkB,kBAClB,kBACA,qBACC;;;;;;;;iBAyCmB,oBACpB,kBACA,cAAa,OAAO,aACnB;;iBAWmB,oBACpB,kBACA,cAAa,OAAO,aACnB;;;;;;;UAac;WACN;WACA;;;iBAIW,uBACpB,OAAO,WACP,UAAS,cACT,cAAa,OAAO,aACnB,QAAQ;;iBA4CW,iBACpB,OAAO,WACP,UAAS,cACT,cAAa,OAAO,aACnB;;;;;;;;;iBAYmB,uBACpB,IAAI,cACJ,mBACA,qBACC;UAkCc;EACf;EACA;EACA,cAAc,OAAO;;;;;;;;iBASD,mBAAmB,IAAI,cAAc,UAAS,iBAAsB,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"composer.d.mts","names":[],"sources":["../../../src/adapters/composer.ts"],"mappings":";;;;;;;;cAiDa;WACX;WACA;WACA;WACA;;;cAIW;UAEI;EACf;EACA;EACA;EACA;;EAEA;;UAGe;;EAEf;;EAEA;EACA;;;;;;;;;;;;;;iBA2Bc,6BAA6B,gBAAgB,UAAU;;;;;;;;;;iBAgBjD,mBACpB,OAAO,WACP,kBACA,cAAa,OAAO,aACnB,QAAQ;;;;;;;;iBA+BK,iBAAiB,iBAAiB;;iBAoBlC,kBAAkB,IAAI,KAAK;;;;;;;iBAgBrB,kBACpB,OAAO,cACP,kBAAkB,kBAClB,kBACA,qBACC;;;;;;;;iBAyCmB,oBACpB,kBACA,cAAa,OAAO,aACnB;;iBAWmB,oBACpB,kBACA,cAAa,OAAO,aACnB;;;;;;;UAac;WACN;WACA;;;iBAIW,uBACpB,OAAO,WACP,UAAS,cACT,cAAa,OAAO,aACnB,QAAQ;;iBA2CW,iBACpB,OAAO,WACP,UAAS,cACT,cAAa,OAAO,aACnB;;;;;;;;;iBAYmB,uBACpB,IAAI,cACJ,mBACA,qBACC;UAkCc;EACf;EACA;EACA,cAAc,OAAO;;;;;;;;iBASD,mBAAmB,IAAI,cAAc,UAAS,iBAAsB,QAAQ"}
1
+ {"version":3,"file":"composer.d.mts","names":[],"sources":["../../../src/adapters/composer.ts"],"mappings":";;;;;;;;cAiDa;WACX;WACA;WACA;WACA;;;cAIW;UAEI;EACf;EACA;EACA;EACA;;EAEA;;UAGe;;EAEf;;EAEA;EACA;;;;;;;;;;;;;;iBA2Bc,6BAA6B,gBAAgB,UAAU;;;;;;;;;;iBAgBjD,mBACpB,OAAO,WACP,kBACA,cAAa,OAAO,aACnB,QAAQ;;;;;;;;iBA+BK,iBAAiB,iBAAiB;;iBAoBlC,kBAAkB,IAAI,KAAK;;;;;;;iBAgBrB,kBACpB,OAAO,cACP,kBAAkB,kBAClB,kBACA,qBACC;;;;;;;;iBAyCmB,oBACpB,kBACA,cAAa,OAAO,aACnB;;iBAWmB,oBACpB,kBACA,cAAa,OAAO,aACnB;;;;;;;UAac;WACN;WACA;;;iBAIW,uBACpB,OAAO,WACP,UAAS,cACT,cAAa,OAAO,aACnB,QAAQ;;iBA4CW,iBACpB,OAAO,WACP,UAAS,cACT,cAAa,OAAO,aACnB;;;;;;;;;iBAYmB,uBACpB,IAAI,cACJ,mBACA,qBACC;UAkCc;EACf;EACA;EACA,cAAc,OAAO;;;;;;;;iBASD,mBAAmB,IAAI,cAAc,UAAS,iBAAsB,QAAQ"}
@@ -1,2 +1,2 @@
1
- import{PERSONA_ENTRY as e,resolveExtensionComposition as t}from"../services/extensionAssembler.mjs";import{computeInputsHash as n,createMapResolvers as r,readSyncState as i,runDirectory as a}from"./syncState.mjs";import{findSyncedRoot as o,readBundleStatus as s}from"./bootstrapLocator.mjs";import{createHarnessSession as c,getHarnessState as l,loadHarnessState as u,readHarnessState as d,updateHarnessState as f}from"./config/harnessState.mjs";import{configurePreset as p}from"./harnessContext.mjs";import{COMPOSED_ENV as m,EXTERNAL_EXTENSIONS_ENV as h,MUTE_ENV as g}from"./compositionState.mjs";import{pathToFileURL as _}from"node:url";import{alreadyComposed as v,alreadyComposed as y}from"@agimon-ai/doompi-extension-contracts/child-process";import b from"node:path";import x from"node:fs";import S from"node:os";import{loadMajorModesConfig as C}from"@agimon-ai/doompi-config/majorModes";import{DOOM_CORDIS_HOST_REQUIRED_ENV as w}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{parseArgs as T}from"@earendil-works/pi-coding-agent";import{withExtensionSource as E}from"@agimon-ai/doompi-ui/extensionName";const D=`@agimon-ai/doompi-config/selectionSwitch`,O=`doompi is configured for Pi but this repository was never synced. Run doompi sync.`,k=`doompi could not read its synchronized state. Run doompi sync.`,A=process.env.DOOMPI_TIMING===`1`||process.env.PI_TIMING===`1`,j={majorMode:`major-mode`,domains:`domains`,profile:`profile`,mute:`mute`},M=`layer`;function N(e){return e instanceof Error?e.message:String(e)}function P(e){try{return x.realpathSync(e)}catch{return b.resolve(e)}}function F(e,t){let{extensions:n}=T(e);if(!n||n.length===0)return!1;let r=new Set(Object.values(t).map(P));return n.some(e=>r.has(P(e)))}async function I(e,t,n=process.env){let r=await B(t,n),i={...d({...e.env,...n}),...e.fileState,mcpProjection:{...e.fileState.mcpProjection,stagingDirectory:r},temporaryDirectory:r};c(i,{directory:r,environment:n});for(let[e,t]of Object.entries(i.profileEnvironment))n[e]===void 0&&(n[e]=t);return i}function L(e){let{unknownFlags:t}=T(e),n=e=>{let n=t.get(e);return typeof n==`string`&&n.trim()?n.trim():void 0},r=n(j.domains);return{majorMode:n(j.majorMode),domains:r?.split(`,`).map(e=>e.trim()).filter(Boolean),profile:n(j.profile),mute:t.get(j.mute)!==void 0,removedLayer:t.get(M)!==void 0}}function R(e){e.registerFlag(j.majorMode,{type:`string`,description:`Named major mode from .doom/modes.yaml`}),e.registerFlag(j.domains,{type:`string`,description:`Comma-separated content domains`}),e.registerFlag(j.profile,{type:`string`,description:`Persona and env from .doom/profiles.yaml`}),e.registerFlag(j.mute,{type:`boolean`,description:`Disable the notification extension`})}async function z(e,t,n,r){if(e.removedLayer&&r.push(`--${M} was replaced by --${j.majorMode}`),e.majorMode)try{let{applyMajorMode:n}=await import(D);n(t,e.majorMode,l())}catch(t){r.push(`--${j.majorMode} ${e.majorMode}: ${N(t)}`)}if(e.domains&&e.domains.length>0)try{let{applyDomains:t}=await import(`@agimon-ai/doompi-domain/apply`);await t(e.domains,l())}catch(t){r.push(`--domains ${e.domains.join(`,`)}: ${N(t)}`)}if(e.profile)try{let[{applyProfile:t},{resolveProfile:r}]=await Promise.all([import(D),import(`@agimon-ai/doompi-config/profiles`)]);await t(r(n,e.profile),l())}catch(t){r.push(`--profile ${e.profile}: ${N(t)}`)}}async function B(e,t=process.env){let n=a(e,process.pid,t.HOME??S.homedir());return await x.promises.mkdir(n,{mode:448,recursive:!0}),f({temporaryDirectory:n},t),n}async function V(e,t=process.env){await x.promises.rm(a(e,process.pid,t.HOME??S.homedir()),{recursive:!0,force:!0})}async function H(n,i=l(),a=process.env){let o=i.root??n.root,s=r(n.resolved,n.compiled),c={agents:i.agents,autoStop:!1,preset:n.selection.preset,personaEntry:s.packageEntry(e),majorMode:i.majorMode,layers:[...i.layers],majorModesConfig:C(o),resolvers:s},u=t({...c,mute:a[g]===`1`});a[w]=`1`,f({childExtensions:[...u.childActivation],compositionFingerprint:u.fingerprint},a),i.agents&&(a.PI_SUBAGENT_EXTRA_AGENT_DIRS=i.agentDirectories.join(b.delimiter),a.PI_SUBAGENT_EXTRA_SKILL_DIRS=i.skillDirectories.join(b.delimiter));let d=[...u.parentActivation],p=n.bundles?.[u.fingerprint];return{entries:p?[p]:d,fingerprint:u.fingerprint}}async function U(e,t=l(),n=process.env){return(await H(e,t,n)).entries}async function W(e,t,n){let r=[];for(let i of t){let t=performance.now(),a;try{a=await import(_(i).href),A&&process.stderr.write(`[doompi:timing] import ${(performance.now()-t).toFixed(1)}ms ${i}\n`)}catch(e){n.push(`${i}: ${N(e)}`);continue}if(typeof a.default!=`function`){n.push(`${i}: does not export an extension factory`);continue}let o=performance.now();try{await a.default(E(e,i)),A&&process.stderr.write(`[doompi:timing] factory ${(performance.now()-o).toFixed(1)}ms ${i}\n`),r.push(i)}catch(e){n.push(`${i}: ${N(e)}`)}}return r}async function G(e,t={}){let r=t.environment??process.env,a=[],c=r.HOME??S.homedir(),l=o(t.cwd??process.cwd(),c);if(!l)return{problems:[O],stale:!1,loaded:[]};let d;try{d=i(l,c)}catch(e){return{problems:[N(e)],stale:!1,loaded:[]}}if(!d)return{problems:[O],stale:!1,loaded:[]};let f=t.argv??process.argv.slice(2);if(r[h]===`1`||F(f,{...d.resolved,...d.bundles}))return{problems:[],stale:!1,loaded:[]};if(p({preset:d.selection.preset,piArgs:[]},r),!v(r)){r[m]=`1`,await I(d,l,r);let e=L(f);e.mute&&(r[g]=`1`),await z(e,C(l),l,a)}let _;try{_=await H(d,u(r).state,r)}catch(e){return{problems:[...a,N(e)],stale:!1,loaded:[]}}let y=d.bundles?.[_.fingerprint];if(y)try{let e=s(l,_.fingerprint,c);if(!e.fresh||e.bundle!==y)return{problems:[...a,k],stale:!1,loaded:[]}}catch{return{problems:[...a,k],stale:!1,loaded:[]}}let b=await W(e,_.entries,a);return{problems:a,stale:n(l,d.selection,c)!==d.inputsHash||u(r).state.majorMode===d.selection.majorMode&&_.fingerprint!==d.compositionFingerprint,loaded:b}}export{m as COMPOSED_ENV,j as DOOM_FLAGS,h as EXTERNAL_EXTENSIONS_ENV,g as MUTE_ENV,M as REMOVED_LAYER_FLAG,y as alreadyComposed,z as applyStartupFlags,V as cleanupRunDirectory,G as composeDoomSession,U as composeLoadOrder,H as composeRuntimeLoadPlan,F as extensionsProvidedExternally,o as findSyncedRoot,W as loadComposedExtensions,B as prepareRunDirectory,L as readStartupFlags,R as registerDoomFlags,I as startSyncedSession};
1
+ import{PERSONA_ENTRY as e,packageAttribution as t,resolveExtensionComposition as n}from"../services/extensionAssembler.mjs";import{computeInputsHash as r,createMapResolvers as i,readSyncState as a,runDirectory as o}from"./syncState.mjs";import{findSyncedRoot as s,readBundleStatus as c}from"./bootstrapLocator.mjs";import{createHarnessSession as l,getHarnessState as u,loadHarnessState as d,readHarnessState as f,updateHarnessState as p}from"./config/harnessState.mjs";import{configurePreset as m}from"./harnessContext.mjs";import{COMPOSED_ENV as h,EXTERNAL_EXTENSIONS_ENV as g,MUTE_ENV as _}from"./compositionState.mjs";import{pathToFileURL as v}from"node:url";import{alreadyComposed as y,alreadyComposed as b}from"@agimon-ai/doompi-extension-contracts/child-process";import x from"node:path";import S from"node:fs";import C from"node:os";import{loadMajorModesConfig as w}from"@agimon-ai/doompi-config/majorModes";import{withExtensionSource as T}from"@agimon-ai/doompi-ui/extensionName";import{DOOM_CORDIS_HOST_REQUIRED_ENV as E}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{parseArgs as D}from"@earendil-works/pi-coding-agent";const O=`@agimon-ai/doompi-config/selectionSwitch`,k=`doompi is configured for Pi but this repository was never synced. Run doompi sync.`,A=`doompi could not read its synchronized state. Run doompi sync.`,j=process.env.DOOMPI_TIMING===`1`||process.env.PI_TIMING===`1`,M={majorMode:`major-mode`,domains:`domains`,profile:`profile`,mute:`mute`},N=`layer`;function P(e){return e instanceof Error?e.message:String(e)}function F(e){try{return S.realpathSync(e)}catch{return x.resolve(e)}}function I(e,t){let{extensions:n}=D(e);if(!n||n.length===0)return!1;let r=new Set(Object.values(t).map(F));return n.some(e=>r.has(F(e)))}async function L(e,t,n=process.env){let r=await V(t,n),i={...f({...e.env,...n}),...e.fileState,mcpProjection:{...e.fileState.mcpProjection,stagingDirectory:r},temporaryDirectory:r};l(i,{directory:r,environment:n});for(let[e,t]of Object.entries(i.profileEnvironment))n[e]===void 0&&(n[e]=t);return i}function R(e){let{unknownFlags:t}=D(e),n=e=>{let n=t.get(e);return typeof n==`string`&&n.trim()?n.trim():void 0},r=n(M.domains);return{majorMode:n(M.majorMode),domains:r?.split(`,`).map(e=>e.trim()).filter(Boolean),profile:n(M.profile),mute:t.get(M.mute)!==void 0,removedLayer:t.get(N)!==void 0}}function z(e){e.registerFlag(M.majorMode,{type:`string`,description:`Named major mode from .doom/modes.yaml`}),e.registerFlag(M.domains,{type:`string`,description:`Comma-separated content domains`}),e.registerFlag(M.profile,{type:`string`,description:`Persona and env from .doom/profiles.yaml`}),e.registerFlag(M.mute,{type:`boolean`,description:`Disable the notification extension`})}async function B(e,t,n,r){if(e.removedLayer&&r.push(`--${N} was replaced by --${M.majorMode}`),e.majorMode)try{let{applyMajorMode:n}=await import(O);n(t,e.majorMode,u())}catch(t){r.push(`--${M.majorMode} ${e.majorMode}: ${P(t)}`)}if(e.domains&&e.domains.length>0)try{let{applyDomains:t}=await import(`@agimon-ai/doompi-domain/apply`);await t(e.domains,u())}catch(t){r.push(`--domains ${e.domains.join(`,`)}: ${P(t)}`)}if(e.profile)try{let[{applyProfile:t},{resolveProfile:r}]=await Promise.all([import(O),import(`@agimon-ai/doompi-config/profiles`)]);await t(r(n,e.profile),u())}catch(t){r.push(`--profile ${e.profile}: ${P(t)}`)}}async function V(e,t=process.env){let n=o(e,process.pid,t.HOME??C.homedir());return await S.promises.mkdir(n,{mode:448,recursive:!0}),p({temporaryDirectory:n},t),n}async function H(e,t=process.env){await S.promises.rm(o(e,process.pid,t.HOME??C.homedir()),{recursive:!0,force:!0})}async function U(r,a=u(),o=process.env){let s=a.root??r.root,c=i(r.resolved,r.compiled),l={agents:a.agents,autoStop:!1,preset:r.selection.preset,personaEntry:c.packageEntry(e),majorMode:a.majorMode,layers:[...a.layers],majorModesConfig:w(s),resolvers:c},d=n({...l,mute:o[_]===`1`});o[E]=`1`,p({childExtensions:[...d.childActivation],compositionFingerprint:d.fingerprint,packageAttribution:t(d)},o),a.agents&&(o.PI_SUBAGENT_EXTRA_AGENT_DIRS=a.agentDirectories.join(x.delimiter),o.PI_SUBAGENT_EXTRA_SKILL_DIRS=a.skillDirectories.join(x.delimiter));let f=[...d.parentActivation],m=r.bundles?.[d.fingerprint];return{entries:m?[m]:f,fingerprint:d.fingerprint}}async function W(e,t=u(),n=process.env){return(await U(e,t,n)).entries}async function G(e,t,n){let r=[];for(let i of t){let t=performance.now(),a;try{a=await import(v(i).href),j&&process.stderr.write(`[doompi:timing] import ${(performance.now()-t).toFixed(1)}ms ${i}\n`)}catch(e){n.push(`${i}: ${P(e)}`);continue}if(typeof a.default!=`function`){n.push(`${i}: does not export an extension factory`);continue}let o=performance.now();try{await a.default(T(e,i)),j&&process.stderr.write(`[doompi:timing] factory ${(performance.now()-o).toFixed(1)}ms ${i}\n`),r.push(i)}catch(e){n.push(`${i}: ${P(e)}`)}}return r}async function K(e,t={}){let n=t.environment??process.env,i=[],o=n.HOME??C.homedir(),l=s(t.cwd??process.cwd(),o);if(!l)return{problems:[k],stale:!1,loaded:[]};let u;try{u=a(l,o)}catch(e){return{problems:[P(e)],stale:!1,loaded:[]}}if(!u)return{problems:[k],stale:!1,loaded:[]};let f=t.argv??process.argv.slice(2);if(n[g]===`1`||I(f,{...u.resolved,...u.bundles}))return{problems:[],stale:!1,loaded:[]};if(m({preset:u.selection.preset,piArgs:[]},n),!y(n)){n[h]=`1`,await L(u,l,n);let e=R(f);e.mute&&(n[_]=`1`),await B(e,w(l),l,i)}let p;try{p=await U(u,d(n).state,n)}catch(e){return{problems:[...i,P(e)],stale:!1,loaded:[]}}let v=u.bundles?.[p.fingerprint];if(v)try{let e=c(l,p.fingerprint,o);if(!e.fresh||e.bundle!==v)return{problems:[...i,A],stale:!1,loaded:[]}}catch{return{problems:[...i,A],stale:!1,loaded:[]}}let b=await G(e,p.entries,i);return{problems:i,stale:r(l,u.selection,o)!==u.inputsHash||d(n).state.majorMode===u.selection.majorMode&&p.fingerprint!==u.compositionFingerprint,loaded:b}}export{h as COMPOSED_ENV,M as DOOM_FLAGS,g as EXTERNAL_EXTENSIONS_ENV,_ as MUTE_ENV,N as REMOVED_LAYER_FLAG,b as alreadyComposed,B as applyStartupFlags,H as cleanupRunDirectory,K as composeDoomSession,W as composeLoadOrder,U as composeRuntimeLoadPlan,I as extensionsProvidedExternally,s as findSyncedRoot,G as loadComposedExtensions,V as prepareRunDirectory,R as readStartupFlags,z as registerDoomFlags,L as startSyncedSession};
2
2
  //# sourceMappingURL=composer.mjs.map