@prodero/curator 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/ARCHITECTURE.md +298 -0
  2. package/CHANGELOG.md +33 -0
  3. package/CONTRIBUTING.md +9 -0
  4. package/LICENSE +21 -0
  5. package/README.md +93 -2
  6. package/SECURITY.md +7 -0
  7. package/bin/prodero +7 -0
  8. package/dist/cli.js +266 -0
  9. package/dist/context-instructions.js +2 -0
  10. package/dist/contracts.js +51 -0
  11. package/dist/conversation-presentation.js +297 -0
  12. package/dist/core/agent-instructions.js +52 -0
  13. package/dist/core/applicability-label.js +11 -0
  14. package/dist/core/applicability.js +143 -0
  15. package/dist/core/bootstrap.js +129 -0
  16. package/dist/core/consolidation.js +315 -0
  17. package/dist/core/constants.js +21 -0
  18. package/dist/core/context-allocation.js +54 -0
  19. package/dist/core/context-snapshot.js +12 -0
  20. package/dist/core/context.js +489 -0
  21. package/dist/core/curation-clauses.js +68 -0
  22. package/dist/core/curation-evidence.js +98 -0
  23. package/dist/core/curation-graphical.js +117 -0
  24. package/dist/core/curation-question.js +28 -0
  25. package/dist/core/curation-review-presentation.js +4 -0
  26. package/dist/core/curation-review-state.js +62 -0
  27. package/dist/core/curation-review.js +573 -0
  28. package/dist/core/curation-revision.js +106 -0
  29. package/dist/core/curation-semantic-contract.js +242 -0
  30. package/dist/core/curation-semantic-jobs.js +340 -0
  31. package/dist/core/diagnostics.js +17 -0
  32. package/dist/core/format.js +114 -0
  33. package/dist/core/ids.js +6 -0
  34. package/dist/core/imports.js +169 -0
  35. package/dist/core/index.js +93 -0
  36. package/dist/core/instruction-review.js +86 -0
  37. package/dist/core/integrity-files.js +174 -0
  38. package/dist/core/intelligence.js +95 -0
  39. package/dist/core/knowledge-analysis.js +46 -0
  40. package/dist/core/knowledge-authority.js +143 -0
  41. package/dist/core/knowledge-curation.js +316 -0
  42. package/dist/core/knowledge-engine.js +475 -0
  43. package/dist/core/knowledge-graph.js +192 -0
  44. package/dist/core/knowledge-lifecycle.js +212 -0
  45. package/dist/core/knowledge-management.js +113 -0
  46. package/dist/core/knowledge-migration.js +84 -0
  47. package/dist/core/knowledge-policy.js +38 -0
  48. package/dist/core/knowledge-provenance.js +90 -0
  49. package/dist/core/knowledge-write-policy.js +85 -0
  50. package/dist/core/language.js +14 -0
  51. package/dist/core/local-preferences.js +76 -0
  52. package/dist/core/matching.js +6 -0
  53. package/dist/core/memory-index.js +241 -0
  54. package/dist/core/memory-intelligence.js +288 -0
  55. package/dist/core/memory-link-repair.js +70 -0
  56. package/dist/core/memory-operations.js +122 -0
  57. package/dist/core/memory-pressure.js +212 -0
  58. package/dist/core/memory-sync-curator.js +82 -0
  59. package/dist/core/memory-sync.js +558 -0
  60. package/dist/core/memory-transformation-bundle.js +163 -0
  61. package/dist/core/memory-transformation-proposal.js +135 -0
  62. package/dist/core/memory-transformation.js +108 -0
  63. package/dist/core/memory.js +186 -0
  64. package/dist/core/migrations.js +569 -0
  65. package/dist/core/npm-update.js +289 -0
  66. package/dist/core/path-scope.js +27 -0
  67. package/dist/core/plugin-files.js +116 -0
  68. package/dist/core/project-root.js +39 -0
  69. package/dist/core/project.js +634 -0
  70. package/dist/core/promotion.js +279 -0
  71. package/dist/core/reconciliation.js +61 -0
  72. package/dist/core/secrets.js +67 -0
  73. package/dist/core/semantic-safety.js +33 -0
  74. package/dist/core/session-index.js +116 -0
  75. package/dist/core/session-review.js +150 -0
  76. package/dist/core/sessions.js +289 -0
  77. package/dist/core/settings.js +94 -0
  78. package/dist/core/skill-curation.js +239 -0
  79. package/dist/core/skill-polish.js +41 -0
  80. package/dist/core/skills.js +387 -0
  81. package/dist/core/storage.js +462 -0
  82. package/dist/core/sync-transaction.js +290 -0
  83. package/dist/core/telemetry.js +64 -0
  84. package/dist/core/templates.js +53 -0
  85. package/dist/core/types.js +2 -0
  86. package/dist/core/update.js +326 -0
  87. package/dist/curation-review-adapter.js +92 -0
  88. package/dist/diagnostic-boundary.js +55 -0
  89. package/dist/filesystem/permissions.js +39 -0
  90. package/dist/hosts/adapters.js +119 -0
  91. package/dist/hosts/files.js +133 -0
  92. package/dist/hosts/manager.js +150 -0
  93. package/dist/hosts/public-entry.js +47 -0
  94. package/dist/hosts/runtime-command.js +66 -0
  95. package/dist/hosts/runtime.js +139 -0
  96. package/dist/hosts/wizard.js +114 -0
  97. package/dist/knowledge-table.js +38 -0
  98. package/dist/mcp-development.js +69 -0
  99. package/dist/mcp-workspace.js +141 -0
  100. package/dist/memory-presentation.js +182 -0
  101. package/dist/memory-sync-operation.js +88 -0
  102. package/dist/plugins/api.js +2 -0
  103. package/dist/plugins/context.js +20 -0
  104. package/dist/plugins/mcp.js +72 -0
  105. package/dist/plugins/registry.js +106 -0
  106. package/dist/presentation-cards.js +32 -0
  107. package/dist/presentation-server.js +94 -0
  108. package/dist/presentation-trace.js +174 -0
  109. package/dist/presentation.js +443 -0
  110. package/dist/providers/mcp-sampling.js +45 -0
  111. package/dist/server.js +664 -0
  112. package/dist/settings-definition.js +22 -0
  113. package/dist/skill-instructions.js +2 -0
  114. package/dist/status-presentation.js +20 -0
  115. package/dist/text-layout.js +278 -0
  116. package/dist/update-command.js +53 -0
  117. package/dist/update-instructions.js +4 -0
  118. package/dist-ui/index.html +216 -0
  119. package/docs/APPLICABILITY.md +151 -0
  120. package/docs/COMPATIBILITY.md +68 -0
  121. package/docs/CONTEXT-SELECTION-V2.md +61 -0
  122. package/docs/CURATION-APP-REVIEW.md +156 -0
  123. package/docs/CURATION-CLAUSE-REFINEMENT.md +45 -0
  124. package/docs/IMPORTER-CURATION.md +142 -0
  125. package/docs/KNOWLEDGE-AUTHORITY.md +165 -0
  126. package/docs/KNOWLEDGE-SCHEMA-V2.md +150 -0
  127. package/docs/MIGRATIONS.md +103 -0
  128. package/docs/PRIVACY.md +28 -0
  129. package/docs/RECOVERY.md +58 -0
  130. package/docs/RELEASING.md +98 -0
  131. package/docs/hosts.md +152 -0
  132. package/docs/images/prodero-context-ready-cropped.png +0 -0
  133. package/docs/images/prodero-overview-cropped.png +0 -0
  134. package/docs/images/prodero-session-learned-cropped.png +0 -0
  135. package/docs/plugins.md +83 -0
  136. package/docs/ui-themes.md +49 -0
  137. package/instructions/AGENTS.md +39 -0
  138. package/package.json +92 -4
  139. package/plugins/migrate_ai_studio/README.md +101 -0
  140. package/plugins/migrate_ai_studio/index.cjs +396 -0
  141. package/plugins/migrate_ai_studio/instructions/audit.md +25 -0
  142. package/plugins/migrate_ai_studio/instructions/classify.md +41 -0
  143. package/plugins/migrate_ai_studio/instructions/migrate.md +34 -0
  144. package/plugins/migrate_ai_studio/instructions/verify.md +20 -0
  145. package/plugins/migrate_ai_studio/plugin.json +11 -0
  146. package/plugins/migrate_ai_studio/schema.cjs +20 -0
  147. package/plugins/migrate_ai_studio/ui/dist/index.html +77 -0
  148. package/prodero.release.json +5 -0
@@ -0,0 +1,326 @@
1
+ import { publicDiagnostic } from "./diagnostics.js";
2
+ import crypto from "node:crypto";
3
+ import { spawn } from "node:child_process";
4
+ import { lstatSync, readFileSync, readdirSync } from "node:fs";
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { z } from "zod";
8
+ import { PRODERO_SCHEMA_VERSION, PRODERO_VERSION } from "./constants.js";
9
+ import { writeFileAtomic } from "./storage.js";
10
+ import { MANUAL_UPDATE_COMMAND, UPDATE_INSTRUCTIONS } from '../update-instructions.js';
11
+ export { MANUAL_UPDATE_COMMAND } from '../update-instructions.js';
12
+ const semverPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
13
+ const versionSchema = z.string().regex(semverPattern, "Expected a valid semantic version");
14
+ export const releaseMetadataSchema = z.object({
15
+ version: versionSchema,
16
+ schemaVersion: z.number().int().positive(),
17
+ releaseId: z.string().regex(/^[A-Za-z0-9._-]+$/).max(200),
18
+ publishedAt: z.string().datetime().optional(),
19
+ notes: z.string().max(4_000).optional(),
20
+ artifact: z.object({ locator: z.string().trim().min(1), integrity: z.string().regex(/^sha256:[a-f0-9]{64}$/) }).strict(),
21
+ }).strict();
22
+ /** MCP/App instructions are inert for every release source, including dev adapters. */
23
+ export function updateInstructions(source = { id: 'npm' }, installedVersion = PRODERO_VERSION) {
24
+ return { ok: true, action: 'update-instructions', instructionOnly: true, status: 'manual-update',
25
+ installedVersion, availableVersion: null, source: source.id, restartRequired: false, command: MANUAL_UPDATE_COMMAND, message: UPDATE_INSTRUCTIONS };
26
+ }
27
+ export class ReleaseSourceUnavailableError extends Error {
28
+ }
29
+ function parseVersion(value) {
30
+ const match = semverPattern.exec(value);
31
+ if (!match)
32
+ throw new Error(`Invalid semantic version: ${value}`);
33
+ return { core: [Number(match[1]), Number(match[2]), Number(match[3])], prerelease: match[4]?.split(".") ?? [] };
34
+ }
35
+ /** Compare strict SemVer values. Build metadata does not affect precedence. */
36
+ export function compareVersions(left, right) {
37
+ const a = parseVersion(left), b = parseVersion(right);
38
+ for (let index = 0; index < 3; index += 1)
39
+ if (a.core[index] !== b.core[index])
40
+ return a.core[index] > b.core[index] ? 1 : -1;
41
+ if (!a.prerelease.length || !b.prerelease.length)
42
+ return a.prerelease.length === b.prerelease.length ? 0 : a.prerelease.length ? -1 : 1;
43
+ for (let index = 0; index < Math.max(a.prerelease.length, b.prerelease.length); index += 1) {
44
+ const av = a.prerelease[index], bv = b.prerelease[index];
45
+ if (av === undefined || bv === undefined)
46
+ return av === bv ? 0 : av === undefined ? -1 : 1;
47
+ if (av === bv)
48
+ continue;
49
+ const an = /^\d+$/.test(av), bn = /^\d+$/.test(bv);
50
+ if (an && bn)
51
+ return Number(av) > Number(bv) ? 1 : -1;
52
+ if (an !== bn)
53
+ return an ? -1 : 1;
54
+ return av > bv ? 1 : -1;
55
+ }
56
+ return 0;
57
+ }
58
+ export class UnconfiguredReleaseSource {
59
+ id = "unconfigured";
60
+ async latest() { return null; }
61
+ async stage() { throw new Error("No Prodero release source is configured."); }
62
+ }
63
+ const LOCAL_RELEASE_FILES = ["package.json", "package-lock.json", "prodero.release.json", "tsconfig.json", "vite.config.ts", "src", "scripts", "bin", "plugins"];
64
+ const MAX_LOCAL_RELEASE_FILES = 5_000;
65
+ const MAX_LOCAL_RELEASE_BYTES = 64 * 1024 * 1024;
66
+ function localCheckoutDigest(root) {
67
+ const digest = crypto.createHash("sha256");
68
+ let files = 0, bytes = 0;
69
+ const visit = (relative) => {
70
+ const absolute = path.join(root, relative);
71
+ let stat;
72
+ try {
73
+ stat = lstatSync(absolute);
74
+ }
75
+ catch (error) {
76
+ if (error.code === "ENOENT")
77
+ return;
78
+ throw error;
79
+ }
80
+ if (stat.isSymbolicLink())
81
+ throw new Error(`Local release source contains a symlink: ${relative}`);
82
+ if (stat.isDirectory()) {
83
+ for (const name of readdirSync(absolute).sort())
84
+ visit(path.join(relative, name));
85
+ return;
86
+ }
87
+ if (!stat.isFile())
88
+ return;
89
+ files += 1;
90
+ bytes += stat.size;
91
+ if (files > MAX_LOCAL_RELEASE_FILES || bytes > MAX_LOCAL_RELEASE_BYTES)
92
+ throw new Error("Local release source exceeds the bounded release snapshot.");
93
+ digest.update(`F\0${relative.split(path.sep).join("/")}\0${stat.size}\0`);
94
+ digest.update(readFileSync(absolute));
95
+ digest.update("\0");
96
+ };
97
+ for (const relative of LOCAL_RELEASE_FILES)
98
+ visit(relative);
99
+ return digest.digest("hex");
100
+ }
101
+ async function runLocalBuild(root, signal) {
102
+ await new Promise((resolve, reject) => {
103
+ const child = spawn(process.platform === "win32" ? "npm.cmd" : "npm", ["run", "build"], { cwd: root, stdio: ["ignore", "pipe", "pipe"] });
104
+ let output = "";
105
+ const append = (chunk) => { output = `${output}${chunk.toString("utf8")}`.slice(-8_000); };
106
+ child.stdout.on("data", append);
107
+ child.stderr.on("data", append);
108
+ const abort = () => child.kill("SIGTERM");
109
+ signal?.addEventListener("abort", abort, { once: true });
110
+ child.once("error", reject);
111
+ child.once("exit", (code, terminationSignal) => {
112
+ signal?.removeEventListener("abort", abort);
113
+ if (signal?.aborted)
114
+ reject(new InterruptedUpdateError("Local release build was interrupted."));
115
+ else if (code === 0)
116
+ resolve();
117
+ else
118
+ reject(new Error(`Local release build failed with ${terminationSignal ? `signal ${terminationSignal}` : `exit code ${code ?? "unknown"}`}.${output.trim() ? `\n${output.trim()}` : ""}`));
119
+ });
120
+ });
121
+ }
122
+ /** Legacy internal development build adapter. Never invoked by MCP update instructions. */
123
+ export class LocalCheckoutReleaseSource {
124
+ root;
125
+ id = "local-checkout";
126
+ installedReleaseId;
127
+ constructor(root) {
128
+ this.root = root;
129
+ this.installedReleaseId = `local-${localCheckoutDigest(root)}`;
130
+ }
131
+ async latest() {
132
+ const identity = JSON.parse(await fs.readFile(path.join(this.root, "package.json"), "utf8"));
133
+ const releaseIdentity = z.object({ formatVersion: z.literal(1), schemaVersion: z.number().int().positive() }).strict().parse(JSON.parse(await fs.readFile(path.join(this.root, "prodero.release.json"), "utf8")));
134
+ const version = versionSchema.parse(identity.version);
135
+ const hash = localCheckoutDigest(this.root);
136
+ return { version, schemaVersion: releaseIdentity.schemaVersion, releaseId: `local-${hash}`, artifact: { locator: ".", integrity: `sha256:${hash}` } };
137
+ }
138
+ async stage(release, signal) {
139
+ const before = await this.latest();
140
+ if (before.releaseId !== release.releaseId || before.artifact.integrity !== release.artifact.integrity)
141
+ throw new InterruptedUpdateError("Local release source changed after update review; check again.");
142
+ await runLocalBuild(this.root, signal);
143
+ const after = await this.latest();
144
+ if (after.releaseId !== release.releaseId || after.artifact.integrity !== release.artifact.integrity)
145
+ throw new InterruptedUpdateError("Local release source changed while it was being built; no activation was recorded.");
146
+ return { restartRequired: true, detail: `Local checkout ${this.root} built and verified successfully.` };
147
+ }
148
+ }
149
+ const localManifestSchema = z.object({
150
+ formatVersion: z.literal(1), release: z.unknown().nullable(),
151
+ simulation: z.object({ outcome: z.enum(["success", "failed", "interrupted"]), message: z.string().optional() }).strict().optional(),
152
+ }).strict();
153
+ class InterruptedUpdateError extends Error {
154
+ }
155
+ /** Deterministic source used for local development and contract tests. */
156
+ export class LocalManifestReleaseSource {
157
+ manifestPath;
158
+ stateDirectory;
159
+ id = "local-manifest";
160
+ constructor(manifestPath, stateDirectory = path.join(path.dirname(manifestPath), ".prodero-update-state")) {
161
+ this.manifestPath = manifestPath;
162
+ this.stateDirectory = stateDirectory;
163
+ }
164
+ async manifest() { return localManifestSchema.parse(JSON.parse(await fs.readFile(this.manifestPath, "utf8"))); }
165
+ async latest() { return (await this.manifest()).release; }
166
+ async stage(release, signal) {
167
+ signal?.throwIfAborted();
168
+ const manifest = await this.manifest();
169
+ const simulation = manifest.simulation?.outcome ?? "success";
170
+ if (simulation === "failed")
171
+ throw new Error(manifest.simulation?.message ?? "Local release installation failed.");
172
+ if (simulation === "interrupted")
173
+ throw new InterruptedUpdateError(manifest.simulation?.message ?? "Local release installation was interrupted.");
174
+ const manifestDirectory = path.dirname(this.manifestPath);
175
+ const artifactPath = path.resolve(manifestDirectory, release.artifact.locator);
176
+ if (path.isAbsolute(release.artifact.locator) || (artifactPath !== manifestDirectory && !artifactPath.startsWith(`${manifestDirectory}${path.sep}`)))
177
+ throw new Error("Release artifact must stay inside the local manifest directory.");
178
+ const artifact = await fs.readFile(artifactPath);
179
+ const integrity = `sha256:${crypto.createHash("sha256").update(artifact).digest("hex")}`;
180
+ if (integrity !== release.artifact.integrity)
181
+ throw new Error("Release artifact integrity verification failed.");
182
+ await fs.mkdir(this.stateDirectory, { recursive: true });
183
+ const stagedPath = path.join(this.stateDirectory, `${release.releaseId}.artifact`);
184
+ const temporary = `${stagedPath}.partial-${process.pid}`;
185
+ try {
186
+ await fs.writeFile(temporary, artifact, { flag: "wx" });
187
+ signal?.throwIfAborted();
188
+ await fs.rename(temporary, stagedPath);
189
+ await writeFileAtomic(path.join(this.stateDirectory, "pending.json"), `${JSON.stringify({ formatVersion: 1, release, stagedPath, stagedAt: new Date().toISOString() }, null, 2)}\n`);
190
+ }
191
+ catch (error) {
192
+ await fs.rm(temporary, { force: true }).catch(() => { });
193
+ throw error;
194
+ }
195
+ return { restartRequired: true, detail: `Release ${release.version} was verified and staged at ${stagedPath}.` };
196
+ }
197
+ }
198
+ /** npm is read-only here: package installation always belongs to the user's package manager. */
199
+ export class NpmReleaseSource {
200
+ request;
201
+ id = "npm";
202
+ constructor(request = fetch) {
203
+ this.request = request;
204
+ }
205
+ async latest(signal) {
206
+ try {
207
+ const response = await this.request("https://registry.npmjs.org/@prodero%2fcurator/latest", { signal, redirect: "error" });
208
+ if (response.status === 404)
209
+ return null;
210
+ if (!response.ok)
211
+ throw new Error("Registry unavailable");
212
+ return await response.json();
213
+ }
214
+ catch {
215
+ throw new ReleaseSourceUnavailableError("Cannot reach the public npm release source. Installed knowledge is unaffected.");
216
+ }
217
+ }
218
+ async stage() { throw new Error(`Use ${MANUAL_UPDATE_COMMAND}`); }
219
+ }
220
+ export function configuredReleaseSource(environment = process.env) {
221
+ if (environment.PRODERO_RELEASE_SOURCE === "none")
222
+ return new UnconfiguredReleaseSource();
223
+ if (environment.PRODERO_DEV_UPDATE === "1") {
224
+ const manifest = environment.PRODERO_RELEASE_MANIFEST?.trim();
225
+ if (manifest)
226
+ return new LocalManifestReleaseSource(path.resolve(manifest), environment.PRODERO_UPDATE_STATE_DIR?.trim());
227
+ const checkout = environment.PRODERO_LOCAL_RELEASE_CHECKOUT?.trim();
228
+ if (checkout)
229
+ return new LocalCheckoutReleaseSource(path.resolve(checkout));
230
+ return new UnconfiguredReleaseSource();
231
+ }
232
+ return new NpmReleaseSource();
233
+ }
234
+ function updateAvailable(source, release, installedVersion) {
235
+ const precedence = compareVersions(release.version, installedVersion);
236
+ return precedence > 0 || (precedence === 0 && Boolean(source.installedReleaseId) && source.installedReleaseId !== release.releaseId);
237
+ }
238
+ async function checkReleaseUpdate(source = configuredReleaseSource(), installedVersion = PRODERO_VERSION) {
239
+ let raw;
240
+ try {
241
+ versionSchema.parse(installedVersion);
242
+ raw = await source.latest(AbortSignal.timeout(2_500));
243
+ }
244
+ catch (error) {
245
+ const status = error instanceof ReleaseSourceUnavailableError || (error instanceof DOMException && ["AbortError", "TimeoutError"].includes(error.name)) ? "unavailable" : "invalid";
246
+ return { current: installedVersion, latest: null, available: false, source: source.id, status, installedSchemaVersion: PRODERO_SCHEMA_VERSION, availableSchemaVersion: null, error: publicDiagnostic(error) };
247
+ }
248
+ if (raw === null)
249
+ return { current: installedVersion, latest: null, available: false, source: source.id, status: "unavailable", installedSchemaVersion: PRODERO_SCHEMA_VERSION, availableSchemaVersion: null };
250
+ try {
251
+ const release = releaseMetadataSchema.parse(raw);
252
+ if (release.schemaVersion < PRODERO_SCHEMA_VERSION)
253
+ throw new Error(`Release schema v${release.schemaVersion} is older than installed schema v${PRODERO_SCHEMA_VERSION}.`);
254
+ const available = updateAvailable(source, release, installedVersion);
255
+ return { current: installedVersion, latest: release.version, available, source: source.id, status: available ? "update-available" : "current", installedSchemaVersion: PRODERO_SCHEMA_VERSION, availableSchemaVersion: release.schemaVersion };
256
+ }
257
+ catch (error) {
258
+ return { current: installedVersion, latest: null, available: false, source: source.id, status: "invalid", installedSchemaVersion: PRODERO_SCHEMA_VERSION, availableSchemaVersion: null, error: publicDiagnostic(error) };
259
+ }
260
+ }
261
+ export async function checkForUpdate(source = configuredReleaseSource(), installedVersion = PRODERO_VERSION) {
262
+ if (!(source instanceof NpmReleaseSource)) {
263
+ const result = await checkReleaseUpdate(source, installedVersion);
264
+ return { ...result, manualUpdateCommand: MANUAL_UPDATE_COMMAND, developmentOnly: source.id !== "unconfigured",
265
+ ...(result.status === "unavailable" && !result.error ? { error: "No production release source is configured or no release is available." } : {}) };
266
+ }
267
+ const base = { current: installedVersion, latest: null, available: false, source: source.id, installedSchemaVersion: PRODERO_SCHEMA_VERSION, availableSchemaVersion: null, manualUpdateCommand: MANUAL_UPDATE_COMMAND, developmentOnly: false };
268
+ try {
269
+ const raw = await source.latest(AbortSignal.timeout(2_500));
270
+ if (raw === null)
271
+ return { ...base, status: "unavailable", error: "No public npm release is available yet." };
272
+ const { version } = z.object({ version: versionSchema }).parse(raw);
273
+ const available = compareVersions(version, installedVersion) > 0;
274
+ return { ...base, latest: version, available, status: available ? "update-available" : "current" };
275
+ }
276
+ catch (error) {
277
+ return { ...base, status: error instanceof ReleaseSourceUnavailableError ? "unavailable" : "invalid", error: error instanceof ReleaseSourceUnavailableError ? error.message : "Invalid version metadata from the npm release source." };
278
+ }
279
+ }
280
+ export async function installLatest(source = configuredReleaseSource(), installedVersion = PRODERO_VERSION) {
281
+ if (source instanceof NpmReleaseSource || source instanceof UnconfiguredReleaseSource) {
282
+ const check = await checkForUpdate(source, installedVersion);
283
+ return { ok: true, status: "manual-update", installedVersion, availableVersion: check.latest, source: source.id, restartRequired: false,
284
+ message: `${check.error ? `${check.error} ` : ""}${UPDATE_INSTRUCTIONS}` };
285
+ }
286
+ let raw;
287
+ try {
288
+ raw = await source.latest(AbortSignal.timeout(2_500));
289
+ }
290
+ catch (error) {
291
+ if (error instanceof ReleaseSourceUnavailableError || (error instanceof DOMException && ["AbortError", "TimeoutError"].includes(error.name)))
292
+ return unavailable(source, installedVersion, error);
293
+ return failure("invalid", source, installedVersion, null, error);
294
+ }
295
+ if (raw === null)
296
+ return { ok: false, status: "unavailable", installedVersion, availableVersion: null, source: source.id, restartRequired: false, message: "No Prodero release source is configured or no release is available." };
297
+ let release;
298
+ try {
299
+ release = releaseMetadataSchema.parse(raw);
300
+ compareVersions(release.version, installedVersion);
301
+ if (release.schemaVersion < PRODERO_SCHEMA_VERSION)
302
+ throw new Error(`Release schema v${release.schemaVersion} is older than installed schema v${PRODERO_SCHEMA_VERSION}.`);
303
+ }
304
+ catch (error) {
305
+ return failure("invalid", source, installedVersion, null, error);
306
+ }
307
+ if (!updateAvailable(source, release, installedVersion))
308
+ return { ok: true, status: "current", installedVersion, availableVersion: release.version, source: source.id, restartRequired: false, message: `Prodero ${installedVersion} is already current.` };
309
+ try {
310
+ const staged = await source.stage(release, AbortSignal.timeout(30_000));
311
+ return { ok: true, status: "restart-required", installedVersion, availableVersion: release.version, source: source.id, restartRequired: staged.restartRequired, message: `${staged.detail ?? `Prodero ${release.version} was staged.`} Development-only staging. Restart the Prodero MCP runtime after a checkout rebuild; manifest simulation has no production activation consumer.` };
312
+ }
313
+ catch (error) {
314
+ const interrupted = error instanceof InterruptedUpdateError || (error instanceof DOMException && ["AbortError", "TimeoutError"].includes(error.name));
315
+ return failure(interrupted ? "interrupted" : "failed", source, installedVersion, release.version, error);
316
+ }
317
+ }
318
+ function unavailable(source, installedVersion, error) {
319
+ const detail = publicDiagnostic(error);
320
+ return { ok: false, status: "unavailable", installedVersion, availableVersion: null, source: source.id, restartRequired: false, message: `Prodero release source is unavailable: ${detail}` };
321
+ }
322
+ function failure(status, source, installedVersion, availableVersion, error) {
323
+ const detail = publicDiagnostic(error);
324
+ return { ok: false, status, installedVersion, availableVersion, source: source.id, restartRequired: false, message: `Prodero update ${status}: ${detail}` };
325
+ }
326
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1,92 @@
1
+ /** App review is a workflow safeguard, not authentication of human consent.
2
+ * The adapter validates the saved review before issuing an exact Core grant;
3
+ * MCP Apps visibility/capability does not authenticate a physical user action.
4
+ * Future interactive text confirmation can call this same executor after its own
5
+ * protocol response; the public text gateway must never dispatch here directly. */
6
+ import { assertSafeCurationData } from './core/curation-evidence.js';
7
+ import { withSyncLock, recoverSyncTransaction } from './core/sync-transaction.js';
8
+ import { listCheckpoints } from './core/storage.js';
9
+ import { buildIndex } from './core/index.js';
10
+ import { planKnowledgeCuration, curationReviewEntries, applyKnowledgeCuration, StaleCurationPlanError } from './core/knowledge-curation.js';
11
+ import { withTrustedKnowledgeBatch } from './core/knowledge-authority.js';
12
+ import { curationReceiptProjection, curationReviewView, assertCurationHead, ensureCurationSuccessor, validateRevisionReceipt, reserveSuccessorCapacity, loadCurationReview, selectedCurationProposals, curationOperationId, CurationReviewError } from './core/curation-review.js';
13
+ import { validateRevision, deterministicBaseline, curationSelectionHash, selectionProblems, revisionHash } from './core/curation-revision.js';
14
+ import { semanticReviewStatus } from './core/curation-semantic-jobs.js';
15
+ import path from 'node:path';
16
+ import fs from 'node:fs/promises';
17
+ export async function executeCurationReview(root, input) {
18
+ const selected = [...input.selectedIds].sort();
19
+ if (!/^review_[a-zA-Z0-9_]+$/.test(input.reviewId) || !selected.length || new Set(selected).size !== selected.length)
20
+ throw new CurationReviewError('invalid_selection', 'Select a valid, non-empty set of suggestions.');
21
+ const operationId = curationOperationId(input.reviewId, selected, input.reviewRevisionId, input.selectionHash);
22
+ return withSyncLock(root, async () => {
23
+ await recoverSyncTransaction(root);
24
+ const history = await listCheckpoints(root);
25
+ const prior = history.find(c => c.curation?.review?.operationId === operationId);
26
+ const result = (receipt, checkpointId, replayed) => ({ status: 'applied', ...receipt, checkpointId, appliedGroupCount: receipt.reviewSnapshot?.units.filter(u => u.proposalIds.every(id => receipt.applied.includes(id))).length ?? receipt.applied.length, historyPath: path.join(path.resolve(root), '.prodero', 'checkpoints', `${checkpointId}.json`), replayed });
27
+ if (prior) {
28
+ const stored = prior.curation;
29
+ assertSafeCurationData(stored);
30
+ if (stored.plan.project !== path.resolve(root) || stored.review.planHash !== input.planHash || stored.review.projectIdentity !== await fs.realpath(root))
31
+ throw new CurationReviewError('review_changed', 'This request does not match the completed review.');
32
+ if (stored.review.reviewRevisionId) {
33
+ validateRevisionReceipt(stored.review);
34
+ if (input.reviewRevisionId !== stored.review.reviewRevisionId || input.decisionRevision !== stored.review.decisionRevision || input.selectionHash !== stored.review.selectionHash || JSON.stringify(selected) !== JSON.stringify(stored.review.selectedIds))
35
+ throw new CurationReviewError('review_changed', 'This request does not match the completed revision.');
36
+ }
37
+ await buildIndex(root);
38
+ const successor = await ensureCurationSuccessor(root, stored.review);
39
+ return { ...result(stored.review, prior.id, true), successor, projection: successor ? (await curationReviewView(root, successor.snapshot.plan, successor)).projection : curationReceiptProjection(stored.plan, stored.review) };
40
+ }
41
+ const review = await loadCurationReview(root, input.reviewId);
42
+ if (review.snapshot.plan.memorySyncSources)
43
+ throw new CurationReviewError('invalid_selection', 'Sync analysis is read-only; continue its owning Curate Memory operation.');
44
+ if (review.snapshot.planHash !== input.planHash)
45
+ throw new CurationReviewError('stale_review', 'Knowledge changed since this review.');
46
+ if (review.state.revision !== input.decisionRevision)
47
+ throw new CurationReviewError('review_changed', 'Your review changed in another window. Reload your decisions.');
48
+ const revision = review.snapshot.revision;
49
+ if (revision) {
50
+ validateRevision(revision);
51
+ if (input.reviewRevisionId !== revision.id)
52
+ throw new CurationReviewError('stale_review', 'Apply requires the exact review revision.');
53
+ if (input.selectionHash !== curationSelectionHash(revision.id, input.decisionRevision, selected))
54
+ throw new CurationReviewError('invalid_selection', 'Apply selection hash changed.');
55
+ if (history.some(c => c.curation?.review?.reviewRevisionId === revision.id))
56
+ throw new CurationReviewError('stale_review', 'This revision was applied. Review its successor.');
57
+ }
58
+ else if (input.reviewRevisionId || input.selectionHash)
59
+ throw new CurationReviewError('invalid_review', 'Legacy review has no revision binding.');
60
+ if (revision)
61
+ await assertCurationHead(root, review);
62
+ const derived = selectedCurationProposals(review);
63
+ if (revision ? selected.some(id => !derived.includes(id)) : JSON.stringify(derived) !== JSON.stringify(selected))
64
+ throw new CurationReviewError('invalid_selection', 'Apply must match your saved review decisions.');
65
+ if (revision) {
66
+ const problems = selectionProblems(revision.dependencies, selected);
67
+ if (problems.length)
68
+ throw new CurationReviewError('dependency_blocked', 'Selected changes require unresolved information or additional reviewed groups.', { problems });
69
+ }
70
+ if (revision) {
71
+ const semantic = await semanticReviewStatus(root, review);
72
+ if (revision.dependencies.groups.some(g => semantic.blockedGroupIds.includes(g.id) && g.proposalIds.some(id => selected.includes(id))))
73
+ throw new CurationReviewError('dependency_blocked', 'Selected changes await semantic analysis or updated answers.');
74
+ }
75
+ // Validate selection, including dependency closure, independently of the UI.
76
+ const entries = curationReviewEntries(review.snapshot.plan, selected, revision);
77
+ const current = await planKnowledgeCuration(root).catch(() => { throw new CurationReviewError('stale_review', 'Knowledge can no longer be validated against this review. Review updated changes.'); });
78
+ if (current.planId !== (revision ? deterministicBaseline(revision).planId : review.snapshot.planHash) || current.fingerprint !== review.snapshot.plan.fingerprint)
79
+ throw new CurationReviewError('stale_review', 'Knowledge changed since this review.');
80
+ if (revision)
81
+ await reserveSuccessorCapacity(root);
82
+ const receipt = { ...(revision ? { reviewRevisionId: revision.id, selectionHash: input.selectionHash, reviewSnapshot: review.snapshot, reviewState: review.state, executionHash: revisionHash([review.snapshot.snapshotHash, review.state, selected]) } : {}), projectIdentity: review.snapshot.projectIdentity, decisions: review.state.decisions, reviewId: input.reviewId, planHash: input.planHash, operationId, decisionRevision: input.decisionRevision, selectedIds: selected, applied: selected, unselected: review.snapshot.plan.proposals.filter(p => !selected.includes(p.id) && !p.attention.length).map(p => p.id), unresolved: review.snapshot.plan.proposals.filter(p => p.attention.length).map(p => p.id) };
83
+ const applied = await withTrustedKnowledgeBatch(root, entries, `app-review:${operationId}`, () => applyKnowledgeCuration(root, review.snapshot.plan, selected, receipt)).catch(error => {
84
+ if (error instanceof StaleCurationPlanError)
85
+ throw new CurationReviewError('stale_review', 'Knowledge changed since this review.');
86
+ throw error;
87
+ });
88
+ const successor = await ensureCurationSuccessor(root, receipt);
89
+ return { ...result(receipt, applied.checkpoint, false), successor, projection: successor ? (await curationReviewView(root, successor.snapshot.plan, successor)).projection : curationReceiptProjection(review.snapshot.plan, receipt) };
90
+ }, { waitMs: 2000 });
91
+ }
92
+ //# sourceMappingURL=curation-review-adapter.js.map
@@ -0,0 +1,55 @@
1
+ import { renderTextError } from './text-layout.js';
2
+ import { publicDiagnostic } from "./core/diagnostics.js";
3
+ // Preserve only known protocol states, with fixed public prose. Never forward
4
+ // arbitrary plugin error payloads or client-supplied diagnostics.
5
+ const reviewMessages = {
6
+ semantic_invalid: 'Semantic reasoning input or saved state is invalid.',
7
+ semantic_limit: 'Semantic reasoning reached its bounded limit. Saved state was preserved.',
8
+ semantic_stale: 'Semantic reasoning input is obsolete. Inspect the current review.',
9
+ dependency_blocked: 'Selected changes require unresolved information or additional reviewed groups.',
10
+ unsupported_review_schema: 'This review uses a newer format. Update Prodero.',
11
+ review_retention_full: 'Saved review decisions reached the retention limit. No decisions were deleted.',
12
+ review_paused: 'Resume this review before applying changes.',
13
+ stale_review: 'Knowledge changed since this review.',
14
+ invalid_review: 'Review is unavailable. Analyze knowledge again.',
15
+ invalid_selection: 'Apply must match valid saved review decisions and complete groups.',
16
+ project_mismatch: 'Review belongs to another project.',
17
+ review_changed: 'Your review changed in another window. Reload your decisions.',
18
+ review_too_large: 'Review is too large. Reduce the review scope first.',
19
+ recovery_required: 'Finish the pending transaction before starting a new review.',
20
+ review_unavailable: 'This connection does not advertise MCP Apps support. Knowledge review is preview-only.',
21
+ };
22
+ /** Wrap the public request registration API, including SDK validation before tool handlers. */
23
+ export function installDiagnosticBoundary(server) {
24
+ const register = server.server.setRequestHandler.bind(server.server);
25
+ server.server.setRequestHandler = (schema, handler) => register(schema, async (request, extra) => {
26
+ try {
27
+ const result = await handler(request, extra);
28
+ if (result && typeof result === "object" && "isError" in result && result.isError) {
29
+ const raw = result;
30
+ const state = raw.structuredContent;
31
+ if (state?.action === 'curation-review-error' && typeof state.code === 'string' && Object.hasOwn(reviewMessages, state.code)) {
32
+ const message = reviewMessages[state.code];
33
+ return { isError: true, content: [{ type: 'text', text: renderTextError(message) }], structuredContent: { ok: false, action: 'curation-review-error', code: state.code, error: message, message, ...(state.code === 'dependency_blocked' ? { details: safeDependencyDetails(state.details) } : {}) } };
34
+ }
35
+ const message = raw.structuredContent?.error ?? raw.content?.find(item => item.type === "text")?.text;
36
+ const safe = publicDiagnostic(message);
37
+ // Drop arbitrary plugin error payloads too: they may contain rejected input.
38
+ return { isError: true, content: [{ type: "text", text: renderTextError(safe) }], structuredContent: { ok: false, error: safe, message: safe, ...(safe.startsWith("POLICY_REVIEW_REQUIRED:") ? { policyTransition: { decision: "review_required", blocking: true, preserved: true } } : {}) } };
39
+ }
40
+ return result;
41
+ }
42
+ catch (error) {
43
+ if (error && typeof error === 'object' && 'code' in error && typeof error.code === 'string' && Object.hasOwn(reviewMessages, error.code)) {
44
+ const message = reviewMessages[error.code];
45
+ return { isError: true, content: [{ type: 'text', text: renderTextError(message) }], structuredContent: { ok: false, action: 'curation-review-error', code: error.code, error: message, message } };
46
+ }
47
+ throw new Error(publicDiagnostic(error));
48
+ }
49
+ });
50
+ }
51
+ function safeDependencyDetails(value) {
52
+ const problems = value?.problems;
53
+ return { problems: Array.isArray(problems) ? problems.slice(0, 1000).flatMap((raw) => typeof raw?.groupId === 'string' && /^item_[a-f0-9]{24}$/.test(raw.groupId) && ['incomplete', 'blocked', 'requires', 'partial_group'].includes(raw.reason) ? [{ groupId: raw.groupId, reason: raw.reason, missing: Array.isArray(raw.missing) ? raw.missing.filter((v) => typeof v === 'string' && /^(?:item|issue)_[a-f0-9]{24}$/.test(v)) : [] }] : []) : [] };
54
+ }
55
+ //# sourceMappingURL=diagnostic-boundary.js.map
@@ -0,0 +1,39 @@
1
+ import fs from 'node:fs/promises';
2
+ import { constants } from 'node:fs';
3
+ // Shared with host integration state. Windows uses its native ACL/inheritance model.
4
+ export const PRIVATE_DIRECTORY_MODE = 0o700;
5
+ export const PRIVATE_FILE_MODE = 0o600;
6
+ /** Tighten only the explicitly addressed inode, never a linked file or another owner's data. */
7
+ export async function tightenPrivateMode(target) {
8
+ if (process.platform === 'win32')
9
+ return;
10
+ const stat = await fs.lstat(target).catch(error => { if (error.code === 'ENOENT')
11
+ return null; throw error; });
12
+ if (!stat)
13
+ return;
14
+ // A concurrent lock release can unlink an inode while lstat is resolving it.
15
+ // Accept only a vanished, owned regular file; any replacement still fails closed.
16
+ if (stat.isFile() && stat.nlink === 0 && process.getuid && stat.uid === process.getuid()) {
17
+ const remaining = await fs.lstat(target).catch(error => { if (error.code === 'ENOENT')
18
+ return null; throw error; });
19
+ if (!remaining)
20
+ return;
21
+ }
22
+ if (stat.isSymbolicLink() || (!stat.isDirectory() && !stat.isFile()) || (stat.isFile() && stat.nlink !== 1)
23
+ || (process.getuid && stat.uid !== process.getuid()))
24
+ throw new Error('Unsafe private state permissions target');
25
+ const mode = stat.isDirectory() ? PRIVATE_DIRECTORY_MODE : PRIVATE_FILE_MODE;
26
+ if ((stat.mode & 0o7777) === mode)
27
+ return;
28
+ const handle = await fs.open(target, constants.O_RDONLY | constants.O_NOFOLLOW);
29
+ try {
30
+ const current = await handle.stat();
31
+ if (current.dev !== stat.dev || current.ino !== stat.ino || current.nlink !== stat.nlink || current.uid !== stat.uid)
32
+ throw new Error('Private state changed while tightening permissions');
33
+ await handle.chmod(mode);
34
+ }
35
+ finally {
36
+ await handle.close();
37
+ }
38
+ }
39
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1,119 @@
1
+ import { PRESENTATION_LAUNCHER } from "../presentation.js";
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { parse as parseToml } from 'smol-toml';
5
+ import { parseTree, modify, applyEdits } from 'jsonc-parser';
6
+ import { fail } from './files.js';
7
+ export const MCP_START = '# prodero:mcp-start', MCP_END = '# prodero:mcp-end';
8
+ export const GLOBAL_START = '<!-- prodero:global-start -->', GLOBAL_END = '<!-- prodero:global-end -->';
9
+ export const LAUNCHER = `${GLOBAL_START}\n## Prodero launcher\n\n${PRESENTATION_LAUNCHER} The connected server resolves the current workspace from the host. Omit \`projectRoot\` normally; use it only to select among host-authorized roots. New projects initialize through Prodero MCP without shell setup.\n${GLOBAL_END}`;
10
+ export function block(text, start, end) {
11
+ const a = text.indexOf(start), b = text.indexOf(end);
12
+ if (a < 0 && b < 0)
13
+ return null;
14
+ if (a < 0 || b < a || text.indexOf(start, a + start.length) >= 0 || text.indexOf(end, b + end.length) >= 0)
15
+ fail('HOST_CONFLICT', 'Managed boundaries are missing, duplicated or ambiguous. Review them manually.');
16
+ return { text: text.slice(a, b + end.length), start: a, end: b + end.length };
17
+ }
18
+ export function replaceBlock(text, start, end, value) {
19
+ const found = block(text, start, end);
20
+ if (found)
21
+ return text.slice(0, found.start) + (value ?? '') + text.slice(found.end);
22
+ return value === undefined ? text : text + (text && !text.endsWith('\n') ? '\n' : '') + (text ? '\n' : '') + value + '\n';
23
+ }
24
+ export function strictJson(text) {
25
+ if (!text.trim())
26
+ fail('HOST_INVALID_CONFIG', 'Empty JSON host configuration. Restore valid JSON before retrying.');
27
+ const errors = [];
28
+ const tree = parseTree(text, errors, { allowTrailingComma: false, disallowComments: true });
29
+ const check = (node) => { if (node.type === 'object') {
30
+ const keys = node.children?.map(c => c.children[0].value) ?? [];
31
+ if (new Set(keys).size !== keys.length)
32
+ fail('HOST_INVALID_CONFIG', 'Duplicate keys in host JSON. Resolve ambiguity before retrying.');
33
+ } node.children?.forEach(check); };
34
+ if (errors.length || !tree || tree.type !== 'object')
35
+ fail('HOST_INVALID_CONFIG', 'Malformed JSON host configuration. Restore valid JSON before retrying.');
36
+ check(tree);
37
+ return JSON.parse(text);
38
+ }
39
+ function toml(text) { try {
40
+ return parseToml(text);
41
+ }
42
+ catch {
43
+ fail('HOST_INVALID_CONFIG', 'Malformed or ambiguous Codex TOML. Restore valid configuration before retrying.');
44
+ } }
45
+ function map(value) { if (value !== undefined && (!value || typeof value !== 'object' || Array.isArray(value)))
46
+ fail('HOST_UNSUPPORTED_CONFIG', 'MCP settings must be an object/table. No files changed.'); return value; }
47
+ export async function adapters(home, env, selection) {
48
+ // Relative overrides would make machine installation depend on the current project.
49
+ for (const key of (selection === 'codex' ? ['CODEX_HOME'] : selection === 'claude' ? ['CLAUDE_CONFIG_DIR'] : ['CODEX_HOME', 'CLAUDE_CONFIG_DIR']))
50
+ if (env[key] && !path.isAbsolute(env[key]))
51
+ fail('HOST_UNSUPPORTED_PATH', 'Host configuration overrides must be absolute paths inside the user home. No files changed.');
52
+ const codex = env.CODEX_HOME ? path.resolve(env.CODEX_HOME) : path.join(home, '.codex');
53
+ const claude = env.CLAUDE_CONFIG_DIR ? path.resolve(env.CLAUDE_CONFIG_DIR) : path.join(home, '.claude');
54
+ const launchers = [path.join(codex, 'AGENTS.md'), path.join(codex, 'AGENTS.override.md')];
55
+ const override = selection === 'claude' ? null : await fs.lstat(launchers[1]).catch(() => null);
56
+ const result = [{ id: 'codex', name: 'Codex', support: 'supported', config: path.join(codex, 'config.toml'), launchers, launcher: override && override.size > 0 ? launchers[1] : launchers[0],
57
+ async detect() { return await executable('codex', env) || Boolean(await fs.lstat(this.config).catch(() => null)); },
58
+ inspectConfig: toml, entry(text) { return map(toml(text).mcp_servers)?.prodero; },
59
+ alternateEntry(text) { return map(toml(text).mcp_servers)?.['prodero-dev']; },
60
+ removeAlternate: removeCodexDev,
61
+ edit(text, value) {
62
+ const stanza = value ? `${MCP_START}\n[mcp_servers.prodero]\ncommand = ${JSON.stringify(value.command)}\nargs = ${JSON.stringify(value.args)}\n${value.env ? `env = { ${Object.entries(value.env).map(([k, v]) => `${JSON.stringify(k)} = ${JSON.stringify(v)}`).join(", ")} }\n` : ""}${MCP_END}` : undefined;
63
+ const next = replaceBlock(text, MCP_START, MCP_END, stanza);
64
+ toml(next);
65
+ return next;
66
+ } },
67
+ { id: 'claude', name: 'Claude Code', support: 'experimental', config: env.CLAUDE_CONFIG_DIR ? path.join(claude, '.claude.json') : path.join(home, '.claude.json'), launchers: [path.join(claude, 'CLAUDE.md')], launcher: path.join(claude, 'CLAUDE.md'),
68
+ async detect() { return await executable('claude', env) || Boolean(await fs.lstat(this.config).catch(() => null)); },
69
+ inspectConfig: strictJson, entry(text) { return map(strictJson(text).mcpServers)?.prodero; },
70
+ alternateEntry(text) { return map(strictJson(text).mcpServers)?.['prodero-dev']; },
71
+ removeAlternate(text) { return applyEdits(text, modify(text, ['mcpServers', 'prodero-dev'], undefined, { formattingOptions: { insertSpaces: true, tabSize: 2, eol: '\n' } })); },
72
+ edit(text, value) { const next = applyEdits(text, modify(text, ['mcpServers', 'prodero'], value, { formattingOptions: { insertSpaces: true, tabSize: 2, eol: '\n' } })); strictJson(next); return next; }
73
+ }];
74
+ return selection && selection !== 'all' ? result.filter(host => host.id === selection) : result;
75
+ }
76
+ export async function executable(name, env) {
77
+ for (const dir of (env.PATH ?? '').split(path.delimiter).filter(d => path.isAbsolute(d))) {
78
+ try {
79
+ const p = path.join(dir, name);
80
+ await fs.access(p, fs.constants.X_OK);
81
+ if ((await fs.stat(p)).isFile())
82
+ return true;
83
+ }
84
+ catch { }
85
+ }
86
+ return false;
87
+ }
88
+ /** Migrate an unambiguous dev table and optional adjacent env table; preserve unrelated bytes. */
89
+ function removeCodexDev(text) {
90
+ const before = toml(text);
91
+ const headers = [...text.matchAll(/^\s*\[mcp_servers\.prodero-dev\][ \t]*(?:#[^\r\n]*)?\r?$/gm)];
92
+ if (headers.length !== 1)
93
+ fail('HOST_UNSUPPORTED_CONFIG', 'Migration requires one standalone [mcp_servers.prodero-dev] table. Review other TOML forms manually.');
94
+ const start = headers[0].index, bodyStart = start + headers[0][0].length;
95
+ const rest = text.slice(bodyStart);
96
+ let next = /^\s*\[/m.exec(rest), end = next ? bodyStart + next.index : text.length;
97
+ // Native Codex configurations commonly serialize env as a separate child table.
98
+ // Remove only this exact adjacent child; the semantic check below rejects any
99
+ // remaining descendants or other structure we cannot preserve unambiguously.
100
+ const envHeader = /^\s*\[mcp_servers\.prodero-dev\.env\][ \t]*(?:#[^\r\n]*)?\r?$/m.exec(text.slice(end));
101
+ if (envHeader?.index === 0) {
102
+ const afterEnv = end + envHeader[0].length;
103
+ next = /^\s*\[/m.exec(text.slice(afterEnv));
104
+ end = next ? afterEnv + next.index : text.length;
105
+ }
106
+ const result = text.slice(0, start) + text.slice(end);
107
+ delete before.mcp_servers['prodero-dev'];
108
+ const after = toml(result);
109
+ // Empty parent maps can disappear when their last child table is removed.
110
+ if (before.mcp_servers && Object.keys(before.mcp_servers).length === 0)
111
+ delete before.mcp_servers;
112
+ if (after.mcp_servers && Object.keys(after.mcp_servers).length === 0)
113
+ delete after.mcp_servers;
114
+ const stable = (v) => JSON.stringify(v, (_k, value) => value && typeof value === 'object' && !Array.isArray(value) ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b))) : value);
115
+ if (stable(before) !== stable(after))
116
+ fail('HOST_UNSUPPORTED_CONFIG', 'Cannot remove the dev table without changing other TOML settings. Review the registration manually.');
117
+ return result;
118
+ }
119
+ //# sourceMappingURL=adapters.js.map