@agimon-ai/doompi 0.0.1-alpha.45 → 0.0.1-alpha.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -10
- package/dist/src/adapters/bootstrapLocator.cjs +1 -1
- package/dist/src/adapters/bootstrapLocator.cjs.map +1 -1
- package/dist/src/adapters/bootstrapLocator.mjs +1 -1
- package/dist/src/adapters/bootstrapLocator.mjs.map +1 -1
- package/dist/src/adapters/harnessContext.cjs +1 -1
- package/dist/src/adapters/harnessContext.mjs +1 -1
- package/dist/src/adapters/piExtensionDispatcher.cjs +7 -8
- package/dist/src/adapters/piExtensionDispatcher.cjs.map +1 -1
- package/dist/src/adapters/piExtensionDispatcher.mjs +7 -8
- package/dist/src/adapters/piExtensionDispatcher.mjs.map +1 -1
- package/dist/src/adapters/repository/repository.cjs +1 -1
- package/dist/src/adapters/repository/repository.cjs.map +1 -1
- package/dist/src/adapters/repository/repository.d.cts +3 -1
- package/dist/src/adapters/repository/repository.d.cts.map +1 -1
- package/dist/src/adapters/repository/repository.d.mts +3 -1
- package/dist/src/adapters/repository/repository.d.mts.map +1 -1
- package/dist/src/adapters/repository/repository.mjs +1 -1
- package/dist/src/adapters/repository/repository.mjs.map +1 -1
- package/dist/src/commands/buildCommand.cjs +2 -2
- package/dist/src/commands/buildCommand.cjs.map +1 -1
- package/dist/src/commands/buildCommand.mjs +2 -2
- package/dist/src/commands/buildCommand.mjs.map +1 -1
- package/dist/src/commands/syncCommand.cjs +4 -4
- package/dist/src/commands/syncCommand.cjs.map +1 -1
- package/dist/src/commands/syncCommand.d.cts.map +1 -1
- package/dist/src/commands/syncCommand.d.mts.map +1 -1
- package/dist/src/commands/syncCommand.mjs +5 -5
- package/dist/src/commands/syncCommand.mjs.map +1 -1
- package/dist/src/commands/syncPipeline.cjs +2 -2
- package/dist/src/commands/syncPipeline.cjs.map +1 -1
- package/dist/src/commands/syncPipeline.mjs +2 -2
- package/dist/src/commands/syncPipeline.mjs.map +1 -1
- package/dist/utils/repository.cjs +1 -1
- package/dist/utils/repository.d.cts +2 -2
- package/dist/utils/repository.d.mts +2 -2
- package/dist/utils/repository.mjs +1 -1
- 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.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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.
|
|
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
|
|
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
|
|
96
|
-
pi #
|
|
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` |
|
|
695
|
-
| `doompi sync --check` | Exits non-zero when the
|
|
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()){
|
|
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":"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"}
|
|
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{
|
|
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":"0VAoCA,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,CACF,GAAI,EAAiB,EAAK,CAAa,EAAG,OAAO,EAAc,CAAG,EAClE,IAAM,EAAO,EAAmB,CAAG,EACnC,OAAO,EAAiB,EAAM,CAAa,EAAI,EAAO,IAAA,EACxD,MAAQ,CACN,MACF,CACF,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
|
+
{"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":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./syncLocation.cjs"),n=require("./modules/moduleResolution.cjs"),r=require("../services/extensionAssembler.cjs"),i=require("./telemetry/logSinkTelemetry.cjs");require("./config/harnessState.cjs");let a=require("node:path");a=e.__toESM(a,1);let o=require("node:fs");o=e.__toESM(o,1);let s=require("node:os");s=e.__toESM(s,1);let c=require("@agimon-ai/doompi-config/
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./syncLocation.cjs"),n=require("./modules/moduleResolution.cjs"),r=require("../services/extensionAssembler.cjs"),i=require("./telemetry/logSinkTelemetry.cjs");require("./config/harnessState.cjs");let a=require("node:path");a=e.__toESM(a,1);let o=require("node:fs");o=e.__toESM(o,1);let s=require("node:os");s=e.__toESM(s,1);let c=require("@agimon-ai/doompi-config/config"),l=require("@agimon-ai/doompi-config/domains"),u=require("@agimon-ai/doompi-config/profiles"),d=require("@agimon-ai/doompi-config/majorModes"),f=require("@agimon-ai/doompi-domain/mcp"),p=require("@agimon-ai/doompi-domain/plugins"),m=require("@agimon-ai/doompi-domain/resources"),h=require("@agimon-ai/doompi-ui/theme"),g=require("@agimon-ai/doompi-extension-contracts/cordis-host"),_=require("@agimon-ai/doompi-extension-contracts/mcp-session"),v=require("@agimon-ai/doompi-config/harnessStore");const y=`ollama`,b=`resolve_config`,x=`collect_resources`;function S(e,t){if(e.preset===`kimi`&&(!t.KIMI_API_KEY&&t.KIMI_CODE_API_KEY&&(t.KIMI_API_KEY=t.KIMI_CODE_API_KEY),!t.KIMI_API_KEY))throw Error(`KIMI_API_KEY or KIMI_CODE_API_KEY is required for the kimi preset`);if(e.preset===y){if((e.piArgs.find(e=>e.startsWith(`${y}/`))?.endsWith(`:cloud`)??!1)&&!t.OLLAMA_API_KEY)throw Error(`OLLAMA_API_KEY is required for Ollama cloud models`);t.OLLAMA_API_KEY||=y,t.DOOMPI_OLLAMA_BASE_URL=t.OLLAMA_BASE_URL??t.ANTHROPIC_BASE_URL??`http://localhost:11434/v1`}}function C(e){return e.profile?(0,u.resolveProfile)(e.repoRoot,e.profile):void 0}async function w(e,y=i.createHarnessTelemetry()){let w=await y.runInSpan(`doom_pi.${b}`,{"harness.phase":b},async()=>{let t=(0,d.loadMajorModesConfig)(e.repoRoot,e.homeDirectory),n=(0,d.filterHookDisabledLayers)(t,(0,d.resolveLayers)(t,e.majorMode),e.hooks),r=C(e);if(!o.default.existsSync(e.cwd)||!o.default.statSync(e.cwd).isDirectory())throw Error(`Working directory does not exist: ${e.cwd}`);return{majorModesConfig:t,selectedLayers:n,hookGroups:(0,d.layerHookGroups)(t,n),profile:r,plugins:await(0,p.materializePluginEntries)((0,l.resolvePluginEntries)(e.repoRoot,e.domains,e.pluginDirectories)),mcpAllowlist:(0,f.resolveMcpAllowlist)(e.repoRoot,e.domains),sharedSkills:(0,l.resolveSharedSkills)(e.repoRoot,e.domains)}}).catch(async t=>{throw await y.recordError(i.HARNESS_EVENT.configLoadFailed,t,{"harness.phase":b,"harness.major_mode":e.majorMode,"harness.domain_count":e.domains.length}),t}),{majorModesConfig:T,selectedLayers:E,hookGroups:D,profile:O,plugins:k,mcpAllowlist:A}=w,j=O?.persona,M=O?.personaRoot??e.repoRoot,N=await y.runInSpan(`doom_pi.${x}`,{"harness.plugin.count":k.length,"harness.agents":e.agents,"harness.mcp":e.mcp},()=>(0,m.collectResources)(e.repoRoot,k,{agents:e.agents,mcp:e.mcp,temporaryDirectory:e.resourceDirectory,skillCacheDirectory:a.default.join(e.resourceDirectory??t.resolveSyncLocation(e.repoRoot,e.homeDirectory??s.default.homedir()).directory,`cache`,`skills`),mcpAllowlist:A,sharedSkills:w.sharedSkills,pluginDataRoot:a.default.join((0,c.globalDoomConfigDirectory)(e.homeDirectory??s.default.homedir()),`plugin-data`)})).catch(async e=>{throw await y.recordError(i.HARNESS_EVENT.resourceCollectionFailed,e,{"harness.phase":x,"harness.plugin.count":k.length}),e});try{let t=await(0,h.writeDefaultTheme)(N.temporaryDirectory),i={...process.env};delete i[_.DOOM_MCP_SESSION_ENV_VAR];let s=(0,u.applyProfileEnvironment)(i,O?.env??{});S(e,i),i.NX_DAEMON??=`false`,i[g.DOOM_CORDIS_HOST_REQUIRED_ENV]=`1`,i.DOOMPI_THEME||=h.DEFAULT_THEME_NAME,i.CLAUDE_PROJECT_DIR=e.repoRoot,i.CODEX_REPO_ROOT=e.repoRoot,i.ORIGINAL_REPO_PATH||=e.repoRoot,i.MCP_UI_VIEWER=e.automation?`none`:i.MCP_UI_VIEWER??``;let c=n.packageEntry(r.PERSONA_ENTRY),l=j?(0,u.buildPersonaPrompt)(M,j):void 0,d;return l&&(d=a.default.join(N.temporaryDirectory,`persona.md`),await o.default.promises.writeFile(d,`${l}\n`,{mode:384})),(0,v.createHarnessSession)({root:e.repoRoot,majorMode:e.majorMode,temporaryDirectory:N.temporaryDirectory,domains:e.domains,layers:E,profile:O?.name,profileEnvironment:s,personaFile:d,skillDirectories:N.skillDirectories,agentDirectories:N.agentDirectories,additionalDirectories:e.additionalDirectories,childExtensions:[],pluginDirectories:e.pluginDirectories,pluginHooks:N.pluginHooks,hookGroups:D,hooks:e.hooks,agents:e.agents,mcp:e.mcp,mcpConfigPath:N.mcpConfigPath,mcpProjection:N.mcpProjection,allowProtectedWrites:e.allowProtectedWrites},{directory:N.temporaryDirectory,environment:i}),e.agents&&(i.PI_SUBAGENT_EXTRA_AGENT_DIRS=N.agentDirectories.join(a.default.delimiter),i.PI_SUBAGENT_EXTRA_SKILL_DIRS=N.skillDirectories.join(a.default.delimiter),i.PI_SUBAGENT_PI_BINARY=a.default.join(e.repoRoot,`pi.sh`)),{options:e,environment:i,resources:N,plugins:k,profile:O?.name,personaDirectory:j,personaRoot:M,mcpAllowlist:A,majorModesConfig:T,selectedLayers:E,hookGroups:D,personaEntry:c,defaultThemePath:t,cleanup:N.cleanup.bind(N)}}catch(t){throw await y.recordError(i.HARNESS_EVENT.contextBuildFailed,t,{"harness.phase":`build_context`,"harness.major_mode":e.majorMode,...O?.name?{"harness.profile":O.name}:{}}),await N.cleanup(),t}}exports.buildHarnessContext=w,exports.configurePreset=S,exports.resolveHarnessProfile=C;
|
|
2
2
|
//# sourceMappingURL=harnessContext.cjs.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{resolveSyncLocation as e}from"./syncLocation.mjs";import{packageEntry as t}from"./modules/moduleResolution.mjs";import{PERSONA_ENTRY as n}from"../services/extensionAssembler.mjs";import{HARNESS_EVENT as r,createHarnessTelemetry as i}from"./telemetry/logSinkTelemetry.mjs";import{createHarnessSession as a}from"./config/harnessState.mjs";import o from"node:path";import s from"node:fs";import c from"node:os";import{resolvePluginEntries as
|
|
1
|
+
import{resolveSyncLocation as e}from"./syncLocation.mjs";import{packageEntry as t}from"./modules/moduleResolution.mjs";import{PERSONA_ENTRY as n}from"../services/extensionAssembler.mjs";import{HARNESS_EVENT as r,createHarnessTelemetry as i}from"./telemetry/logSinkTelemetry.mjs";import{createHarnessSession as a}from"./config/harnessState.mjs";import o from"node:path";import s from"node:fs";import c from"node:os";import{globalDoomConfigDirectory as l}from"@agimon-ai/doompi-config/config";import{resolvePluginEntries as u,resolveSharedSkills as d}from"@agimon-ai/doompi-config/domains";import{applyProfileEnvironment as f,buildPersonaPrompt as p,resolveProfile as m}from"@agimon-ai/doompi-config/profiles";import{filterHookDisabledLayers as h,layerHookGroups as g,loadMajorModesConfig as _,resolveLayers as v}from"@agimon-ai/doompi-config/majorModes";import{resolveMcpAllowlist as y}from"@agimon-ai/doompi-domain/mcp";import{materializePluginEntries as b}from"@agimon-ai/doompi-domain/plugins";import{collectResources as x}from"@agimon-ai/doompi-domain/resources";import{DEFAULT_THEME_NAME as S,writeDefaultTheme as C}from"@agimon-ai/doompi-ui/theme";import{DOOM_CORDIS_HOST_REQUIRED_ENV as w}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_MCP_SESSION_ENV_VAR as T}from"@agimon-ai/doompi-extension-contracts/mcp-session";const E=`ollama`,D=`resolve_config`,O=`collect_resources`;function k(e,t){if(e.preset===`kimi`&&(!t.KIMI_API_KEY&&t.KIMI_CODE_API_KEY&&(t.KIMI_API_KEY=t.KIMI_CODE_API_KEY),!t.KIMI_API_KEY))throw Error(`KIMI_API_KEY or KIMI_CODE_API_KEY is required for the kimi preset`);if(e.preset===E){if((e.piArgs.find(e=>e.startsWith(`${E}/`))?.endsWith(`:cloud`)??!1)&&!t.OLLAMA_API_KEY)throw Error(`OLLAMA_API_KEY is required for Ollama cloud models`);t.OLLAMA_API_KEY||=E,t.DOOMPI_OLLAMA_BASE_URL=t.OLLAMA_BASE_URL??t.ANTHROPIC_BASE_URL??`http://localhost:11434/v1`}}function A(e){return e.profile?m(e.repoRoot,e.profile):void 0}async function j(m,E=i()){let j=await E.runInSpan(`doom_pi.${D}`,{"harness.phase":D},async()=>{let e=_(m.repoRoot,m.homeDirectory),t=h(e,v(e,m.majorMode),m.hooks),n=A(m);if(!s.existsSync(m.cwd)||!s.statSync(m.cwd).isDirectory())throw Error(`Working directory does not exist: ${m.cwd}`);return{majorModesConfig:e,selectedLayers:t,hookGroups:g(e,t),profile:n,plugins:await b(u(m.repoRoot,m.domains,m.pluginDirectories)),mcpAllowlist:y(m.repoRoot,m.domains),sharedSkills:d(m.repoRoot,m.domains)}}).catch(async e=>{throw await E.recordError(r.configLoadFailed,e,{"harness.phase":D,"harness.major_mode":m.majorMode,"harness.domain_count":m.domains.length}),e}),{majorModesConfig:M,selectedLayers:N,hookGroups:P,profile:F,plugins:I,mcpAllowlist:L}=j,R=F?.persona,z=F?.personaRoot??m.repoRoot,B=await E.runInSpan(`doom_pi.${O}`,{"harness.plugin.count":I.length,"harness.agents":m.agents,"harness.mcp":m.mcp},()=>x(m.repoRoot,I,{agents:m.agents,mcp:m.mcp,temporaryDirectory:m.resourceDirectory,skillCacheDirectory:o.join(m.resourceDirectory??e(m.repoRoot,m.homeDirectory??c.homedir()).directory,`cache`,`skills`),mcpAllowlist:L,sharedSkills:j.sharedSkills,pluginDataRoot:o.join(l(m.homeDirectory??c.homedir()),`plugin-data`)})).catch(async e=>{throw await E.recordError(r.resourceCollectionFailed,e,{"harness.phase":O,"harness.plugin.count":I.length}),e});try{let e=await C(B.temporaryDirectory),r={...process.env};delete r[T];let i=f(r,F?.env??{});k(m,r),r.NX_DAEMON??=`false`,r[w]=`1`,r.DOOMPI_THEME||=S,r.CLAUDE_PROJECT_DIR=m.repoRoot,r.CODEX_REPO_ROOT=m.repoRoot,r.ORIGINAL_REPO_PATH||=m.repoRoot,r.MCP_UI_VIEWER=m.automation?`none`:r.MCP_UI_VIEWER??``;let c=t(n),l=R?p(z,R):void 0,u;return l&&(u=o.join(B.temporaryDirectory,`persona.md`),await s.promises.writeFile(u,`${l}\n`,{mode:384})),a({root:m.repoRoot,majorMode:m.majorMode,temporaryDirectory:B.temporaryDirectory,domains:m.domains,layers:N,profile:F?.name,profileEnvironment:i,personaFile:u,skillDirectories:B.skillDirectories,agentDirectories:B.agentDirectories,additionalDirectories:m.additionalDirectories,childExtensions:[],pluginDirectories:m.pluginDirectories,pluginHooks:B.pluginHooks,hookGroups:P,hooks:m.hooks,agents:m.agents,mcp:m.mcp,mcpConfigPath:B.mcpConfigPath,mcpProjection:B.mcpProjection,allowProtectedWrites:m.allowProtectedWrites},{directory:B.temporaryDirectory,environment:r}),m.agents&&(r.PI_SUBAGENT_EXTRA_AGENT_DIRS=B.agentDirectories.join(o.delimiter),r.PI_SUBAGENT_EXTRA_SKILL_DIRS=B.skillDirectories.join(o.delimiter),r.PI_SUBAGENT_PI_BINARY=o.join(m.repoRoot,`pi.sh`)),{options:m,environment:r,resources:B,plugins:I,profile:F?.name,personaDirectory:R,personaRoot:z,mcpAllowlist:L,majorModesConfig:M,selectedLayers:N,hookGroups:P,personaEntry:c,defaultThemePath:e,cleanup:B.cleanup.bind(B)}}catch(e){throw await E.recordError(r.contextBuildFailed,e,{"harness.phase":`build_context`,"harness.major_mode":m.majorMode,...F?.name?{"harness.profile":F.name}:{}}),await B.cleanup(),e}}export{j as buildHarnessContext,k as configurePreset,A as resolveHarnessProfile};
|
|
2
2
|
//# sourceMappingURL=harnessContext.mjs.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./doomPackage.cjs"),n=require("./serialization/json.cjs");let r=require("node:path");r=e.__toESM(r,1);let i=require("node:fs");i=e.__toESM(i,1);const a=`dispatcher.mjs`,o=`package.json`;function s(){return`${JSON.stringify({name:t.DOOM_PACKAGE_NAME,private:!0,type:`module`,doompiDispatcher:
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./doomPackage.cjs"),n=require("./serialization/json.cjs");let r=require("node:path");r=e.__toESM(r,1);let i=require("node:fs");i=e.__toESM(i,1);const a=`dispatcher.mjs`,o=`package.json`;function s(){return`${JSON.stringify({name:t.DOOM_PACKAGE_NAME,private:!0,type:`module`,doompiDispatcher:2,pi:{extensions:[`./${a}`]}},null,2)}\n`}function c(){return`import crypto from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
@@ -81,21 +81,20 @@ function registration(root) {
|
|
|
81
81
|
return value;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
function report(pi, message) {
|
|
85
|
-
pi.on('session_start', (_event, context) => context.ui.notify(\`doompi could not load
|
|
84
|
+
function report(pi, target, message) {
|
|
85
|
+
pi.on('session_start', (_event, context) => context.ui.notify(\`doompi could not load \${target}: \${message}. Run doompi init, then doompi sync.\`, WARNING));
|
|
86
86
|
}
|
|
87
|
-
|
|
88
87
|
export default async function doompiDispatcher(pi) {
|
|
89
|
-
const
|
|
90
|
-
if (!root) return;
|
|
88
|
+
const repository = repositoryRoot(process.cwd());
|
|
91
89
|
try {
|
|
90
|
+
const root = repository ?? canonical(path.join(os.homedir(), '.pi', '.doom'));
|
|
92
91
|
const record = registration(root);
|
|
93
92
|
const loaded = await import(pathToFileURL(record.package.entry).href);
|
|
94
93
|
if (typeof loaded.default !== 'function') throw new Error('recorded package entry has no extension factory');
|
|
95
94
|
await loaded.default(pi);
|
|
96
95
|
} catch (error) {
|
|
97
|
-
report(pi, error instanceof Error ? error.message : String(error));
|
|
96
|
+
report(pi, repository ? 'this repository' : 'the global composition', error instanceof Error ? error.message : String(error));
|
|
98
97
|
}
|
|
99
98
|
}
|
|
100
|
-
`}function l(e){return r.default.join(e,...t.DOOM_PACKAGE_NAME.split(`/`))}function u(e){try{let t=JSON.parse(i.default.readFileSync(r.default.join(e,o),`utf8`));return t.name===`@agimon-ai/doompi`&&t.doompiDispatcher
|
|
99
|
+
`}function l(e){return r.default.join(e,...t.DOOM_PACKAGE_NAME.split(`/`))}function u(e){try{let t=JSON.parse(i.default.readFileSync(r.default.join(e,o),`utf8`));return t.name===`@agimon-ai/doompi`&&Number.isSafeInteger(t.doompiDispatcher)?t.doompiDispatcher:void 0}catch{return}}function d(e){return u(e)===2}function f(e){let t=u(e);return t!==void 0&&t>0&&t<=2}function p(e){let t=l(e),n=i.default.lstatSync(t,{throwIfNoEntry:!1});return!n?.isDirectory()||n.isSymbolicLink()||!d(t)?!1:i.default.lstatSync(r.default.join(t,a),{throwIfNoEntry:!1})?.isFile()===!0}function m(e){try{return t.manifestName(i.default.realpathSync(e))===t.DOOM_PACKAGE_NAME}catch{return!0}}function h(e){let t=l(e),u=i.default.lstatSync(t,{throwIfNoEntry:!1});if(u?.isSymbolicLink()){if(!m(t))throw Error(`Refusing to replace unmanaged Pi extension path: ${t}`);i.default.rmSync(t,{force:!0})}else if(u&&(!u.isDirectory()||!f(t)))throw Error(`Refusing to replace unmanaged Pi extension path: ${t}`);let d=r.default.dirname(t),p=i.default.lstatSync(d,{throwIfNoEntry:!1});if(p&&!p.isDirectory())throw Error(`Refusing to use unmanaged Pi extension scope: ${d}`);return i.default.mkdirSync(t,{mode:448,recursive:!0}),i.default.chmodSync(t,448),n.writeFileAtomic(r.default.join(t,o),s()),n.writeFileAtomic(r.default.join(t,a),c()),i.default.chmodSync(r.default.join(t,o),384),i.default.chmodSync(r.default.join(t,a),384),t}exports.piExtensionDispatcherIsCurrent=p,exports.piExtensionDispatcherPath=l,exports.writePiExtensionDispatcher=h;
|
|
101
100
|
//# sourceMappingURL=piExtensionDispatcher.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"piExtensionDispatcher.cjs","names":["DOOM_PACKAGE_NAME","path","fs","manifestName"],"sources":["../../../src/adapters/piExtensionDispatcher.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { DOOM_PACKAGE_NAME, manifestName } from './doomPackage.ts';\nimport { writeFileAtomic } from './serialization/json.ts';\nimport { SYNC_REGISTRATION_VERSION } from './syncRegistration.ts';\n\n/** Protocol marker proving that the user package path is managed by DoomPi init. */\nexport const PI_DISPATCHER_VERSION = 1;\n\nconst DISPATCHER_ENTRY = 'dispatcher.mjs';\nconst PACKAGE_MANIFEST = 'package.json';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nfunction dispatcherManifest(): string {\n return `${JSON.stringify(\n {\n name: DOOM_PACKAGE_NAME,\n private: true,\n type: 'module',\n doompiDispatcher: PI_DISPATCHER_VERSION,\n pi: { extensions: [`./${DISPATCHER_ENTRY}`] },\n },\n null,\n 2,\n )}\\n`;\n}\n\nfunction dispatcherSource(): string {\n return `import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nconst REGISTRATION_VERSION = ${String(SYNC_REGISTRATION_VERSION)};\nconst PACKAGE_NAME = ${JSON.stringify(DOOM_PACKAGE_NAME)};\nconst WARNING = 'warning';\n\nfunction canonical(target) {\n return fs.realpathSync.native(path.resolve(target));\n}\n\nfunction isFile(target) {\n try { return fs.statSync(target).isFile(); } catch { return false; }\n}\n\nfunction isDirectory(target) {\n try { return fs.statSync(target).isDirectory(); } catch { return false; }\n}\n\nfunction repositoryRoot(start) {\n let directory = path.resolve(start);\n while (true) {\n const git = path.join(directory, '.git');\n if (isDirectory(path.join(directory, '.doom')) || isFile(path.join(directory, '.pi', 'settings.json')) || isDirectory(git) || isFile(git)) return canonical(directory);\n const parent = path.dirname(directory);\n if (parent === directory) return undefined;\n directory = parent;\n }\n}\n\nfunction gitDirectory(root) {\n const target = path.join(root, '.git');\n try {\n const stat = fs.statSync(target);\n if (stat.isDirectory()) return canonical(target);\n if (!stat.isFile()) return undefined;\n const match = /^gitdir:\\\\s*(.+)$/imu.exec(fs.readFileSync(target, 'utf8').trim());\n return match?.[1] ? canonical(path.resolve(root, match[1].trim())) : undefined;\n } catch { return undefined; }\n}\n\nfunction commonDirectory(root) {\n const git = gitDirectory(root);\n if (!git) return undefined;\n try {\n const relative = fs.readFileSync(path.join(git, 'commondir'), 'utf8').trim();\n return relative ? canonical(path.resolve(git, relative)) : git;\n } catch { return git; }\n}\n\nfunction identity(root) {\n const common = commonDirectory(root);\n const token = common ? \\`git:\\${common}\\` : \\`root:\\${root}\\`;\n const hash = (value) => crypto.createHash('sha256').update(value, 'utf8').digest('hex').slice(0, 32);\n return { repositoryId: hash(token), worktreeId: hash(\\`\\${token}\\\\0worktree:\\${root}\\`) };\n}\n\nfunction inside(directory, target) {\n const relative = path.relative(canonical(directory), canonical(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction registration(root) {\n const ids = identity(root);\n const recordPath = path.join(os.homedir(), '.pi', '.doom', 'sync', 'registrations', ids.repositoryId, \\`\\${ids.worktreeId}.json\\`);\n const value = JSON.parse(fs.readFileSync(recordPath, 'utf8'));\n if (value.version !== REGISTRATION_VERSION || canonical(value.root) !== root) throw new Error('registration identity mismatch');\n if (value.identity?.repositoryId !== ids.repositoryId || value.identity?.worktreeId !== ids.worktreeId) throw new Error('registration worktree mismatch');\n if (!inside(value.generationRoot, value.statePath) || !inside(value.package.root, value.package.entry)) throw new Error('registration path escapes its owner');\n const stateHash = crypto.createHash('sha256').update(fs.readFileSync(value.statePath)).digest('hex');\n if (stateHash !== value.stateSha256) throw new Error('registration state hash mismatch');\n const manifestPath = path.join(canonical(value.package.root), 'package.json');\n if (canonical(value.package.manifestPath) !== canonical(manifestPath)) throw new Error('registration package manifest mismatch');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n if (manifest.name !== PACKAGE_NAME || manifest.version !== value.package.version) throw new Error('registration package mismatch');\n const entries = manifest.pi?.extensions;\n if (!Array.isArray(entries) || !entries.some((entry) => typeof entry === 'string' && canonical(path.resolve(value.package.root, entry)) === canonical(value.package.entry))) throw new Error('registration package entry mismatch');\n return value;\n}\n\nfunction report(pi, message) {\n pi.on('session_start', (_event, context) => context.ui.notify(\\`doompi could not load this repository: \\${message}. Run doompi init, then doompi sync.\\`, WARNING));\n}\n\nexport default async function doompiDispatcher(pi) {\n const root = repositoryRoot(process.cwd());\n if (!root) return;\n try {\n const record = registration(root);\n const loaded = await import(pathToFileURL(record.package.entry).href);\n if (typeof loaded.default !== 'function') throw new Error('recorded package entry has no extension factory');\n await loaded.default(pi);\n } catch (error) {\n report(pi, error instanceof Error ? error.message : String(error));\n }\n}\n`;\n}\n\n/** Stable path Pi derives from the user settings package spelling. */\nexport function piExtensionDispatcherPath(piDirectory: string): string {\n return path.join(piDirectory, ...DOOM_PACKAGE_NAME.split('/'));\n}\n\nfunction managedManifest(directory: string): boolean {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(directory, PACKAGE_MANIFEST), 'utf8')) as {\n name?: unknown;\n doompiDispatcher?: unknown;\n };\n return parsed.name === DOOM_PACKAGE_NAME && parsed.doompiDispatcher === PI_DISPATCHER_VERSION;\n } catch {\n return false;\n }\n}\n\n/** Whether init's dispatcher package has a supported protocol and complete entry. */\nexport function piExtensionDispatcherIsCurrent(piDirectory: string): boolean {\n const directory = piExtensionDispatcherPath(piDirectory);\n const stat = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (!stat?.isDirectory() || stat.isSymbolicLink() || !managedManifest(directory)) return false;\n return fs.lstatSync(path.join(directory, DISPATCHER_ENTRY), { throwIfNoEntry: false })?.isFile() === true;\n}\n\nfunction legacyLinkIsManaged(linkPath: string): boolean {\n try {\n return manifestName(fs.realpathSync(linkPath)) === DOOM_PACKAGE_NAME;\n } catch {\n return true;\n }\n}\n\n/** Installs or repairs the init-owned dependency-free dispatcher package. */\nexport function writePiExtensionDispatcher(piDirectory: string): string {\n const directory = piExtensionDispatcherPath(piDirectory);\n const current = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (current?.isSymbolicLink()) {\n if (!legacyLinkIsManaged(directory)) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n fs.rmSync(directory, { force: true });\n } else if (current && (!current.isDirectory() || !managedManifest(directory))) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n\n const scope = path.dirname(directory);\n const scopeStat = fs.lstatSync(scope, { throwIfNoEntry: false });\n if (scopeStat && !scopeStat.isDirectory()) {\n throw new Error(`Refusing to use unmanaged Pi extension scope: ${scope}`);\n }\n fs.mkdirSync(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n fs.chmodSync(directory, PRIVATE_DIRECTORY_MODE);\n writeFileAtomic(path.join(directory, PACKAGE_MANIFEST), dispatcherManifest());\n writeFileAtomic(path.join(directory, DISPATCHER_ENTRY), dispatcherSource());\n fs.chmodSync(path.join(directory, PACKAGE_MANIFEST), PRIVATE_FILE_MODE);\n fs.chmodSync(path.join(directory, DISPATCHER_ENTRY), PRIVATE_FILE_MODE);\n return directory;\n}\n"],"mappings":"mNASA,MAAM,EAAmB,iBACnB,EAAmB,eAIzB,SAAS,GAA6B,CACpC,MAAO,GAAG,KAAK,UACb,CACE,KAAMA,EAAAA,kBACN,QAAS,GACT,KAAM,SACN,iBAAA,EACA,GAAI,CAAE,WAAY,CAAC,KAAK,GAAkB,CAAE,CAC9C,EACA,KACA,CACF,EAAE,GACJ,CAEA,SAAS,GAA2B,CAClC,MAAO;;;;;;;uBAOc,KAAK,UAAUA,EAAAA,iBAAiB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6FzD,CAGA,SAAgB,EAA0B,EAA6B,CACrE,OAAOC,EAAAA,QAAK,KAAK,EAAa,GAAGD,EAAAA,kBAAkB,MAAM,GAAG,CAAC,CAC/D,CAEA,SAAS,EAAgB,EAA4B,CACnD,GAAI,CACF,IAAM,EAAS,KAAK,MAAME,EAAAA,QAAG,aAAaD,EAAAA,QAAK,KAAK,EAAW,CAAgB,EAAG,MAAM,CAAC,EAIzF,OAAO,EAAO,OAAA,qBAA8B,EAAO,mBAAA,CACrD,MAAQ,CACN,MAAO,EACT,CACF,CAGA,SAAgB,EAA+B,EAA8B,CAC3E,IAAM,EAAY,EAA0B,CAAW,EACjD,EAAOC,EAAAA,QAAG,UAAU,EAAW,CAAE,eAAgB,EAAM,CAAC,EAE9D,MADI,CAAC,GAAM,YAAY,GAAK,EAAK,eAAe,GAAK,CAAC,EAAgB,CAAS,EAAU,GAClFA,EAAAA,QAAG,UAAUD,EAAAA,QAAK,KAAK,EAAW,CAAgB,EAAG,CAAE,eAAgB,EAAM,CAAC,CAAC,EAAE,OAAO,IAAM,EACvG,CAEA,SAAS,EAAoB,EAA2B,CACtD,GAAI,CACF,OAAOE,EAAAA,aAAaD,EAAAA,QAAG,aAAa,CAAQ,CAAC,IAAMF,EAAAA,iBACrD,MAAQ,CACN,MAAO,EACT,CACF,CAGA,SAAgB,EAA2B,EAA6B,CACtE,IAAM,EAAY,EAA0B,CAAW,EACjD,EAAUE,EAAAA,QAAG,UAAU,EAAW,CAAE,eAAgB,EAAM,CAAC,EACjE,GAAI,GAAS,eAAe,EAAG,CAC7B,GAAI,CAAC,EAAoB,CAAS,EAChC,MAAU,MAAM,oDAAoD,GAAW,EAEjF,EAAA,QAAG,OAAO,EAAW,CAAE,MAAO,EAAK,CAAC,CACtC,MAAO,GAAI,IAAY,CAAC,EAAQ,YAAY,GAAK,CAAC,EAAgB,CAAS,GACzE,MAAU,MAAM,oDAAoD,GAAW,EAGjF,IAAM,EAAQD,EAAAA,QAAK,QAAQ,CAAS,EAC9B,EAAYC,EAAAA,QAAG,UAAU,EAAO,CAAE,eAAgB,EAAM,CAAC,EAC/D,GAAI,GAAa,CAAC,EAAU,YAAY,EACtC,MAAU,MAAM,iDAAiD,GAAO,EAQ1E,OANA,EAAA,QAAG,UAAU,EAAW,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EACzE,EAAA,QAAG,UAAU,EAAW,GAAsB,EAC9C,EAAA,gBAAgBD,EAAAA,QAAK,KAAK,EAAW,CAAgB,EAAG,EAAmB,CAAC,EAC5E,EAAA,gBAAgBA,EAAAA,QAAK,KAAK,EAAW,CAAgB,EAAG,EAAiB,CAAC,EAC1E,EAAA,QAAG,UAAUA,EAAAA,QAAK,KAAK,EAAW,CAAgB,EAAG,GAAiB,EACtE,EAAA,QAAG,UAAUA,EAAAA,QAAK,KAAK,EAAW,CAAgB,EAAG,GAAiB,EAC/D,CACT"}
|
|
1
|
+
{"version":3,"file":"piExtensionDispatcher.cjs","names":["DOOM_PACKAGE_NAME","path","fs","manifestName"],"sources":["../../../src/adapters/piExtensionDispatcher.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { DOOM_PACKAGE_NAME, manifestName } from './doomPackage.ts';\nimport { writeFileAtomic } from './serialization/json.ts';\nimport { SYNC_REGISTRATION_VERSION } from './syncRegistration.ts';\n\n/** Protocol marker proving that the user package path is managed by DoomPi init. */\nexport const PI_DISPATCHER_VERSION = 2;\n\nconst DISPATCHER_ENTRY = 'dispatcher.mjs';\nconst PACKAGE_MANIFEST = 'package.json';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nfunction dispatcherManifest(): string {\n return `${JSON.stringify(\n {\n name: DOOM_PACKAGE_NAME,\n private: true,\n type: 'module',\n doompiDispatcher: PI_DISPATCHER_VERSION,\n pi: { extensions: [`./${DISPATCHER_ENTRY}`] },\n },\n null,\n 2,\n )}\\n`;\n}\n\nfunction dispatcherSource(): string {\n return `import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nconst REGISTRATION_VERSION = ${String(SYNC_REGISTRATION_VERSION)};\nconst PACKAGE_NAME = ${JSON.stringify(DOOM_PACKAGE_NAME)};\nconst WARNING = 'warning';\n\nfunction canonical(target) {\n return fs.realpathSync.native(path.resolve(target));\n}\n\nfunction isFile(target) {\n try { return fs.statSync(target).isFile(); } catch { return false; }\n}\n\nfunction isDirectory(target) {\n try { return fs.statSync(target).isDirectory(); } catch { return false; }\n}\n\nfunction repositoryRoot(start) {\n let directory = path.resolve(start);\n while (true) {\n const git = path.join(directory, '.git');\n if (isDirectory(path.join(directory, '.doom')) || isFile(path.join(directory, '.pi', 'settings.json')) || isDirectory(git) || isFile(git)) return canonical(directory);\n const parent = path.dirname(directory);\n if (parent === directory) return undefined;\n directory = parent;\n }\n}\n\nfunction gitDirectory(root) {\n const target = path.join(root, '.git');\n try {\n const stat = fs.statSync(target);\n if (stat.isDirectory()) return canonical(target);\n if (!stat.isFile()) return undefined;\n const match = /^gitdir:\\\\s*(.+)$/imu.exec(fs.readFileSync(target, 'utf8').trim());\n return match?.[1] ? canonical(path.resolve(root, match[1].trim())) : undefined;\n } catch { return undefined; }\n}\n\nfunction commonDirectory(root) {\n const git = gitDirectory(root);\n if (!git) return undefined;\n try {\n const relative = fs.readFileSync(path.join(git, 'commondir'), 'utf8').trim();\n return relative ? canonical(path.resolve(git, relative)) : git;\n } catch { return git; }\n}\n\nfunction identity(root) {\n const common = commonDirectory(root);\n const token = common ? \\`git:\\${common}\\` : \\`root:\\${root}\\`;\n const hash = (value) => crypto.createHash('sha256').update(value, 'utf8').digest('hex').slice(0, 32);\n return { repositoryId: hash(token), worktreeId: hash(\\`\\${token}\\\\0worktree:\\${root}\\`) };\n}\n\nfunction inside(directory, target) {\n const relative = path.relative(canonical(directory), canonical(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction registration(root) {\n const ids = identity(root);\n const recordPath = path.join(os.homedir(), '.pi', '.doom', 'sync', 'registrations', ids.repositoryId, \\`\\${ids.worktreeId}.json\\`);\n const value = JSON.parse(fs.readFileSync(recordPath, 'utf8'));\n if (value.version !== REGISTRATION_VERSION || canonical(value.root) !== root) throw new Error('registration identity mismatch');\n if (value.identity?.repositoryId !== ids.repositoryId || value.identity?.worktreeId !== ids.worktreeId) throw new Error('registration worktree mismatch');\n if (!inside(value.generationRoot, value.statePath) || !inside(value.package.root, value.package.entry)) throw new Error('registration path escapes its owner');\n const stateHash = crypto.createHash('sha256').update(fs.readFileSync(value.statePath)).digest('hex');\n if (stateHash !== value.stateSha256) throw new Error('registration state hash mismatch');\n const manifestPath = path.join(canonical(value.package.root), 'package.json');\n if (canonical(value.package.manifestPath) !== canonical(manifestPath)) throw new Error('registration package manifest mismatch');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n if (manifest.name !== PACKAGE_NAME || manifest.version !== value.package.version) throw new Error('registration package mismatch');\n const entries = manifest.pi?.extensions;\n if (!Array.isArray(entries) || !entries.some((entry) => typeof entry === 'string' && canonical(path.resolve(value.package.root, entry)) === canonical(value.package.entry))) throw new Error('registration package entry mismatch');\n return value;\n}\n\nfunction report(pi, target, message) {\n pi.on('session_start', (_event, context) => context.ui.notify(\\`doompi could not load \\${target}: \\${message}. Run doompi init, then doompi sync.\\`, WARNING));\n}\nexport default async function doompiDispatcher(pi) {\n const repository = repositoryRoot(process.cwd());\n try {\n const root = repository ?? canonical(path.join(os.homedir(), '.pi', '.doom'));\n const record = registration(root);\n const loaded = await import(pathToFileURL(record.package.entry).href);\n if (typeof loaded.default !== 'function') throw new Error('recorded package entry has no extension factory');\n await loaded.default(pi);\n } catch (error) {\n report(pi, repository ? 'this repository' : 'the global composition', error instanceof Error ? error.message : String(error));\n }\n}\n`;\n}\n\n/** Stable path Pi derives from the user settings package spelling. */\nexport function piExtensionDispatcherPath(piDirectory: string): string {\n return path.join(piDirectory, ...DOOM_PACKAGE_NAME.split('/'));\n}\n\nfunction managedManifestVersion(directory: string): number | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(directory, PACKAGE_MANIFEST), 'utf8')) as {\n name?: unknown;\n doompiDispatcher?: unknown;\n };\n return parsed.name === DOOM_PACKAGE_NAME && Number.isSafeInteger(parsed.doompiDispatcher)\n ? (parsed.doompiDispatcher as number)\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction managedManifest(directory: string): boolean {\n return managedManifestVersion(directory) === PI_DISPATCHER_VERSION;\n}\n\nfunction upgradeableManagedManifest(directory: string): boolean {\n const version = managedManifestVersion(directory);\n return version !== undefined && version > 0 && version <= PI_DISPATCHER_VERSION;\n}\n\n/** Whether init's dispatcher package has a supported protocol and complete entry. */\nexport function piExtensionDispatcherIsCurrent(piDirectory: string): boolean {\n const directory = piExtensionDispatcherPath(piDirectory);\n const stat = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (!stat?.isDirectory() || stat.isSymbolicLink() || !managedManifest(directory)) return false;\n return fs.lstatSync(path.join(directory, DISPATCHER_ENTRY), { throwIfNoEntry: false })?.isFile() === true;\n}\n\nfunction legacyLinkIsManaged(linkPath: string): boolean {\n try {\n return manifestName(fs.realpathSync(linkPath)) === DOOM_PACKAGE_NAME;\n } catch {\n return true;\n }\n}\n\n/** Installs or repairs the init-owned dependency-free dispatcher package. */\nexport function writePiExtensionDispatcher(piDirectory: string): string {\n const directory = piExtensionDispatcherPath(piDirectory);\n const current = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (current?.isSymbolicLink()) {\n if (!legacyLinkIsManaged(directory)) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n fs.rmSync(directory, { force: true });\n } else if (current && (!current.isDirectory() || !upgradeableManagedManifest(directory))) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n\n const scope = path.dirname(directory);\n const scopeStat = fs.lstatSync(scope, { throwIfNoEntry: false });\n if (scopeStat && !scopeStat.isDirectory()) {\n throw new Error(`Refusing to use unmanaged Pi extension scope: ${scope}`);\n }\n fs.mkdirSync(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n fs.chmodSync(directory, PRIVATE_DIRECTORY_MODE);\n writeFileAtomic(path.join(directory, PACKAGE_MANIFEST), dispatcherManifest());\n writeFileAtomic(path.join(directory, DISPATCHER_ENTRY), dispatcherSource());\n fs.chmodSync(path.join(directory, PACKAGE_MANIFEST), PRIVATE_FILE_MODE);\n fs.chmodSync(path.join(directory, DISPATCHER_ENTRY), PRIVATE_FILE_MODE);\n return directory;\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{DOOM_PACKAGE_NAME as e,manifestName as t}from"./doomPackage.mjs";import{writeFileAtomic as n}from"./serialization/json.mjs";import r from"node:path";import i from"node:fs";const a=`dispatcher.mjs`,o=`package.json`;function s(){return`${JSON.stringify({name:e,private:!0,type:`module`,doompiDispatcher:
|
|
1
|
+
import{DOOM_PACKAGE_NAME as e,manifestName as t}from"./doomPackage.mjs";import{writeFileAtomic as n}from"./serialization/json.mjs";import r from"node:path";import i from"node:fs";const a=`dispatcher.mjs`,o=`package.json`;function s(){return`${JSON.stringify({name:e,private:!0,type:`module`,doompiDispatcher:2,pi:{extensions:[`./${a}`]}},null,2)}\n`}function c(){return`import crypto from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
@@ -81,21 +81,20 @@ function registration(root) {
|
|
|
81
81
|
return value;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
function report(pi, message) {
|
|
85
|
-
pi.on('session_start', (_event, context) => context.ui.notify(\`doompi could not load
|
|
84
|
+
function report(pi, target, message) {
|
|
85
|
+
pi.on('session_start', (_event, context) => context.ui.notify(\`doompi could not load \${target}: \${message}. Run doompi init, then doompi sync.\`, WARNING));
|
|
86
86
|
}
|
|
87
|
-
|
|
88
87
|
export default async function doompiDispatcher(pi) {
|
|
89
|
-
const
|
|
90
|
-
if (!root) return;
|
|
88
|
+
const repository = repositoryRoot(process.cwd());
|
|
91
89
|
try {
|
|
90
|
+
const root = repository ?? canonical(path.join(os.homedir(), '.pi', '.doom'));
|
|
92
91
|
const record = registration(root);
|
|
93
92
|
const loaded = await import(pathToFileURL(record.package.entry).href);
|
|
94
93
|
if (typeof loaded.default !== 'function') throw new Error('recorded package entry has no extension factory');
|
|
95
94
|
await loaded.default(pi);
|
|
96
95
|
} catch (error) {
|
|
97
|
-
report(pi, error instanceof Error ? error.message : String(error));
|
|
96
|
+
report(pi, repository ? 'this repository' : 'the global composition', error instanceof Error ? error.message : String(error));
|
|
98
97
|
}
|
|
99
98
|
}
|
|
100
|
-
`}function l(t){return r.join(t,...e.split(`/`))}function u(e){try{let t=JSON.parse(i.readFileSync(r.join(e,o),`utf8`));return t.name===`@agimon-ai/doompi`&&t.doompiDispatcher
|
|
99
|
+
`}function l(t){return r.join(t,...e.split(`/`))}function u(e){try{let t=JSON.parse(i.readFileSync(r.join(e,o),`utf8`));return t.name===`@agimon-ai/doompi`&&Number.isSafeInteger(t.doompiDispatcher)?t.doompiDispatcher:void 0}catch{return}}function d(e){return u(e)===2}function f(e){let t=u(e);return t!==void 0&&t>0&&t<=2}function p(e){let t=l(e),n=i.lstatSync(t,{throwIfNoEntry:!1});return!n?.isDirectory()||n.isSymbolicLink()||!d(t)?!1:i.lstatSync(r.join(t,a),{throwIfNoEntry:!1})?.isFile()===!0}function m(n){try{return t(i.realpathSync(n))===e}catch{return!0}}function h(e){let t=l(e),u=i.lstatSync(t,{throwIfNoEntry:!1});if(u?.isSymbolicLink()){if(!m(t))throw Error(`Refusing to replace unmanaged Pi extension path: ${t}`);i.rmSync(t,{force:!0})}else if(u&&(!u.isDirectory()||!f(t)))throw Error(`Refusing to replace unmanaged Pi extension path: ${t}`);let d=r.dirname(t),p=i.lstatSync(d,{throwIfNoEntry:!1});if(p&&!p.isDirectory())throw Error(`Refusing to use unmanaged Pi extension scope: ${d}`);return i.mkdirSync(t,{mode:448,recursive:!0}),i.chmodSync(t,448),n(r.join(t,o),s()),n(r.join(t,a),c()),i.chmodSync(r.join(t,o),384),i.chmodSync(r.join(t,a),384),t}export{p as piExtensionDispatcherIsCurrent,l as piExtensionDispatcherPath,h as writePiExtensionDispatcher};
|
|
101
100
|
//# sourceMappingURL=piExtensionDispatcher.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"piExtensionDispatcher.mjs","names":[],"sources":["../../../src/adapters/piExtensionDispatcher.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { DOOM_PACKAGE_NAME, manifestName } from './doomPackage.ts';\nimport { writeFileAtomic } from './serialization/json.ts';\nimport { SYNC_REGISTRATION_VERSION } from './syncRegistration.ts';\n\n/** Protocol marker proving that the user package path is managed by DoomPi init. */\nexport const PI_DISPATCHER_VERSION = 1;\n\nconst DISPATCHER_ENTRY = 'dispatcher.mjs';\nconst PACKAGE_MANIFEST = 'package.json';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nfunction dispatcherManifest(): string {\n return `${JSON.stringify(\n {\n name: DOOM_PACKAGE_NAME,\n private: true,\n type: 'module',\n doompiDispatcher: PI_DISPATCHER_VERSION,\n pi: { extensions: [`./${DISPATCHER_ENTRY}`] },\n },\n null,\n 2,\n )}\\n`;\n}\n\nfunction dispatcherSource(): string {\n return `import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nconst REGISTRATION_VERSION = ${String(SYNC_REGISTRATION_VERSION)};\nconst PACKAGE_NAME = ${JSON.stringify(DOOM_PACKAGE_NAME)};\nconst WARNING = 'warning';\n\nfunction canonical(target) {\n return fs.realpathSync.native(path.resolve(target));\n}\n\nfunction isFile(target) {\n try { return fs.statSync(target).isFile(); } catch { return false; }\n}\n\nfunction isDirectory(target) {\n try { return fs.statSync(target).isDirectory(); } catch { return false; }\n}\n\nfunction repositoryRoot(start) {\n let directory = path.resolve(start);\n while (true) {\n const git = path.join(directory, '.git');\n if (isDirectory(path.join(directory, '.doom')) || isFile(path.join(directory, '.pi', 'settings.json')) || isDirectory(git) || isFile(git)) return canonical(directory);\n const parent = path.dirname(directory);\n if (parent === directory) return undefined;\n directory = parent;\n }\n}\n\nfunction gitDirectory(root) {\n const target = path.join(root, '.git');\n try {\n const stat = fs.statSync(target);\n if (stat.isDirectory()) return canonical(target);\n if (!stat.isFile()) return undefined;\n const match = /^gitdir:\\\\s*(.+)$/imu.exec(fs.readFileSync(target, 'utf8').trim());\n return match?.[1] ? canonical(path.resolve(root, match[1].trim())) : undefined;\n } catch { return undefined; }\n}\n\nfunction commonDirectory(root) {\n const git = gitDirectory(root);\n if (!git) return undefined;\n try {\n const relative = fs.readFileSync(path.join(git, 'commondir'), 'utf8').trim();\n return relative ? canonical(path.resolve(git, relative)) : git;\n } catch { return git; }\n}\n\nfunction identity(root) {\n const common = commonDirectory(root);\n const token = common ? \\`git:\\${common}\\` : \\`root:\\${root}\\`;\n const hash = (value) => crypto.createHash('sha256').update(value, 'utf8').digest('hex').slice(0, 32);\n return { repositoryId: hash(token), worktreeId: hash(\\`\\${token}\\\\0worktree:\\${root}\\`) };\n}\n\nfunction inside(directory, target) {\n const relative = path.relative(canonical(directory), canonical(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction registration(root) {\n const ids = identity(root);\n const recordPath = path.join(os.homedir(), '.pi', '.doom', 'sync', 'registrations', ids.repositoryId, \\`\\${ids.worktreeId}.json\\`);\n const value = JSON.parse(fs.readFileSync(recordPath, 'utf8'));\n if (value.version !== REGISTRATION_VERSION || canonical(value.root) !== root) throw new Error('registration identity mismatch');\n if (value.identity?.repositoryId !== ids.repositoryId || value.identity?.worktreeId !== ids.worktreeId) throw new Error('registration worktree mismatch');\n if (!inside(value.generationRoot, value.statePath) || !inside(value.package.root, value.package.entry)) throw new Error('registration path escapes its owner');\n const stateHash = crypto.createHash('sha256').update(fs.readFileSync(value.statePath)).digest('hex');\n if (stateHash !== value.stateSha256) throw new Error('registration state hash mismatch');\n const manifestPath = path.join(canonical(value.package.root), 'package.json');\n if (canonical(value.package.manifestPath) !== canonical(manifestPath)) throw new Error('registration package manifest mismatch');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n if (manifest.name !== PACKAGE_NAME || manifest.version !== value.package.version) throw new Error('registration package mismatch');\n const entries = manifest.pi?.extensions;\n if (!Array.isArray(entries) || !entries.some((entry) => typeof entry === 'string' && canonical(path.resolve(value.package.root, entry)) === canonical(value.package.entry))) throw new Error('registration package entry mismatch');\n return value;\n}\n\nfunction report(pi, message) {\n pi.on('session_start', (_event, context) => context.ui.notify(\\`doompi could not load this repository: \\${message}. Run doompi init, then doompi sync.\\`, WARNING));\n}\n\nexport default async function doompiDispatcher(pi) {\n const root = repositoryRoot(process.cwd());\n if (!root) return;\n try {\n const record = registration(root);\n const loaded = await import(pathToFileURL(record.package.entry).href);\n if (typeof loaded.default !== 'function') throw new Error('recorded package entry has no extension factory');\n await loaded.default(pi);\n } catch (error) {\n report(pi, error instanceof Error ? error.message : String(error));\n }\n}\n`;\n}\n\n/** Stable path Pi derives from the user settings package spelling. */\nexport function piExtensionDispatcherPath(piDirectory: string): string {\n return path.join(piDirectory, ...DOOM_PACKAGE_NAME.split('/'));\n}\n\nfunction managedManifest(directory: string): boolean {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(directory, PACKAGE_MANIFEST), 'utf8')) as {\n name?: unknown;\n doompiDispatcher?: unknown;\n };\n return parsed.name === DOOM_PACKAGE_NAME && parsed.doompiDispatcher === PI_DISPATCHER_VERSION;\n } catch {\n return false;\n }\n}\n\n/** Whether init's dispatcher package has a supported protocol and complete entry. */\nexport function piExtensionDispatcherIsCurrent(piDirectory: string): boolean {\n const directory = piExtensionDispatcherPath(piDirectory);\n const stat = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (!stat?.isDirectory() || stat.isSymbolicLink() || !managedManifest(directory)) return false;\n return fs.lstatSync(path.join(directory, DISPATCHER_ENTRY), { throwIfNoEntry: false })?.isFile() === true;\n}\n\nfunction legacyLinkIsManaged(linkPath: string): boolean {\n try {\n return manifestName(fs.realpathSync(linkPath)) === DOOM_PACKAGE_NAME;\n } catch {\n return true;\n }\n}\n\n/** Installs or repairs the init-owned dependency-free dispatcher package. */\nexport function writePiExtensionDispatcher(piDirectory: string): string {\n const directory = piExtensionDispatcherPath(piDirectory);\n const current = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (current?.isSymbolicLink()) {\n if (!legacyLinkIsManaged(directory)) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n fs.rmSync(directory, { force: true });\n } else if (current && (!current.isDirectory() || !managedManifest(directory))) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n\n const scope = path.dirname(directory);\n const scopeStat = fs.lstatSync(scope, { throwIfNoEntry: false });\n if (scopeStat && !scopeStat.isDirectory()) {\n throw new Error(`Refusing to use unmanaged Pi extension scope: ${scope}`);\n }\n fs.mkdirSync(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n fs.chmodSync(directory, PRIVATE_DIRECTORY_MODE);\n writeFileAtomic(path.join(directory, PACKAGE_MANIFEST), dispatcherManifest());\n writeFileAtomic(path.join(directory, DISPATCHER_ENTRY), dispatcherSource());\n fs.chmodSync(path.join(directory, PACKAGE_MANIFEST), PRIVATE_FILE_MODE);\n fs.chmodSync(path.join(directory, DISPATCHER_ENTRY), PRIVATE_FILE_MODE);\n return directory;\n}\n"],"mappings":"mLASA,MAAM,EAAmB,iBACnB,EAAmB,eAIzB,SAAS,GAA6B,CACpC,MAAO,GAAG,KAAK,UACb,CACE,KAAM,EACN,QAAS,GACT,KAAM,SACN,iBAAA,EACA,GAAI,CAAE,WAAY,CAAC,KAAK,GAAkB,CAAE,CAC9C,EACA,KACA,CACF,EAAE,GACJ,CAEA,SAAS,GAA2B,CAClC,MAAO;;;;;;;uBAOc,KAAK,UAAU,CAAiB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6FzD,CAGA,SAAgB,EAA0B,EAA6B,CACrE,OAAO,EAAK,KAAK,EAAa,GAAG,EAAkB,MAAM,GAAG,CAAC,CAC/D,CAEA,SAAS,EAAgB,EAA4B,CACnD,GAAI,CACF,IAAM,EAAS,KAAK,MAAM,EAAG,aAAa,EAAK,KAAK,EAAW,CAAgB,EAAG,MAAM,CAAC,EAIzF,OAAO,EAAO,OAAA,qBAA8B,EAAO,mBAAA,CACrD,MAAQ,CACN,MAAO,EACT,CACF,CAGA,SAAgB,EAA+B,EAA8B,CAC3E,IAAM,EAAY,EAA0B,CAAW,EACjD,EAAO,EAAG,UAAU,EAAW,CAAE,eAAgB,EAAM,CAAC,EAE9D,MADI,CAAC,GAAM,YAAY,GAAK,EAAK,eAAe,GAAK,CAAC,EAAgB,CAAS,EAAU,GAClF,EAAG,UAAU,EAAK,KAAK,EAAW,CAAgB,EAAG,CAAE,eAAgB,EAAM,CAAC,CAAC,EAAE,OAAO,IAAM,EACvG,CAEA,SAAS,EAAoB,EAA2B,CACtD,GAAI,CACF,OAAO,EAAa,EAAG,aAAa,CAAQ,CAAC,IAAM,CACrD,MAAQ,CACN,MAAO,EACT,CACF,CAGA,SAAgB,EAA2B,EAA6B,CACtE,IAAM,EAAY,EAA0B,CAAW,EACjD,EAAU,EAAG,UAAU,EAAW,CAAE,eAAgB,EAAM,CAAC,EACjE,GAAI,GAAS,eAAe,EAAG,CAC7B,GAAI,CAAC,EAAoB,CAAS,EAChC,MAAU,MAAM,oDAAoD,GAAW,EAEjF,EAAG,OAAO,EAAW,CAAE,MAAO,EAAK,CAAC,CACtC,MAAO,GAAI,IAAY,CAAC,EAAQ,YAAY,GAAK,CAAC,EAAgB,CAAS,GACzE,MAAU,MAAM,oDAAoD,GAAW,EAGjF,IAAM,EAAQ,EAAK,QAAQ,CAAS,EAC9B,EAAY,EAAG,UAAU,EAAO,CAAE,eAAgB,EAAM,CAAC,EAC/D,GAAI,GAAa,CAAC,EAAU,YAAY,EACtC,MAAU,MAAM,iDAAiD,GAAO,EAQ1E,OANA,EAAG,UAAU,EAAW,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EACzE,EAAG,UAAU,EAAW,GAAsB,EAC9C,EAAgB,EAAK,KAAK,EAAW,CAAgB,EAAG,EAAmB,CAAC,EAC5E,EAAgB,EAAK,KAAK,EAAW,CAAgB,EAAG,EAAiB,CAAC,EAC1E,EAAG,UAAU,EAAK,KAAK,EAAW,CAAgB,EAAG,GAAiB,EACtE,EAAG,UAAU,EAAK,KAAK,EAAW,CAAgB,EAAG,GAAiB,EAC/D,CACT"}
|
|
1
|
+
{"version":3,"file":"piExtensionDispatcher.mjs","names":[],"sources":["../../../src/adapters/piExtensionDispatcher.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { DOOM_PACKAGE_NAME, manifestName } from './doomPackage.ts';\nimport { writeFileAtomic } from './serialization/json.ts';\nimport { SYNC_REGISTRATION_VERSION } from './syncRegistration.ts';\n\n/** Protocol marker proving that the user package path is managed by DoomPi init. */\nexport const PI_DISPATCHER_VERSION = 2;\n\nconst DISPATCHER_ENTRY = 'dispatcher.mjs';\nconst PACKAGE_MANIFEST = 'package.json';\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\nfunction dispatcherManifest(): string {\n return `${JSON.stringify(\n {\n name: DOOM_PACKAGE_NAME,\n private: true,\n type: 'module',\n doompiDispatcher: PI_DISPATCHER_VERSION,\n pi: { extensions: [`./${DISPATCHER_ENTRY}`] },\n },\n null,\n 2,\n )}\\n`;\n}\n\nfunction dispatcherSource(): string {\n return `import crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nconst REGISTRATION_VERSION = ${String(SYNC_REGISTRATION_VERSION)};\nconst PACKAGE_NAME = ${JSON.stringify(DOOM_PACKAGE_NAME)};\nconst WARNING = 'warning';\n\nfunction canonical(target) {\n return fs.realpathSync.native(path.resolve(target));\n}\n\nfunction isFile(target) {\n try { return fs.statSync(target).isFile(); } catch { return false; }\n}\n\nfunction isDirectory(target) {\n try { return fs.statSync(target).isDirectory(); } catch { return false; }\n}\n\nfunction repositoryRoot(start) {\n let directory = path.resolve(start);\n while (true) {\n const git = path.join(directory, '.git');\n if (isDirectory(path.join(directory, '.doom')) || isFile(path.join(directory, '.pi', 'settings.json')) || isDirectory(git) || isFile(git)) return canonical(directory);\n const parent = path.dirname(directory);\n if (parent === directory) return undefined;\n directory = parent;\n }\n}\n\nfunction gitDirectory(root) {\n const target = path.join(root, '.git');\n try {\n const stat = fs.statSync(target);\n if (stat.isDirectory()) return canonical(target);\n if (!stat.isFile()) return undefined;\n const match = /^gitdir:\\\\s*(.+)$/imu.exec(fs.readFileSync(target, 'utf8').trim());\n return match?.[1] ? canonical(path.resolve(root, match[1].trim())) : undefined;\n } catch { return undefined; }\n}\n\nfunction commonDirectory(root) {\n const git = gitDirectory(root);\n if (!git) return undefined;\n try {\n const relative = fs.readFileSync(path.join(git, 'commondir'), 'utf8').trim();\n return relative ? canonical(path.resolve(git, relative)) : git;\n } catch { return git; }\n}\n\nfunction identity(root) {\n const common = commonDirectory(root);\n const token = common ? \\`git:\\${common}\\` : \\`root:\\${root}\\`;\n const hash = (value) => crypto.createHash('sha256').update(value, 'utf8').digest('hex').slice(0, 32);\n return { repositoryId: hash(token), worktreeId: hash(\\`\\${token}\\\\0worktree:\\${root}\\`) };\n}\n\nfunction inside(directory, target) {\n const relative = path.relative(canonical(directory), canonical(target));\n return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));\n}\n\nfunction registration(root) {\n const ids = identity(root);\n const recordPath = path.join(os.homedir(), '.pi', '.doom', 'sync', 'registrations', ids.repositoryId, \\`\\${ids.worktreeId}.json\\`);\n const value = JSON.parse(fs.readFileSync(recordPath, 'utf8'));\n if (value.version !== REGISTRATION_VERSION || canonical(value.root) !== root) throw new Error('registration identity mismatch');\n if (value.identity?.repositoryId !== ids.repositoryId || value.identity?.worktreeId !== ids.worktreeId) throw new Error('registration worktree mismatch');\n if (!inside(value.generationRoot, value.statePath) || !inside(value.package.root, value.package.entry)) throw new Error('registration path escapes its owner');\n const stateHash = crypto.createHash('sha256').update(fs.readFileSync(value.statePath)).digest('hex');\n if (stateHash !== value.stateSha256) throw new Error('registration state hash mismatch');\n const manifestPath = path.join(canonical(value.package.root), 'package.json');\n if (canonical(value.package.manifestPath) !== canonical(manifestPath)) throw new Error('registration package manifest mismatch');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));\n if (manifest.name !== PACKAGE_NAME || manifest.version !== value.package.version) throw new Error('registration package mismatch');\n const entries = manifest.pi?.extensions;\n if (!Array.isArray(entries) || !entries.some((entry) => typeof entry === 'string' && canonical(path.resolve(value.package.root, entry)) === canonical(value.package.entry))) throw new Error('registration package entry mismatch');\n return value;\n}\n\nfunction report(pi, target, message) {\n pi.on('session_start', (_event, context) => context.ui.notify(\\`doompi could not load \\${target}: \\${message}. Run doompi init, then doompi sync.\\`, WARNING));\n}\nexport default async function doompiDispatcher(pi) {\n const repository = repositoryRoot(process.cwd());\n try {\n const root = repository ?? canonical(path.join(os.homedir(), '.pi', '.doom'));\n const record = registration(root);\n const loaded = await import(pathToFileURL(record.package.entry).href);\n if (typeof loaded.default !== 'function') throw new Error('recorded package entry has no extension factory');\n await loaded.default(pi);\n } catch (error) {\n report(pi, repository ? 'this repository' : 'the global composition', error instanceof Error ? error.message : String(error));\n }\n}\n`;\n}\n\n/** Stable path Pi derives from the user settings package spelling. */\nexport function piExtensionDispatcherPath(piDirectory: string): string {\n return path.join(piDirectory, ...DOOM_PACKAGE_NAME.split('/'));\n}\n\nfunction managedManifestVersion(directory: string): number | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(directory, PACKAGE_MANIFEST), 'utf8')) as {\n name?: unknown;\n doompiDispatcher?: unknown;\n };\n return parsed.name === DOOM_PACKAGE_NAME && Number.isSafeInteger(parsed.doompiDispatcher)\n ? (parsed.doompiDispatcher as number)\n : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction managedManifest(directory: string): boolean {\n return managedManifestVersion(directory) === PI_DISPATCHER_VERSION;\n}\n\nfunction upgradeableManagedManifest(directory: string): boolean {\n const version = managedManifestVersion(directory);\n return version !== undefined && version > 0 && version <= PI_DISPATCHER_VERSION;\n}\n\n/** Whether init's dispatcher package has a supported protocol and complete entry. */\nexport function piExtensionDispatcherIsCurrent(piDirectory: string): boolean {\n const directory = piExtensionDispatcherPath(piDirectory);\n const stat = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (!stat?.isDirectory() || stat.isSymbolicLink() || !managedManifest(directory)) return false;\n return fs.lstatSync(path.join(directory, DISPATCHER_ENTRY), { throwIfNoEntry: false })?.isFile() === true;\n}\n\nfunction legacyLinkIsManaged(linkPath: string): boolean {\n try {\n return manifestName(fs.realpathSync(linkPath)) === DOOM_PACKAGE_NAME;\n } catch {\n return true;\n }\n}\n\n/** Installs or repairs the init-owned dependency-free dispatcher package. */\nexport function writePiExtensionDispatcher(piDirectory: string): string {\n const directory = piExtensionDispatcherPath(piDirectory);\n const current = fs.lstatSync(directory, { throwIfNoEntry: false });\n if (current?.isSymbolicLink()) {\n if (!legacyLinkIsManaged(directory)) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n fs.rmSync(directory, { force: true });\n } else if (current && (!current.isDirectory() || !upgradeableManagedManifest(directory))) {\n throw new Error(`Refusing to replace unmanaged Pi extension path: ${directory}`);\n }\n\n const scope = path.dirname(directory);\n const scopeStat = fs.lstatSync(scope, { throwIfNoEntry: false });\n if (scopeStat && !scopeStat.isDirectory()) {\n throw new Error(`Refusing to use unmanaged Pi extension scope: ${scope}`);\n }\n fs.mkdirSync(directory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n fs.chmodSync(directory, PRIVATE_DIRECTORY_MODE);\n writeFileAtomic(path.join(directory, PACKAGE_MANIFEST), dispatcherManifest());\n writeFileAtomic(path.join(directory, DISPATCHER_ENTRY), dispatcherSource());\n fs.chmodSync(path.join(directory, PACKAGE_MANIFEST), PRIVATE_FILE_MODE);\n fs.chmodSync(path.join(directory, DISPATCHER_ENTRY), PRIVATE_FILE_MODE);\n return directory;\n}\n"],"mappings":"mLASA,MAAM,EAAmB,iBACnB,EAAmB,eAIzB,SAAS,GAA6B,CACpC,MAAO,GAAG,KAAK,UACb,CACE,KAAM,EACN,QAAS,GACT,KAAM,SACN,iBAAA,EACA,GAAI,CAAE,WAAY,CAAC,KAAK,GAAkB,CAAE,CAC9C,EACA,KACA,CACF,EAAE,GACJ,CAEA,SAAS,GAA2B,CAClC,MAAO;;;;;;;uBAOc,KAAK,UAAU,CAAiB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4FzD,CAGA,SAAgB,EAA0B,EAA6B,CACrE,OAAO,EAAK,KAAK,EAAa,GAAG,EAAkB,MAAM,GAAG,CAAC,CAC/D,CAEA,SAAS,EAAuB,EAAuC,CACrE,GAAI,CACF,IAAM,EAAS,KAAK,MAAM,EAAG,aAAa,EAAK,KAAK,EAAW,CAAgB,EAAG,MAAM,CAAC,EAIzF,OAAO,EAAO,OAAA,qBAA8B,OAAO,cAAc,EAAO,gBAAgB,EACnF,EAAO,iBACR,IAAA,EACN,MAAQ,CACN,MACF,CACF,CAEA,SAAS,EAAgB,EAA4B,CACnD,OAAO,EAAuB,CAAS,IAAA,CACzC,CAEA,SAAS,EAA2B,EAA4B,CAC9D,IAAM,EAAU,EAAuB,CAAS,EAChD,OAAO,IAAY,IAAA,IAAa,EAAU,GAAK,GAAA,CACjD,CAGA,SAAgB,EAA+B,EAA8B,CAC3E,IAAM,EAAY,EAA0B,CAAW,EACjD,EAAO,EAAG,UAAU,EAAW,CAAE,eAAgB,EAAM,CAAC,EAE9D,MADI,CAAC,GAAM,YAAY,GAAK,EAAK,eAAe,GAAK,CAAC,EAAgB,CAAS,EAAU,GAClF,EAAG,UAAU,EAAK,KAAK,EAAW,CAAgB,EAAG,CAAE,eAAgB,EAAM,CAAC,CAAC,EAAE,OAAO,IAAM,EACvG,CAEA,SAAS,EAAoB,EAA2B,CACtD,GAAI,CACF,OAAO,EAAa,EAAG,aAAa,CAAQ,CAAC,IAAM,CACrD,MAAQ,CACN,MAAO,EACT,CACF,CAGA,SAAgB,EAA2B,EAA6B,CACtE,IAAM,EAAY,EAA0B,CAAW,EACjD,EAAU,EAAG,UAAU,EAAW,CAAE,eAAgB,EAAM,CAAC,EACjE,GAAI,GAAS,eAAe,EAAG,CAC7B,GAAI,CAAC,EAAoB,CAAS,EAChC,MAAU,MAAM,oDAAoD,GAAW,EAEjF,EAAG,OAAO,EAAW,CAAE,MAAO,EAAK,CAAC,CACtC,MAAO,GAAI,IAAY,CAAC,EAAQ,YAAY,GAAK,CAAC,EAA2B,CAAS,GACpF,MAAU,MAAM,oDAAoD,GAAW,EAGjF,IAAM,EAAQ,EAAK,QAAQ,CAAS,EAC9B,EAAY,EAAG,UAAU,EAAO,CAAE,eAAgB,EAAM,CAAC,EAC/D,GAAI,GAAa,CAAC,EAAU,YAAY,EACtC,MAAU,MAAM,iDAAiD,GAAO,EAQ1E,OANA,EAAG,UAAU,EAAW,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EACzE,EAAG,UAAU,EAAW,GAAsB,EAC9C,EAAgB,EAAK,KAAK,EAAW,CAAgB,EAAG,EAAmB,CAAC,EAC5E,EAAgB,EAAK,KAAK,EAAW,CAAgB,EAAG,EAAiB,CAAC,EAC1E,EAAG,UAAU,EAAK,KAAK,EAAW,CAAgB,EAAG,GAAiB,EACtE,EAAG,UAAU,EAAK,KAAK,EAAW,CAAgB,EAAG,GAAiB,EAC/D,CACT"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);let n=require("node:fs");n=e.__toESM(n,1);
|
|
1
|
+
const e=require("../../../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);let n=require("node:fs");n=e.__toESM(n,1);let r=require("@agimon-ai/doompi-config/config");const i=t.default.join(`.pi`,`settings.json`);function a(e){try{return n.default.statSync(e).isDirectory()}catch{return!1}}function o(e){try{return n.default.statSync(e).isFile()}catch{return!1}}function s(e){let n=t.default.join(e,`.git`);return a(t.default.join(e,`.doom`))||o(t.default.join(e,i))||a(n)||o(n)}function c(e){let n=t.default.resolve(e);for(;;){if(s(n))return n;let r=t.default.dirname(n);if(r===n)throw Error(`Could not find repository root from ${e}`);n=r}}function l(e,t){try{return c(e)}catch{return(0,r.globalDoomConfigDirectory)(t)}}exports.findRepositoryRoot=c,exports.isRepositoryRoot=s,exports.resolveDoomConfigurationRoot=l;
|
|
2
2
|
//# sourceMappingURL=repository.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repository.cjs","names":["path","fs"],"sources":["../../../../src/adapters/repository/repository.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\n/** Doom's own repository configuration directory. */\nconst DOOM_DIRECTORY = '.doom';\n/** Git's root marker, which may be a directory or a worktree pointer file. */\nconst GIT_DIRECTORY = '.git';\n/** A Pi project that has been configured, rather than any stray `.pi` folder. */\nconst PI_SETTINGS = path.join('.pi', 'settings.json');\n\nfunction isDirectory(target: string): boolean {\n try {\n return fs.statSync(target).isDirectory();\n } catch {\n return false;\n }\n}\n\nfunction isFile(target: string): boolean {\n try {\n return fs.statSync(target).isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * True when a directory carries a marker Doom Pi is willing to treat as a root.\n *\n * Doom and Pi markers identify configured projects. A Git marker also counts so\n * personal configuration can be synchronized into a new checkout before it has\n * repository-local configuration. Build-tooling markers remain deliberately\n * excluded because they do not establish a repository boundary.\n */\nexport function isRepositoryRoot(directory: string): boolean {\n const gitMarker = path.join(directory, GIT_DIRECTORY);\n return (\n isDirectory(path.join(directory, DOOM_DIRECTORY)) ||\n isFile(path.join(directory, PI_SETTINGS)) ||\n isDirectory(gitMarker) ||\n isFile(gitMarker)\n );\n}\n\n/**\n * Walks up from `start` looking for the repository root.\n *\n * The nearest marked ancestor wins, so a session started in a subdirectory\n * resolves to the repository that configured Doom rather than to a parent\n * checkout that happens to sit above it.\n */\nexport function findRepositoryRoot(start: string): string {\n let directory = path.resolve(start);\n while (true) {\n if (isRepositoryRoot(directory)) return directory;\n const parent = path.dirname(directory);\n if (parent === directory) throw new Error(`Could not find repository root from ${start}`);\n directory = parent;\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"repository.cjs","names":["path","fs","globalDoomConfigDirectory"],"sources":["../../../../src/adapters/repository/repository.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config/config';\n\n/** Doom's own repository configuration directory. */\nconst DOOM_DIRECTORY = '.doom';\n/** Git's root marker, which may be a directory or a worktree pointer file. */\nconst GIT_DIRECTORY = '.git';\n/** A Pi project that has been configured, rather than any stray `.pi` folder. */\nconst PI_SETTINGS = path.join('.pi', 'settings.json');\n\nfunction isDirectory(target: string): boolean {\n try {\n return fs.statSync(target).isDirectory();\n } catch {\n return false;\n }\n}\n\nfunction isFile(target: string): boolean {\n try {\n return fs.statSync(target).isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * True when a directory carries a marker Doom Pi is willing to treat as a root.\n *\n * Doom and Pi markers identify configured projects. A Git marker also counts so\n * personal configuration can be synchronized into a new checkout before it has\n * repository-local configuration. Build-tooling markers remain deliberately\n * excluded because they do not establish a repository boundary.\n */\nexport function isRepositoryRoot(directory: string): boolean {\n const gitMarker = path.join(directory, GIT_DIRECTORY);\n return (\n isDirectory(path.join(directory, DOOM_DIRECTORY)) ||\n isFile(path.join(directory, PI_SETTINGS)) ||\n isDirectory(gitMarker) ||\n isFile(gitMarker)\n );\n}\n\n/**\n * Walks up from `start` looking for the repository root.\n *\n * The nearest marked ancestor wins, so a session started in a subdirectory\n * resolves to the repository that configured Doom rather than to a parent\n * checkout that happens to sit above it.\n */\nexport function findRepositoryRoot(start: string): string {\n let directory = path.resolve(start);\n while (true) {\n if (isRepositoryRoot(directory)) return directory;\n const parent = path.dirname(directory);\n if (parent === directory) throw new Error(`Could not find repository root from ${start}`);\n directory = parent;\n }\n}\n\n/** Uses the nearest repository when one exists, otherwise the user's global Doom composition. */\nexport function resolveDoomConfigurationRoot(start: string, homeDirectory: string): string {\n try {\n return findRepositoryRoot(start);\n } catch {\n return globalDoomConfigDirectory(homeDirectory);\n }\n}\n"],"mappings":"kMAKA,MAIM,EAAcA,EAAAA,QAAK,KAAK,MAAO,eAAe,EAEpD,SAAS,EAAY,EAAyB,CAC5C,GAAI,CACF,OAAOC,EAAAA,QAAG,SAAS,CAAM,CAAC,CAAC,YAAY,CACzC,MAAQ,CACN,MAAO,EACT,CACF,CAEA,SAAS,EAAO,EAAyB,CACvC,GAAI,CACF,OAAOA,EAAAA,QAAG,SAAS,CAAM,CAAC,CAAC,OAAO,CACpC,MAAQ,CACN,MAAO,EACT,CACF,CAUA,SAAgB,EAAiB,EAA4B,CAC3D,IAAM,EAAYD,EAAAA,QAAK,KAAK,EAAW,MAAa,EACpD,OACE,EAAYA,EAAAA,QAAK,KAAK,EAAW,OAAc,CAAC,GAChD,EAAOA,EAAAA,QAAK,KAAK,EAAW,CAAW,CAAC,GACxC,EAAY,CAAS,GACrB,EAAO,CAAS,CAEpB,CASA,SAAgB,EAAmB,EAAuB,CACxD,IAAI,EAAYA,EAAAA,QAAK,QAAQ,CAAK,EAClC,OAAa,CACX,GAAI,EAAiB,CAAS,EAAG,OAAO,EACxC,IAAM,EAASA,EAAAA,QAAK,QAAQ,CAAS,EACrC,GAAI,IAAW,EAAW,MAAU,MAAM,uCAAuC,GAAO,EACxF,EAAY,CACd,CACF,CAGA,SAAgB,EAA6B,EAAe,EAA+B,CACzF,GAAI,CACF,OAAO,EAAmB,CAAK,CACjC,MAAQ,CACN,OAAA,EAAOE,EAAAA,0BAAAA,CAA0B,CAAa,CAChD,CACF"}
|