superpowers-manager 0.2.0 → 0.4.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 (68) hide show
  1. package/README.md +340 -66
  2. package/dist/adapter-result.js +204 -0
  3. package/dist/adapter.js +690 -0
  4. package/dist/atomic.js +142 -0
  5. package/dist/cli-arguments.js +32 -0
  6. package/dist/cli.js +313 -0
  7. package/dist/codex-harness.js +271 -0
  8. package/dist/codex-json.js +89 -0
  9. package/dist/codex-prepare.js +199 -0
  10. package/dist/codex-presentation.js +259 -0
  11. package/dist/codex-state.js +78 -0
  12. package/dist/commands/context.js +1 -0
  13. package/dist/commands/install.js +291 -0
  14. package/dist/commands/pin.js +79 -0
  15. package/dist/commands/prepare.js +420 -0
  16. package/dist/commands/probe.js +257 -0
  17. package/dist/commands/track-latest.js +44 -0
  18. package/dist/commands/uninstall.js +217 -0
  19. package/dist/commands/unpin.js +95 -0
  20. package/dist/commands/update.js +79 -0
  21. package/dist/domain/fingerprint.js +4 -0
  22. package/dist/domain/refs.js +37 -0
  23. package/dist/effective-selection.js +123 -0
  24. package/dist/generated-plugin.js +748 -0
  25. package/dist/git.js +38 -0
  26. package/dist/harness.js +1 -0
  27. package/dist/hooks.js +290 -0
  28. package/dist/lifecycle.js +135 -0
  29. package/dist/manifest-overlay.js +125 -0
  30. package/dist/provenance.js +111 -0
  31. package/dist/python-json-format.js +83 -0
  32. package/dist/python-json.js +29 -0
  33. package/dist/python-text.js +74 -0
  34. package/dist/safe-path.js +137 -0
  35. package/dist/safety-error.js +10 -0
  36. package/dist/selection-state-cli.js +85 -0
  37. package/dist/selection-store.js +172 -0
  38. package/dist/selection.js +200 -0
  39. package/dist/status.js +24 -0
  40. package/dist/strict-json.js +248 -0
  41. package/dist/upstream-cli.js +110 -0
  42. package/dist/upstream-version.js +40 -0
  43. package/dist/upstream.js +253 -0
  44. package/dist/validate-generated-plugin-cli.js +118 -0
  45. package/dist/validator.js +317 -0
  46. package/dist/workspace.js +139 -0
  47. package/package.json +25 -4
  48. package/bin/superpowers-manager.js +0 -290
  49. package/scripts/adapters/codex/adapter +0 -603
  50. package/scripts/adapters/codex/lib.sh +0 -449
  51. package/scripts/adapters/codex/validate-generated-plugin.py +0 -314
  52. package/scripts/core/adapter.sh +0 -103
  53. package/scripts/core/common.sh +0 -59
  54. package/scripts/core/lifecycle.sh +0 -141
  55. package/scripts/core/provenance.sh +0 -135
  56. package/scripts/core/selection-state.py +0 -414
  57. package/scripts/core/selection.sh +0 -168
  58. package/scripts/core/status.sh +0 -27
  59. package/scripts/core/upstream.sh +0 -280
  60. package/scripts/core/validate-adapter-response.py +0 -284
  61. package/scripts/install +0 -59
  62. package/scripts/pin +0 -51
  63. package/scripts/prepare +0 -117
  64. package/scripts/probe +0 -82
  65. package/scripts/track-latest +0 -30
  66. package/scripts/uninstall +0 -35
  67. package/scripts/unpin +0 -41
  68. package/scripts/update +0 -34
@@ -0,0 +1,271 @@
1
+ import { failureResult, hasTerminalControl, successResult, } from "./adapter-result.js";
2
+ import { codexInspect, codexInstall, codexRemove, } from "./adapter.js";
3
+ import { codexPreparationLocation, inspectCodexPrepared, prepareCodexCandidate, readCodexPrepared, validateCodexPreparationBeforeFetch, } from "./codex-prepare.js";
4
+ import { codexInstallReceipt, codexPresentation, } from "./codex-presentation.js";
5
+ import { reportLegacyState, requireManagedUpdateControl, requireNoLegacyState, verifyUninstalledResources, } from "./lifecycle.js";
6
+ import { commitMatches } from "./status.js";
7
+ function preserveFailure(result) {
8
+ if (result.outcome.ok) {
9
+ throw new Error("cannot preserve a successful adapter result as failure");
10
+ }
11
+ return { status: result.status, outcome: result.outcome };
12
+ }
13
+ function malformed(result, message) {
14
+ return failureResult(result.outcome.operation, "malformed-result", message, [], result.outcome.messages);
15
+ }
16
+ function invalidStatus(result, message) {
17
+ return failureResult(result.outcome.operation, "invalid-status", message, [], result.outcome.messages);
18
+ }
19
+ function resultRecord(result) {
20
+ const value = result.outcome.ok ? result.outcome.result : null;
21
+ return typeof value === "object" && value !== null && !Array.isArray(value)
22
+ ? value
23
+ : null;
24
+ }
25
+ function stringField(result, key) {
26
+ const raw = result[key];
27
+ if (raw === null || raw === undefined)
28
+ return { ok: true, value: "" };
29
+ if (typeof raw === "string")
30
+ return { ok: true, value: raw };
31
+ return { ok: false };
32
+ }
33
+ function resourceFlag(result, key) {
34
+ const resources = result.resources;
35
+ const bag = typeof resources === "object" &&
36
+ resources !== null &&
37
+ !Array.isArray(resources)
38
+ ? resources
39
+ : {};
40
+ const value = bag[key];
41
+ return typeof value === "boolean" ? value : null;
42
+ }
43
+ function installDecision(legacy) {
44
+ switch (legacy.kind) {
45
+ case "ok":
46
+ return { kind: "allowed" };
47
+ case "blocked":
48
+ return {
49
+ kind: "blocked",
50
+ output: { stdout: [], stderr: legacy.lines },
51
+ };
52
+ case "unknown":
53
+ return {
54
+ kind: "blocked",
55
+ output: { stdout: [], stderr: [`error: ${legacy.message}`] },
56
+ };
57
+ case "report":
58
+ return {
59
+ kind: "blocked",
60
+ output: {
61
+ stdout: [],
62
+ stderr: ["error: unexpected legacy report during installation"],
63
+ },
64
+ };
65
+ }
66
+ }
67
+ export function normalizeCodexOwnership(result) {
68
+ if (!result.outcome.ok)
69
+ return preserveFailure(result);
70
+ if (result.status !== 0) {
71
+ return invalidStatus(result, "adapter reported a failure status for inspect --view ownership");
72
+ }
73
+ const record = resultRecord(result);
74
+ if (record === null) {
75
+ return malformed(result, "adapter returned a non-object result for inspect --view ownership");
76
+ }
77
+ const pluginPresent = resourceFlag(record, "plugin");
78
+ if (pluginPresent === null) {
79
+ return malformed(result, "expected a Boolean adapter result at resources.plugin");
80
+ }
81
+ const marketplacePresent = resourceFlag(record, "marketplace");
82
+ if (marketplacePresent === null) {
83
+ return malformed(result, "expected a Boolean adapter result at resources.marketplace");
84
+ }
85
+ const identity = stringField(record, "identity_state");
86
+ if (!identity.ok) {
87
+ return malformed(result, "adapter returned a non-string identity_state for inspect --view ownership");
88
+ }
89
+ const identityState = identity.value;
90
+ const installEligibility = identityState.length === 0
91
+ ? {
92
+ kind: "blocked",
93
+ output: {
94
+ stdout: [],
95
+ stderr: ["error: probe did not report adapter identity state"],
96
+ },
97
+ }
98
+ : installDecision(requireNoLegacyState(identityState));
99
+ const verification = verifyUninstalledResources(result);
100
+ const legacyReport = reportLegacyState(identityState);
101
+ let removalVerification;
102
+ if (!verification.ok) {
103
+ removalVerification = {
104
+ kind: "blocked",
105
+ output: { stdout: [], stderr: [`error: ${verification.message}`] },
106
+ };
107
+ }
108
+ else if (legacyReport.kind === "unknown") {
109
+ removalVerification = {
110
+ kind: "blocked",
111
+ output: { stdout: [], stderr: [`error: ${legacyReport.message}`] },
112
+ };
113
+ }
114
+ else if (legacyReport.kind === "blocked") {
115
+ removalVerification = {
116
+ kind: "blocked",
117
+ output: {
118
+ stdout: [],
119
+ stderr: ["error: unexpected legacy block after removal"],
120
+ },
121
+ };
122
+ }
123
+ else {
124
+ removalVerification = { kind: "allowed" };
125
+ }
126
+ const postRemovalOutput = legacyReport.kind === "report"
127
+ ? { stdout: legacyReport.lines, stderr: [] }
128
+ : { stdout: [], stderr: [] };
129
+ return successResult(result.outcome.operation, {
130
+ installEligibility,
131
+ removalInput: { pluginPresent, marketplacePresent },
132
+ removalVerification,
133
+ postRemovalOutput,
134
+ presentationValue: identityState,
135
+ }, result.outcome.messages);
136
+ }
137
+ export function normalizeCodexControl(result) {
138
+ if (!result.outcome.ok)
139
+ return preserveFailure(result);
140
+ if (result.status !== 0) {
141
+ return invalidStatus(result, "adapter reported a failure status for inspect --view update-control");
142
+ }
143
+ const record = resultRecord(result);
144
+ if (record === null) {
145
+ return malformed(result, "adapter returned a non-object result for inspect --view update-control");
146
+ }
147
+ const control = stringField(record, "update_control");
148
+ if (!control.ok) {
149
+ return malformed(result, "adapter returned a non-string update_control for inspect --view update-control");
150
+ }
151
+ const updateControl = control.value;
152
+ const probeEligibility = updateControl.length === 0
153
+ ? {
154
+ kind: "blocked",
155
+ output: {
156
+ stdout: [],
157
+ stderr: [
158
+ "error: probe did not report adapter update-control capability",
159
+ ],
160
+ },
161
+ }
162
+ : { kind: "allowed" };
163
+ const managed = requireManagedUpdateControl(updateControl);
164
+ const mutationEligibility = probeEligibility.kind === "blocked"
165
+ ? probeEligibility
166
+ : managed.ok
167
+ ? { kind: "allowed" }
168
+ : {
169
+ kind: "blocked",
170
+ output: {
171
+ stdout: [],
172
+ stderr: [`error: ${managed.message}`],
173
+ },
174
+ };
175
+ return successResult(result.outcome.operation, { probeEligibility, mutationEligibility, presentationValue: updateControl }, result.outcome.messages);
176
+ }
177
+ export function normalizeCodexInstalled(result, desiredCommit) {
178
+ if (!result.outcome.ok)
179
+ return preserveFailure(result);
180
+ if (result.status !== 0) {
181
+ return invalidStatus(result, "adapter reported a failure status for inspect --view fingerprint");
182
+ }
183
+ const record = resultRecord(result);
184
+ if (record === null) {
185
+ return malformed(result, "adapter returned a non-object result for inspect --view fingerprint");
186
+ }
187
+ const fingerprint = stringField(record, "fingerprint");
188
+ if (!fingerprint.ok) {
189
+ return malformed(result, "adapter returned a non-string fingerprint for inspect --view fingerprint");
190
+ }
191
+ const observedIdentity = fingerprint.value;
192
+ const installed = observedIdentity.length === 0
193
+ ? { kind: "absent", observedIdentity: "" }
194
+ : commitMatches(desiredCommit, observedIdentity)
195
+ ? { kind: "current", observedIdentity }
196
+ : { kind: "mismatch", observedIdentity };
197
+ return successResult(result.outcome.operation, installed, result.outcome.messages);
198
+ }
199
+ function safeHint(value) {
200
+ return typeof value === "string" && !hasTerminalControl(value) ? value : "";
201
+ }
202
+ export function normalizeCodexInstall(result) {
203
+ if (!result.outcome.ok)
204
+ return preserveFailure(result);
205
+ if (result.status !== 0) {
206
+ return invalidStatus(result, "adapter reported a failure status for install");
207
+ }
208
+ const record = resultRecord(result);
209
+ if (record === null) {
210
+ return malformed(result, "adapter returned a non-object result for install");
211
+ }
212
+ const hints = record.verification_hints;
213
+ const bag = typeof hints === "object" && hints !== null && !Array.isArray(hints)
214
+ ? hints
215
+ : {};
216
+ return successResult(result.outcome.operation, codexInstallReceipt(safeHint(bag.missing), safeHint(bag.mismatch)), result.outcome.messages);
217
+ }
218
+ // The canonical payload normalizer has no context parameter. Keep the native
219
+ // call's defensive success/nonzero-status guard beside the concrete adapter,
220
+ // where the exact legacy diagnostic can still name ctx.root.
221
+ export function normalizeCodexInstallForContext(result, ctx) {
222
+ if (!result.outcome.ok)
223
+ return preserveFailure(result);
224
+ if (result.status !== 0) {
225
+ return invalidStatus(result, codexPresentation.callFailure("install", ctx).invalidStatus);
226
+ }
227
+ return normalizeCodexInstall(result);
228
+ }
229
+ function requirements(command, env) {
230
+ if (command !== "probe" &&
231
+ command !== "install" &&
232
+ command !== "update" &&
233
+ command !== "uninstall") {
234
+ return [];
235
+ }
236
+ const executable = env.SUPERPOWERS_CODEX || "codex";
237
+ return [
238
+ {
239
+ name: "codex",
240
+ executable,
241
+ lookup: "explicit-path-or-path",
242
+ missingMessage: `required command not found: ${executable} — install the Codex CLI or set SUPERPOWERS_CODEX`,
243
+ },
244
+ ];
245
+ }
246
+ export const codexHarness = {
247
+ preparationLocation: codexPreparationLocation,
248
+ validatePreparationBeforeFetch: validateCodexPreparationBeforeFetch,
249
+ prepareCandidate: prepareCodexCandidate,
250
+ inspectPrepared: inspectCodexPrepared,
251
+ readPrepared: readCodexPrepared,
252
+ inspectOwnership: async (ctx) => normalizeCodexOwnership(await codexInspect("ownership", ctx)),
253
+ inspectUpdateControl: async (ctx) => normalizeCodexControl(await codexInspect("update-control", ctx)),
254
+ inspectInstalled: async (selection, ctx) => normalizeCodexInstalled(await codexInspect("fingerprint", ctx), selection.desiredCommit),
255
+ install: async (_artifact, ctx) => {
256
+ return normalizeCodexInstallForContext(await codexInstall(ctx.root, ctx), ctx);
257
+ },
258
+ remove: async (input, ctx) => {
259
+ const result = await codexRemove(input, ctx);
260
+ if (!result.outcome.ok)
261
+ return preserveFailure(result);
262
+ if (result.status !== 0) {
263
+ return invalidStatus(result, codexPresentation.callFailure("remove", ctx, input).invalidStatus);
264
+ }
265
+ return successResult(result.outcome.operation, null, result.outcome.messages);
266
+ },
267
+ requirements,
268
+ get presentation() {
269
+ return codexPresentation;
270
+ },
271
+ };
@@ -0,0 +1,89 @@
1
+ import { SafetyError } from "./safety-error.js";
2
+ import { parseStrictJson, } from "./strict-json.js";
3
+ const ACCEPT_CONSTANTS = {
4
+ duplicateKeys: "last-wins",
5
+ nonStandardConstants: "accept",
6
+ };
7
+ const REJECT_CONSTANTS = {
8
+ duplicateKeys: "last-wins",
9
+ nonStandardConstants: "reject",
10
+ };
11
+ function fail(message, cause) {
12
+ throw new SafetyError("codex-json", message, { cause });
13
+ }
14
+ function object(value) {
15
+ return value !== null && typeof value === "object" && !Array.isArray(value)
16
+ ? value
17
+ : undefined;
18
+ }
19
+ function parseObject(raw, profile) {
20
+ let parsed;
21
+ try {
22
+ parsed = parseStrictJson(raw, profile);
23
+ }
24
+ catch (cause) {
25
+ fail("cannot parse Codex JSON", cause);
26
+ }
27
+ const result = object(parsed);
28
+ if (result === undefined)
29
+ fail("Codex JSON must be an object");
30
+ return result;
31
+ }
32
+ function checkedItems(raw, profile, arrayKey, field) {
33
+ const items = parseObject(raw, profile)[arrayKey];
34
+ if (!Array.isArray(items))
35
+ fail(`Codex JSON ${arrayKey} must be an array`);
36
+ return items.map((item) => {
37
+ const candidate = object(item);
38
+ if (candidate === undefined) {
39
+ fail(`Codex JSON ${arrayKey} item must be an object`);
40
+ }
41
+ const fieldValue = candidate[field];
42
+ if (typeof fieldValue !== "string" || fieldValue.length === 0) {
43
+ fail(`Codex JSON ${arrayKey} item needs non-empty ${field}`);
44
+ }
45
+ return candidate;
46
+ });
47
+ }
48
+ function hasTerminalControl(value) {
49
+ for (const character of value) {
50
+ const code = character.codePointAt(0);
51
+ if (code < 0x20 ||
52
+ (code >= 0x7f && code <= 0x9f) ||
53
+ (code >= 0xd800 && code <= 0xdfff)) {
54
+ return true;
55
+ }
56
+ }
57
+ return false;
58
+ }
59
+ export function installedListingHas(raw, arrayKey, field, value) {
60
+ return checkedItems(raw, ACCEPT_CONSTANTS, arrayKey, field).some((item) => item[field] === value);
61
+ }
62
+ export function marketplaceRootFromJson(raw, marketplaceName) {
63
+ const items = checkedItems(raw, ACCEPT_CONSTANTS, "marketplaces", "name");
64
+ const match = items.find((item) => item.name === marketplaceName);
65
+ if (match === undefined)
66
+ return "";
67
+ if (typeof match.root !== "string" || match.root.length === 0) {
68
+ fail("matching marketplace needs a non-empty root");
69
+ }
70
+ return match.root;
71
+ }
72
+ export function activePluginVersionFromJson(raw, pluginId) {
73
+ const matches = checkedItems(raw, REJECT_CONSTANTS, "installed", "pluginId").filter((item) => item.pluginId === pluginId);
74
+ if (matches.length > 1)
75
+ fail("active plugin appears more than once");
76
+ if (matches.length === 0)
77
+ return "";
78
+ const version = matches[0].version;
79
+ if (typeof version !== "string" ||
80
+ version.length === 0 ||
81
+ version === "." ||
82
+ version === ".." ||
83
+ version.includes("/") ||
84
+ version.includes("\\") ||
85
+ hasTerminalControl(version)) {
86
+ fail("active plugin version is invalid");
87
+ }
88
+ return version;
89
+ }
@@ -0,0 +1,199 @@
1
+ import { cp, mkdir, rm, stat } from "node:fs/promises";
2
+ import { isAbsolute, join, resolve } from "node:path";
3
+ import { failureResult, successResult, } from "./adapter-result.js";
4
+ import { codexBuild } from "./adapter.js";
5
+ import { commitMatches } from "./domain/fingerprint.js";
6
+ import { readManifest } from "./hooks.js";
7
+ import { generatedCommitOrEmpty, generatedMetadataPath, readStrictProvenanceField, writeProvenance, } from "./provenance.js";
8
+ import { SafetyError } from "./safety-error.js";
9
+ import { manifestVersionForRef } from "./upstream-version.js";
10
+ // Order is inherited from the original prepare command; the first miss wins.
11
+ const REQUIRED_UPSTREAM = [
12
+ { path: "skills", label: "skills/" },
13
+ { path: "LICENSE", label: "LICENSE" },
14
+ { path: "README.md", label: "README.md" },
15
+ { path: "CODE_OF_CONDUCT.md", label: "CODE_OF_CONDUCT.md" },
16
+ ];
17
+ const COPY_PATHS = [
18
+ "skills",
19
+ "assets",
20
+ "LICENSE",
21
+ "README.md",
22
+ "CODE_OF_CONDUCT.md",
23
+ ];
24
+ const RESOLUTION_KINDS = [
25
+ "latest-release",
26
+ "tag",
27
+ "ref",
28
+ "raw-commit",
29
+ ];
30
+ function prepareError(message, cause) {
31
+ return new SafetyError("prepare", message, { cause });
32
+ }
33
+ function isControlledPreparationError(cause) {
34
+ return (cause instanceof SafetyError &&
35
+ (cause.module === "prepare" ||
36
+ cause.module === "hooks" ||
37
+ cause.module === "provenance"));
38
+ }
39
+ async function pathExists(path) {
40
+ try {
41
+ await stat(path);
42
+ return true;
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
48
+ async function regularFileExists(path) {
49
+ try {
50
+ return (await stat(path)).isFile();
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ }
56
+ async function owned(message, fn) {
57
+ try {
58
+ return await fn();
59
+ }
60
+ catch (cause) {
61
+ throw prepareError(message, cause);
62
+ }
63
+ }
64
+ async function copyPathIfPresent(source, destination) {
65
+ if (!(await pathExists(source)))
66
+ return;
67
+ await owned(`cannot clear candidate path: ${destination}`, () => rm(destination, { recursive: true, force: true }));
68
+ await owned(`cannot copy upstream path into candidate: ${source}`, () => cp(source, destination, { recursive: true, verbatimSymlinks: true }));
69
+ }
70
+ export function resolveFromCwd(value, cwd) {
71
+ return isAbsolute(value) ? value : resolve(cwd, value);
72
+ }
73
+ function manifestTemplate(ctx) {
74
+ const env = ctx.env ?? {};
75
+ return (env.SUPERPOWERS_MANIFEST_TEMPLATE ||
76
+ join(ctx.root, "plugins", "superpowers", ".codex-plugin", "plugin.template.json"));
77
+ }
78
+ export function codexPreparationLocation(ctx) {
79
+ const env = ctx.env ?? {};
80
+ return {
81
+ destinationRoot: resolveFromCwd(env.SUPERPOWERS_PLUGIN_ROOT || join(ctx.root, "plugins", "superpowers"), process.cwd()),
82
+ stagingLeaf: "superpowers",
83
+ };
84
+ }
85
+ export async function validateCodexPreparationBeforeFetch(ctx) {
86
+ const template = manifestTemplate(ctx);
87
+ if (!(await regularFileExists(template))) {
88
+ return failureResult("prepare", "missing-template", `missing fallback manifest template: ${template}`, [], []);
89
+ }
90
+ return successResult("prepare", null, []);
91
+ }
92
+ export async function readUpstreamManifestVersion(path) {
93
+ const manifest = await readManifest(path);
94
+ const value = manifest.version;
95
+ if (value === undefined || value === null)
96
+ return "";
97
+ if (typeof value !== "string") {
98
+ throw prepareError(`upstream manifest version is not a string: ${path}`);
99
+ }
100
+ return value;
101
+ }
102
+ function asResolutionKind(value) {
103
+ for (const kind of RESOLUTION_KINDS) {
104
+ if (kind === value)
105
+ return kind;
106
+ }
107
+ throw prepareError(`unknown upstream resolution kind: ${value}`);
108
+ }
109
+ export async function prepareCodexCandidate(input, ctx) {
110
+ let upstreamManifestVersion = "";
111
+ let managerVersion = "";
112
+ try {
113
+ for (const required of REQUIRED_UPSTREAM) {
114
+ if (!(await pathExists(join(input.upstreamRoot, required.path)))) {
115
+ return failureResult("prepare", "missing-upstream-path", `required upstream path missing: ${required.label}`, [], []);
116
+ }
117
+ }
118
+ await owned(`cannot clear candidate root: ${input.candidateRoot}`, () => rm(input.candidateRoot, { recursive: true, force: true }));
119
+ await owned(`cannot create candidate root: ${input.candidateRoot}`, () => mkdir(join(input.candidateRoot, ".codex-plugin"), { recursive: true }));
120
+ for (const name of COPY_PATHS) {
121
+ await copyPathIfPresent(join(input.upstreamRoot, name), join(input.candidateRoot, name));
122
+ }
123
+ const upstreamManifest = join(input.upstreamRoot, ".codex-plugin", "plugin.json");
124
+ if (await regularFileExists(upstreamManifest)) {
125
+ upstreamManifestVersion =
126
+ await readUpstreamManifestVersion(upstreamManifest);
127
+ }
128
+ await writeProvenance(join(input.candidateRoot, ".superpowers-upstream.json"), {
129
+ source: input.selection.effectiveSource,
130
+ requested_ref: input.selection.requestedRef,
131
+ resolved_ref: input.selection.resolvedRef,
132
+ commit: input.selection.desiredCommit,
133
+ upstream_manifest_version: upstreamManifestVersion,
134
+ });
135
+ managerVersion = manifestVersionForRef({
136
+ requestedRef: input.selection.requestedRef,
137
+ resolutionKind: asResolutionKind(input.selection.resolutionKind),
138
+ resolvedRef: input.selection.resolvedRef,
139
+ commit: input.selection.desiredCommit,
140
+ });
141
+ }
142
+ catch (cause) {
143
+ // owned()/asResolutionKind(), readManifest(), and writeProvenance replace
144
+ // every subordinate failure with controlled preparation, hook, or
145
+ // provenance text. Re-emitting those owned diagnostics is therefore safe.
146
+ // codexBuild stays below this catch because it deliberately rethrows
147
+ // exceptions its native operation does not own; the shared command must
148
+ // hide those.
149
+ if (!isControlledPreparationError(cause))
150
+ throw cause;
151
+ return failureResult("prepare", "prepare-failed", cause.message, [], []);
152
+ }
153
+ const built = await codexBuild({
154
+ upstreamRoot: input.upstreamRoot,
155
+ candidateRoot: input.candidateRoot,
156
+ requestedRef: input.selection.requestedRef,
157
+ resolvedRef: input.selection.resolvedRef,
158
+ commit: input.selection.desiredCommit,
159
+ managerVersion,
160
+ upstreamManifestVersion,
161
+ fallbackManifest: manifestTemplate(ctx),
162
+ }, ctx);
163
+ if (!built.outcome.ok) {
164
+ return { status: built.status, outcome: built.outcome };
165
+ }
166
+ if (built.status !== 0) {
167
+ return failureResult("build", "invalid-status", "adapter reported failure without an error outcome", [], built.outcome.messages);
168
+ }
169
+ return successResult(built.outcome.operation, { root: input.candidateRoot, commit: input.selection.desiredCommit }, built.outcome.messages);
170
+ }
171
+ export async function inspectCodexPrepared(selection, ctx) {
172
+ const observedIdentity = await generatedCommitOrEmpty(ctx.root);
173
+ if (!commitMatches(selection.desiredCommit, observedIdentity)) {
174
+ return successResult("inspect-prepared", { kind: "needs-prepare", observedIdentity }, []);
175
+ }
176
+ return successResult("inspect-prepared", {
177
+ kind: "current",
178
+ artifact: {
179
+ root: join(ctx.root, "plugins", "superpowers"),
180
+ commit: observedIdentity,
181
+ },
182
+ observedIdentity,
183
+ }, []);
184
+ }
185
+ export async function readCodexPrepared(ctx) {
186
+ let commit = "";
187
+ try {
188
+ const value = await readStrictProvenanceField(generatedMetadataPath(ctx.root), "commit");
189
+ if (typeof value === "string")
190
+ commit = value;
191
+ }
192
+ catch {
193
+ // The install path treats malformed metadata as an absent commit.
194
+ }
195
+ if (commit.length === 0) {
196
+ return failureResult("read-prepared", "missing-commit", "generated metadata missing desired commit after prepare", [], []);
197
+ }
198
+ return successResult("read-prepared", { root: join(ctx.root, "plugins", "superpowers"), commit }, []);
199
+ }