superpowers-manager 0.5.2 → 0.7.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.
- package/README.md +44 -26
- package/dist/adapter-result.js +5 -9
- package/dist/atomic.js +4 -14
- package/dist/cli-arguments.js +1 -27
- package/dist/cli.js +32 -52
- package/dist/commands/adapter-call.js +28 -0
- package/dist/commands/install.js +26 -107
- package/dist/commands/mutation.js +7 -0
- package/dist/commands/pin.js +1 -1
- package/dist/commands/prepare.js +39 -129
- package/dist/commands/probe.js +35 -264
- package/dist/commands/track-latest.js +1 -1
- package/dist/commands/uninstall.js +16 -92
- package/dist/commands/update.js +5 -25
- package/dist/effective-selection.js +7 -9
- package/dist/git-tree.js +85 -0
- package/dist/harness-command-result.js +47 -0
- package/dist/harnesses/codex/adapter.js +231 -510
- package/dist/harnesses/codex/compatibility.js +2 -17
- package/dist/harnesses/codex/conflicts.js +2 -3
- package/dist/harnesses/codex/harness.js +18 -263
- package/dist/harnesses/codex/hooks.js +1 -1
- package/dist/harnesses/codex/json.js +1 -11
- package/dist/harnesses/codex/lifecycle.js +80 -72
- package/dist/harnesses/codex/manifest-overlay.js +23 -18
- package/dist/harnesses/codex/paths.js +14 -29
- package/dist/harnesses/codex/presentation.js +8 -37
- package/dist/harnesses/codex/publication.js +13 -37
- package/dist/harnesses/codex/recovery.js +5 -5
- package/dist/harnesses/codex/stored-state.js +1 -1
- package/dist/harnesses/opencode/config.js +256 -0
- package/dist/harnesses/opencode/discovery.js +587 -0
- package/dist/harnesses/opencode/harness.js +37 -0
- package/dist/harnesses/opencode/install.js +601 -0
- package/dist/harnesses/opencode/native.js +57 -0
- package/dist/harnesses/opencode/package.js +89 -0
- package/dist/harnesses/opencode/paths.js +38 -0
- package/dist/harnesses/opencode/prepare.js +30 -0
- package/dist/harnesses/opencode/presentation.js +28 -0
- package/dist/harnesses/opencode/state.js +174 -0
- package/dist/harnesses/pi/compatibility.js +5 -37
- package/dist/harnesses/pi/install.js +7 -5
- package/dist/harnesses/pi/native.js +3 -47
- package/dist/harnesses/pi/package.js +5 -107
- package/dist/harnesses/pi/prepare.js +13 -104
- package/dist/harnesses/pi/presentation.js +23 -66
- package/dist/harnesses/pi/shared-skills.js +28 -39
- package/dist/harnesses/pi/state.js +7 -27
- package/dist/provenance.js +2 -2
- package/dist/python-json-format.js +0 -69
- package/dist/python-json.js +2 -27
- package/dist/python-text.js +9 -69
- package/dist/safe-path.js +2 -2
- package/dist/selection-store.js +2 -22
- package/dist/snapshot-package.js +78 -0
- package/dist/snapshot-prepare.js +99 -0
- package/dist/snapshot-presentation.js +44 -0
- package/dist/validator.js +39 -73
- package/node_modules/jsonc-parser/CHANGELOG.md +76 -0
- package/node_modules/jsonc-parser/LICENSE.md +21 -0
- package/node_modules/jsonc-parser/README.md +364 -0
- package/node_modules/jsonc-parser/SECURITY.md +41 -0
- package/node_modules/jsonc-parser/lib/esm/impl/edit.js +185 -0
- package/node_modules/jsonc-parser/lib/esm/impl/format.js +261 -0
- package/node_modules/jsonc-parser/lib/esm/impl/parser.js +659 -0
- package/node_modules/jsonc-parser/lib/esm/impl/scanner.js +443 -0
- package/node_modules/jsonc-parser/lib/esm/impl/string-intern.js +29 -0
- package/node_modules/jsonc-parser/lib/esm/main.d.ts +351 -0
- package/node_modules/jsonc-parser/lib/esm/main.js +178 -0
- package/node_modules/jsonc-parser/lib/umd/impl/edit.js +201 -0
- package/node_modules/jsonc-parser/lib/umd/impl/format.js +275 -0
- package/node_modules/jsonc-parser/lib/umd/impl/parser.js +682 -0
- package/node_modules/jsonc-parser/lib/umd/impl/scanner.js +456 -0
- package/node_modules/jsonc-parser/lib/umd/impl/string-intern.js +42 -0
- package/node_modules/jsonc-parser/lib/umd/main.d.ts +351 -0
- package/node_modules/jsonc-parser/lib/umd/main.js +194 -0
- package/node_modules/jsonc-parser/package.json +37 -0
- package/package.json +8 -4
- package/dist/domain/fingerprint.js +0 -4
- package/dist/selection-state-cli.js +0 -85
- package/dist/status.js +0 -24
- package/dist/upstream-cli.js +0 -110
- package/dist/validate-generated-plugin-cli.js +0 -118
|
@@ -61,24 +61,9 @@ export async function readCodexAssessment(root) {
|
|
|
61
61
|
});
|
|
62
62
|
if (errors.length)
|
|
63
63
|
throw new Error("generated resources");
|
|
64
|
-
const
|
|
64
|
+
const compatibility = await assessCodexCompatibility(root, {
|
|
65
65
|
effectiveSource: receipt.source,
|
|
66
|
-
|
|
67
|
-
requestedRef: provenance.requested_ref,
|
|
68
|
-
resolvedRef: provenance.resolved_ref,
|
|
69
|
-
resolutionKind: "raw-commit",
|
|
70
|
-
selectionOrigin: "package-default",
|
|
71
|
-
selectionMode: "default",
|
|
72
|
-
upstreamSourceOrigin: "package-default",
|
|
73
|
-
saved: {
|
|
74
|
-
saved_mode: "none",
|
|
75
|
-
saved_source: "",
|
|
76
|
-
saved_requested_ref: "",
|
|
77
|
-
saved_resolved_ref: "",
|
|
78
|
-
saved_commit: "",
|
|
79
|
-
},
|
|
80
|
-
};
|
|
81
|
-
const compatibility = await assessCodexCompatibility(root, selection);
|
|
66
|
+
});
|
|
82
67
|
return {
|
|
83
68
|
root,
|
|
84
69
|
commit: receipt.commit,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { lstat, readFile, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { codexInstalledPluginsFromJson, } from "./json.js";
|
|
4
3
|
export const CODEX_MANAGER_PLUGIN_ID = "superpowers@superpowers-manager";
|
|
5
4
|
export const CODEX_LEGACY_PLUGIN_ID = "superpowers@superpowers-wrapper";
|
|
6
5
|
const SUPERPOWERS_PLUGIN_PREFIX = "superpowers@";
|
|
@@ -85,9 +84,9 @@ async function nativeRouteConflict(ctx) {
|
|
|
85
84
|
}
|
|
86
85
|
return NATIVE_ROUTE_CONFLICT;
|
|
87
86
|
}
|
|
88
|
-
export async function inspectCodexConflicts(ctx,
|
|
87
|
+
export async function inspectCodexConflicts(ctx, installedPlugins) {
|
|
89
88
|
const conflicts = new Set();
|
|
90
|
-
for (const plugin of
|
|
89
|
+
for (const plugin of installedPlugins) {
|
|
91
90
|
const conflict = unmanagedPluginConflict(plugin);
|
|
92
91
|
if (conflict.length > 0)
|
|
93
92
|
conflicts.add(conflict);
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { failureResult,
|
|
2
|
-
import {
|
|
1
|
+
import { failureResult, successResult, } from "../../adapter-result.js";
|
|
2
|
+
import { codexInspectControl, codexInspectOwnership, codexInstall, } from "./adapter.js";
|
|
3
3
|
import { codexPreparationLocation, inspectCodexPrepared, prepareCodexCandidate, readCodexPrepared, validateCodexPreparationBeforeFetch, } from "./prepare.js";
|
|
4
|
-
import {
|
|
5
|
-
import { reportLegacyState, requireManagedUpdateControl, requireNoLegacyState, verifyUninstalledResources, } from "./lifecycle.js";
|
|
6
|
-
import { commitMatches } from "../../status.js";
|
|
4
|
+
import { codexPresentation } from "./presentation.js";
|
|
7
5
|
import { codexHome, codexPaths } from "./paths.js";
|
|
8
6
|
import { installCodexMarketplace, removeCodexMarketplace, } from "./publication.js";
|
|
9
7
|
import { readCodexRecovery } from "./recovery.js";
|
|
@@ -14,257 +12,11 @@ function preserveFailure(result) {
|
|
|
14
12
|
}
|
|
15
13
|
return { status: result.status, outcome: result.outcome };
|
|
16
14
|
}
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
function invalidStatus(result, message) {
|
|
15
|
+
export function rejectSuccessfulNonzeroStatus(result, message) {
|
|
16
|
+
if (!result.outcome.ok || result.status === 0)
|
|
17
|
+
return result;
|
|
21
18
|
return failureResult(result.outcome.operation, "invalid-status", message, [], result.outcome.messages);
|
|
22
19
|
}
|
|
23
|
-
function resultRecord(result) {
|
|
24
|
-
const value = result.outcome.ok ? result.outcome.result : null;
|
|
25
|
-
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
26
|
-
? value
|
|
27
|
-
: null;
|
|
28
|
-
}
|
|
29
|
-
function stringField(result, key) {
|
|
30
|
-
const raw = result[key];
|
|
31
|
-
if (raw === null || raw === undefined)
|
|
32
|
-
return { ok: true, value: "" };
|
|
33
|
-
if (typeof raw === "string")
|
|
34
|
-
return { ok: true, value: raw };
|
|
35
|
-
return { ok: false };
|
|
36
|
-
}
|
|
37
|
-
function resourceFlag(result, key) {
|
|
38
|
-
const resources = result.resources;
|
|
39
|
-
const bag = typeof resources === "object" &&
|
|
40
|
-
resources !== null &&
|
|
41
|
-
!Array.isArray(resources)
|
|
42
|
-
? resources
|
|
43
|
-
: {};
|
|
44
|
-
const value = bag[key];
|
|
45
|
-
return typeof value === "boolean" ? value : null;
|
|
46
|
-
}
|
|
47
|
-
function conflictDescriptions(result) {
|
|
48
|
-
const raw = result.conflicts;
|
|
49
|
-
if (raw === null || raw === undefined)
|
|
50
|
-
return [];
|
|
51
|
-
if (!Array.isArray(raw))
|
|
52
|
-
return null;
|
|
53
|
-
const descriptions = [];
|
|
54
|
-
for (const item of raw) {
|
|
55
|
-
if (typeof item !== "string" ||
|
|
56
|
-
item.length === 0 ||
|
|
57
|
-
hasTerminalControl(item)) {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
descriptions.push(item);
|
|
61
|
-
}
|
|
62
|
-
return descriptions;
|
|
63
|
-
}
|
|
64
|
-
function installDecision(legacy) {
|
|
65
|
-
switch (legacy.kind) {
|
|
66
|
-
case "ok":
|
|
67
|
-
return { kind: "allowed" };
|
|
68
|
-
case "blocked":
|
|
69
|
-
return {
|
|
70
|
-
kind: "blocked",
|
|
71
|
-
output: { stdout: [], stderr: legacy.lines },
|
|
72
|
-
};
|
|
73
|
-
case "unknown":
|
|
74
|
-
return {
|
|
75
|
-
kind: "blocked",
|
|
76
|
-
output: { stdout: [], stderr: [`error: ${legacy.message}`] },
|
|
77
|
-
};
|
|
78
|
-
case "report":
|
|
79
|
-
return {
|
|
80
|
-
kind: "blocked",
|
|
81
|
-
output: {
|
|
82
|
-
stdout: [],
|
|
83
|
-
stderr: ["error: unexpected legacy report during installation"],
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
export function normalizeCodexOwnership(result) {
|
|
89
|
-
if (!result.outcome.ok)
|
|
90
|
-
return preserveFailure(result);
|
|
91
|
-
if (result.status !== 0) {
|
|
92
|
-
return invalidStatus(result, "adapter reported a failure status for inspect --view ownership");
|
|
93
|
-
}
|
|
94
|
-
const record = resultRecord(result);
|
|
95
|
-
if (record === null) {
|
|
96
|
-
return malformed(result, "adapter returned a non-object result for inspect --view ownership");
|
|
97
|
-
}
|
|
98
|
-
const pluginPresent = resourceFlag(record, "plugin");
|
|
99
|
-
if (pluginPresent === null) {
|
|
100
|
-
return malformed(result, "expected a Boolean adapter result at resources.plugin");
|
|
101
|
-
}
|
|
102
|
-
const marketplacePresent = resourceFlag(record, "marketplace");
|
|
103
|
-
if (marketplacePresent === null) {
|
|
104
|
-
return malformed(result, "expected a Boolean adapter result at resources.marketplace");
|
|
105
|
-
}
|
|
106
|
-
const identity = stringField(record, "identity_state");
|
|
107
|
-
if (!identity.ok) {
|
|
108
|
-
return malformed(result, "adapter returned a non-string identity_state for inspect --view ownership");
|
|
109
|
-
}
|
|
110
|
-
const identityState = identity.value;
|
|
111
|
-
const conflicts = conflictDescriptions(record);
|
|
112
|
-
if (conflicts === null) {
|
|
113
|
-
return malformed(result, "expected an array of strings at conflicts");
|
|
114
|
-
}
|
|
115
|
-
const legacyEligibility = identityState.length === 0
|
|
116
|
-
? {
|
|
117
|
-
kind: "blocked",
|
|
118
|
-
output: {
|
|
119
|
-
stdout: [],
|
|
120
|
-
stderr: ["error: probe did not report adapter identity state"],
|
|
121
|
-
},
|
|
122
|
-
}
|
|
123
|
-
: installDecision(requireNoLegacyState(identityState));
|
|
124
|
-
const installEligibility = legacyEligibility.kind === "blocked" || conflicts.length === 0
|
|
125
|
-
? legacyEligibility
|
|
126
|
-
: {
|
|
127
|
-
kind: "blocked",
|
|
128
|
-
output: {
|
|
129
|
-
stdout: [],
|
|
130
|
-
stderr: [
|
|
131
|
-
"Conflicting unmanaged Superpowers Codex resources require manual resolution:",
|
|
132
|
-
...conflicts.map((conflict) => `- ${conflict}`),
|
|
133
|
-
"Remove or disable each resource manually, then retry.",
|
|
134
|
-
],
|
|
135
|
-
},
|
|
136
|
-
};
|
|
137
|
-
const verification = verifyUninstalledResources(result);
|
|
138
|
-
const legacyReport = reportLegacyState(identityState);
|
|
139
|
-
let removalVerification;
|
|
140
|
-
if (!verification.ok) {
|
|
141
|
-
removalVerification = {
|
|
142
|
-
kind: "blocked",
|
|
143
|
-
output: { stdout: [], stderr: [`error: ${verification.message}`] },
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
else if (legacyReport.kind === "unknown") {
|
|
147
|
-
removalVerification = {
|
|
148
|
-
kind: "blocked",
|
|
149
|
-
output: { stdout: [], stderr: [`error: ${legacyReport.message}`] },
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
else if (legacyReport.kind === "blocked") {
|
|
153
|
-
removalVerification = {
|
|
154
|
-
kind: "blocked",
|
|
155
|
-
output: {
|
|
156
|
-
stdout: [],
|
|
157
|
-
stderr: ["error: unexpected legacy block after removal"],
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
removalVerification = { kind: "allowed" };
|
|
163
|
-
}
|
|
164
|
-
const postRemovalOutput = legacyReport.kind === "report"
|
|
165
|
-
? { stdout: legacyReport.lines, stderr: [] }
|
|
166
|
-
: { stdout: [], stderr: [] };
|
|
167
|
-
return successResult(result.outcome.operation, {
|
|
168
|
-
installEligibility,
|
|
169
|
-
removalInput: { pluginPresent, marketplacePresent },
|
|
170
|
-
removalVerification,
|
|
171
|
-
postRemovalOutput,
|
|
172
|
-
presentationValue: identityState,
|
|
173
|
-
presentationConflicts: conflicts,
|
|
174
|
-
}, result.outcome.messages);
|
|
175
|
-
}
|
|
176
|
-
export function normalizeCodexControl(result) {
|
|
177
|
-
if (!result.outcome.ok)
|
|
178
|
-
return preserveFailure(result);
|
|
179
|
-
if (result.status !== 0) {
|
|
180
|
-
return invalidStatus(result, "adapter reported a failure status for inspect --view update-control");
|
|
181
|
-
}
|
|
182
|
-
const record = resultRecord(result);
|
|
183
|
-
if (record === null) {
|
|
184
|
-
return malformed(result, "adapter returned a non-object result for inspect --view update-control");
|
|
185
|
-
}
|
|
186
|
-
const control = stringField(record, "update_control");
|
|
187
|
-
if (!control.ok) {
|
|
188
|
-
return malformed(result, "adapter returned a non-string update_control for inspect --view update-control");
|
|
189
|
-
}
|
|
190
|
-
const updateControl = control.value;
|
|
191
|
-
const probeEligibility = updateControl.length === 0
|
|
192
|
-
? {
|
|
193
|
-
kind: "blocked",
|
|
194
|
-
output: {
|
|
195
|
-
stdout: [],
|
|
196
|
-
stderr: [
|
|
197
|
-
"error: probe did not report adapter update-control capability",
|
|
198
|
-
],
|
|
199
|
-
},
|
|
200
|
-
}
|
|
201
|
-
: { kind: "allowed" };
|
|
202
|
-
const managed = requireManagedUpdateControl(updateControl);
|
|
203
|
-
const mutationEligibility = probeEligibility.kind === "blocked"
|
|
204
|
-
? probeEligibility
|
|
205
|
-
: managed.ok
|
|
206
|
-
? { kind: "allowed" }
|
|
207
|
-
: {
|
|
208
|
-
kind: "blocked",
|
|
209
|
-
output: {
|
|
210
|
-
stdout: [],
|
|
211
|
-
stderr: [`error: ${managed.message}`],
|
|
212
|
-
},
|
|
213
|
-
};
|
|
214
|
-
return successResult(result.outcome.operation, { probeEligibility, mutationEligibility, presentationValue: updateControl }, result.outcome.messages);
|
|
215
|
-
}
|
|
216
|
-
export function normalizeCodexInstalled(result, desiredCommit) {
|
|
217
|
-
if (!result.outcome.ok)
|
|
218
|
-
return preserveFailure(result);
|
|
219
|
-
if (result.status !== 0) {
|
|
220
|
-
return invalidStatus(result, "adapter reported a failure status for inspect --view fingerprint");
|
|
221
|
-
}
|
|
222
|
-
const record = resultRecord(result);
|
|
223
|
-
if (record === null) {
|
|
224
|
-
return malformed(result, "adapter returned a non-object result for inspect --view fingerprint");
|
|
225
|
-
}
|
|
226
|
-
const fingerprint = stringField(record, "fingerprint");
|
|
227
|
-
if (!fingerprint.ok) {
|
|
228
|
-
return malformed(result, "adapter returned a non-string fingerprint for inspect --view fingerprint");
|
|
229
|
-
}
|
|
230
|
-
const observedIdentity = fingerprint.value;
|
|
231
|
-
const installed = observedIdentity.length === 0
|
|
232
|
-
? { kind: "absent", observedIdentity: "" }
|
|
233
|
-
: commitMatches(desiredCommit, observedIdentity)
|
|
234
|
-
? { kind: "current", observedIdentity }
|
|
235
|
-
: { kind: "mismatch", observedIdentity };
|
|
236
|
-
return successResult(result.outcome.operation, installed, result.outcome.messages);
|
|
237
|
-
}
|
|
238
|
-
function safeHint(value) {
|
|
239
|
-
return typeof value === "string" && !hasTerminalControl(value) ? value : "";
|
|
240
|
-
}
|
|
241
|
-
export function normalizeCodexInstall(result) {
|
|
242
|
-
if (!result.outcome.ok)
|
|
243
|
-
return preserveFailure(result);
|
|
244
|
-
if (result.status !== 0) {
|
|
245
|
-
return invalidStatus(result, "adapter reported a failure status for install");
|
|
246
|
-
}
|
|
247
|
-
const record = resultRecord(result);
|
|
248
|
-
if (record === null) {
|
|
249
|
-
return malformed(result, "adapter returned a non-object result for install");
|
|
250
|
-
}
|
|
251
|
-
const hints = record.verification_hints;
|
|
252
|
-
const bag = typeof hints === "object" && hints !== null && !Array.isArray(hints)
|
|
253
|
-
? hints
|
|
254
|
-
: {};
|
|
255
|
-
return successResult(result.outcome.operation, codexInstallReceipt(safeHint(bag.missing), safeHint(bag.mismatch)), result.outcome.messages);
|
|
256
|
-
}
|
|
257
|
-
// The canonical payload normalizer has no context parameter. Keep the native
|
|
258
|
-
// call's defensive success/nonzero-status guard beside the concrete adapter,
|
|
259
|
-
// where the exact legacy diagnostic can still name ctx.root.
|
|
260
|
-
export function normalizeCodexInstallForContext(result, ctx) {
|
|
261
|
-
if (!result.outcome.ok)
|
|
262
|
-
return preserveFailure(result);
|
|
263
|
-
if (result.status !== 0) {
|
|
264
|
-
return invalidStatus(result, codexPresentation.callFailure("install", ctx).invalidStatus);
|
|
265
|
-
}
|
|
266
|
-
return normalizeCodexInstall(result);
|
|
267
|
-
}
|
|
268
20
|
function requirements(command, env) {
|
|
269
21
|
if (command !== "probe" &&
|
|
270
22
|
command !== "install" &&
|
|
@@ -286,9 +38,9 @@ async function mutationRoots(ctx) {
|
|
|
286
38
|
return [codexHome(ctx.env ?? {}, process.cwd())];
|
|
287
39
|
}
|
|
288
40
|
async function inspectCodexUpdateControl(ctx) {
|
|
289
|
-
const
|
|
290
|
-
if (!
|
|
291
|
-
return
|
|
41
|
+
const result = rejectSuccessfulNonzeroStatus(await codexInspectControl(ctx), "adapter reported a failure status for inspect --view update-control");
|
|
42
|
+
if (!result.outcome.ok)
|
|
43
|
+
return result;
|
|
292
44
|
const paths = codexPaths(ctx.env ?? {}, process.cwd());
|
|
293
45
|
let diagnostic = null;
|
|
294
46
|
try {
|
|
@@ -300,17 +52,17 @@ async function inspectCodexUpdateControl(ctx) {
|
|
|
300
52
|
diagnostic = `cannot inspect Codex recovery state at ${paths.recoveryRoot}`;
|
|
301
53
|
}
|
|
302
54
|
if (diagnostic === null)
|
|
303
|
-
return
|
|
55
|
+
return result;
|
|
304
56
|
const decision = {
|
|
305
57
|
kind: "blocked",
|
|
306
58
|
output: { stdout: [], stderr: [`error: ${diagnostic}`] },
|
|
307
59
|
};
|
|
308
|
-
return successResult(
|
|
60
|
+
return successResult(result.outcome.operation, {
|
|
309
61
|
probeEligibility: decision,
|
|
310
62
|
mutationEligibility: decision,
|
|
311
63
|
presentationValue: diagnostic,
|
|
312
64
|
recoveryState: "required",
|
|
313
|
-
},
|
|
65
|
+
}, result.outcome.messages);
|
|
314
66
|
}
|
|
315
67
|
export const codexHarness = {
|
|
316
68
|
preparationLocation: codexPreparationLocation,
|
|
@@ -319,16 +71,19 @@ export const codexHarness = {
|
|
|
319
71
|
prepareCandidate: prepareCodexCandidate,
|
|
320
72
|
inspectPrepared: inspectCodexPrepared,
|
|
321
73
|
readPrepared: readCodexPrepared,
|
|
322
|
-
inspectOwnership:
|
|
74
|
+
inspectOwnership: codexInspectOwnership,
|
|
323
75
|
inspectUpdateControl: inspectCodexUpdateControl,
|
|
324
76
|
inspectInstalled: inspectCodexInstallation,
|
|
325
|
-
install: async (artifact, ctx) => installCodexMarketplace(artifact, ctx, async (root, context) =>
|
|
77
|
+
install: async (artifact, ctx) => installCodexMarketplace(artifact, ctx, async (root, context) => {
|
|
78
|
+
const result = await codexInstall(root, context);
|
|
79
|
+
return rejectSuccessfulNonzeroStatus(result, codexPresentation.callFailure("install", context).invalidStatus);
|
|
80
|
+
}),
|
|
326
81
|
remove: async (input, ctx) => {
|
|
327
82
|
const result = await removeCodexMarketplace(input, ctx);
|
|
328
83
|
if (!result.outcome.ok)
|
|
329
84
|
return preserveFailure(result);
|
|
330
85
|
if (result.status !== 0) {
|
|
331
|
-
return
|
|
86
|
+
return rejectSuccessfulNonzeroStatus(result, codexPresentation.callFailure("remove", ctx, input).invalidStatus);
|
|
332
87
|
}
|
|
333
88
|
return successResult(result.outcome.operation, null, result.outcome.messages);
|
|
334
89
|
},
|
|
@@ -5,7 +5,7 @@ import { SafetyError } from "../../safety-error.js";
|
|
|
5
5
|
import { parseStrictJson } from "../../strict-json.js";
|
|
6
6
|
// Annotated, not `as const`, matching
|
|
7
7
|
// `src/provenance.ts:20::const PROVENANCE_STRICT_PROFILE` and
|
|
8
|
-
// `src/selection-store.ts:
|
|
8
|
+
// `src/selection-store.ts:15::const SELECTION_JSON_PROFILE`. The annotation
|
|
9
9
|
// rejects a misspelled or unsupported profile field here rather than at the
|
|
10
10
|
// parseStrictJson call.
|
|
11
11
|
const MANIFEST_PROFILE = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SafetyError } from "../../safety-error.js";
|
|
2
|
+
import { hasTerminalControl } from "../../adapter-result.js";
|
|
2
3
|
import { parseStrictJson, } from "../../strict-json.js";
|
|
3
4
|
const ACCEPT_CONSTANTS = {
|
|
4
5
|
duplicateKeys: "last-wins",
|
|
@@ -45,17 +46,6 @@ function checkedItems(raw, profile, arrayKey, field) {
|
|
|
45
46
|
return candidate;
|
|
46
47
|
});
|
|
47
48
|
}
|
|
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
49
|
export function installedListingHas(raw, arrayKey, field, value) {
|
|
60
50
|
return checkedItems(raw, ACCEPT_CONSTANTS, arrayKey, field).some((item) => item[field] === value);
|
|
61
51
|
}
|
|
@@ -52,84 +52,92 @@ export function reportLegacyState(identityState) {
|
|
|
52
52
|
}
|
|
53
53
|
return unknownState(identityState);
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
export function requireManagedUpdateControl(value) {
|
|
61
|
-
if (value === "managed")
|
|
62
|
-
return { ok: true };
|
|
63
|
-
if (value === "unsupported") {
|
|
64
|
-
return {
|
|
65
|
-
ok: false,
|
|
66
|
-
message: "adapter cannot guarantee manager-controlled updates",
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
return {
|
|
70
|
-
ok: false,
|
|
71
|
-
message: `unknown adapter update-control capability: ${value}`,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function readResult(adapterResult) {
|
|
75
|
-
if (adapterResult.status !== 0)
|
|
76
|
-
return { kind: "call-failed" };
|
|
77
|
-
const outcome = adapterResult.outcome;
|
|
78
|
-
if (!outcome.ok)
|
|
79
|
-
return { kind: "call-failed" };
|
|
80
|
-
const value = outcome.result;
|
|
81
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
82
|
-
return { kind: "unusable" };
|
|
83
|
-
}
|
|
84
|
-
return { kind: "object", value: value };
|
|
85
|
-
}
|
|
86
|
-
// Ported from
|
|
87
|
-
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/lifecycle.sh:126-141::spw_verify_uninstalled_resources`,
|
|
88
|
-
// with the Boolean coercion of
|
|
89
|
-
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/adapter.sh:58-73::spw_adapter_result_boolean`
|
|
90
|
-
// folded in: a non-Boolean is a hard failure, never a falsy "absent".
|
|
91
|
-
export function verifyUninstalledResources(inspectResult) {
|
|
92
|
-
const read = readResult(inspectResult);
|
|
93
|
-
if (read.kind !== "object") {
|
|
94
|
-
return {
|
|
95
|
-
ok: false,
|
|
96
|
-
message: "cannot read the adapter ownership inspection after removal",
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
const inspected = read.value;
|
|
100
|
-
// A missing or non-object `resources` falls THROUGH to the Boolean check
|
|
101
|
-
// rather than getting its own message.
|
|
102
|
-
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/adapter.sh:70::expected`
|
|
103
|
-
// emits "expected Boolean adapter result at resources.plugin" for input {} —
|
|
104
|
-
// the input tests/unit/harnesses/codex/lifecycle.test.ts's "a non-object resources falls
|
|
105
|
-
// through to the Boolean message" exercises — so a distinct "not an object"
|
|
106
|
-
// message here would be a port-only divergence. Parity, not divergence. Spec
|
|
107
|
-
// §6.2.3 item 3.
|
|
108
|
-
const resources = inspected.resources;
|
|
109
|
-
const bag = typeof resources === "object" &&
|
|
110
|
-
resources !== null &&
|
|
111
|
-
!Array.isArray(resources)
|
|
112
|
-
? resources
|
|
113
|
-
: {};
|
|
114
|
-
for (const key of ["plugin", "marketplace"]) {
|
|
115
|
-
if (typeof bag[key] !== "boolean") {
|
|
55
|
+
function installDecision(legacy) {
|
|
56
|
+
switch (legacy.kind) {
|
|
57
|
+
case "ok":
|
|
58
|
+
return { kind: "allowed" };
|
|
59
|
+
case "blocked":
|
|
116
60
|
return {
|
|
117
|
-
|
|
118
|
-
|
|
61
|
+
kind: "blocked",
|
|
62
|
+
output: { stdout: [], stderr: legacy.lines },
|
|
119
63
|
};
|
|
64
|
+
case "unknown":
|
|
65
|
+
return {
|
|
66
|
+
kind: "blocked",
|
|
67
|
+
output: { stdout: [], stderr: [`error: ${legacy.message}`] },
|
|
68
|
+
};
|
|
69
|
+
case "report":
|
|
70
|
+
return {
|
|
71
|
+
kind: "blocked",
|
|
72
|
+
output: {
|
|
73
|
+
stdout: [],
|
|
74
|
+
stderr: ["error: unexpected legacy report during installation"],
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export function codexOwnershipInspection(identityState, removalInput, conflicts) {
|
|
80
|
+
const { pluginPresent, marketplacePresent } = removalInput;
|
|
81
|
+
const legacyEligibility = identityState.length === 0
|
|
82
|
+
? {
|
|
83
|
+
kind: "blocked",
|
|
84
|
+
output: {
|
|
85
|
+
stdout: [],
|
|
86
|
+
stderr: ["error: probe did not report adapter identity state"],
|
|
87
|
+
},
|
|
120
88
|
}
|
|
89
|
+
: installDecision(requireNoLegacyState(identityState));
|
|
90
|
+
const installEligibility = legacyEligibility.kind === "blocked" || conflicts.length === 0
|
|
91
|
+
? legacyEligibility
|
|
92
|
+
: {
|
|
93
|
+
kind: "blocked",
|
|
94
|
+
output: {
|
|
95
|
+
stdout: [],
|
|
96
|
+
stderr: [
|
|
97
|
+
"Conflicting unmanaged Superpowers Codex resources require manual resolution:",
|
|
98
|
+
...conflicts.map((conflict) => `- ${conflict}`),
|
|
99
|
+
"Remove or disable each resource manually, then retry.",
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
const legacyReport = reportLegacyState(identityState);
|
|
104
|
+
let removalVerification;
|
|
105
|
+
if (pluginPresent || marketplacePresent) {
|
|
106
|
+
const message = pluginPresent
|
|
107
|
+
? "owned plugin resource is still installed after removal"
|
|
108
|
+
: "owned marketplace resource is still registered after removal";
|
|
109
|
+
removalVerification = {
|
|
110
|
+
kind: "blocked",
|
|
111
|
+
output: { stdout: [], stderr: ["error: " + message] },
|
|
112
|
+
};
|
|
121
113
|
}
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
114
|
+
else if (legacyReport.kind === "unknown") {
|
|
115
|
+
removalVerification = {
|
|
116
|
+
kind: "blocked",
|
|
117
|
+
output: { stdout: [], stderr: [`error: ${legacyReport.message}`] },
|
|
126
118
|
};
|
|
127
119
|
}
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
120
|
+
else if (legacyReport.kind === "blocked") {
|
|
121
|
+
removalVerification = {
|
|
122
|
+
kind: "blocked",
|
|
123
|
+
output: {
|
|
124
|
+
stdout: [],
|
|
125
|
+
stderr: ["error: unexpected legacy block after removal"],
|
|
126
|
+
},
|
|
132
127
|
};
|
|
133
128
|
}
|
|
134
|
-
|
|
129
|
+
else {
|
|
130
|
+
removalVerification = { kind: "allowed" };
|
|
131
|
+
}
|
|
132
|
+
const postRemovalOutput = legacyReport.kind === "report"
|
|
133
|
+
? { stdout: legacyReport.lines, stderr: [] }
|
|
134
|
+
: { stdout: [], stderr: [] };
|
|
135
|
+
return {
|
|
136
|
+
installEligibility,
|
|
137
|
+
removalInput,
|
|
138
|
+
removalVerification,
|
|
139
|
+
postRemovalOutput,
|
|
140
|
+
presentationValue: identityState,
|
|
141
|
+
presentationConflicts: conflicts,
|
|
142
|
+
};
|
|
135
143
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { escapeNonAscii
|
|
1
|
+
import { escapeNonAscii } from "../../python-json-format.js";
|
|
2
2
|
import { SafetyError } from "../../safety-error.js";
|
|
3
3
|
import { isRawNumber, isRawObject, parseStrictJsonPreservingNumbers, } from "../../strict-json.js";
|
|
4
4
|
const OVERLAY_PROFILE = {
|
|
@@ -9,14 +9,17 @@ const OVERLAY_PROFILE = {
|
|
|
9
9
|
/**
|
|
10
10
|
* Apply the manager's manifest overlay to a Codex plugin manifest.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `tests/baseline/
|
|
16
|
-
*
|
|
17
|
-
* the fixtures under `tests/fixtures/baseline/overlay-parity/`. Known,
|
|
18
|
-
* deliberate divergences from strict byte parity are recorded in
|
|
12
|
+
* Preserves validated JSON numeric source tokens while applying the manager's
|
|
13
|
+
* manifest fields, with a trailing newline. The baseline contract lives in
|
|
14
|
+
* `tests/baseline/harnesses/codex/manifest-overlay-parity.test.ts` against the
|
|
15
|
+
* fixtures under `tests/fixtures/baseline/overlay-parity/`. The 5,000-digit
|
|
16
|
+
* integer widening is recorded in
|
|
19
17
|
* `tests/fixtures/baseline/overlay-parity/divergent/README.md`.
|
|
18
|
+
*
|
|
19
|
+
* Decimal and exponent tokens are range-checked before emission. The strict
|
|
20
|
+
* parser rejects non-standard constants before they reach the emitter. Object
|
|
21
|
+
* member order, string escaping, and unknown fields remain part of this
|
|
22
|
+
* overlay contract.
|
|
20
23
|
*/
|
|
21
24
|
export function applyManifestOverlay(source, version, path) {
|
|
22
25
|
let parsed;
|
|
@@ -43,14 +46,10 @@ export function applyManifestOverlay(source, version, path) {
|
|
|
43
46
|
throw rewrapNumberOutOfRange(cause, path);
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
// with no indication of which manifest produced the diagnostic, unlike every
|
|
51
|
-
// other overlay failure. This rewrap adds the path without touching
|
|
52
|
-
// formatPythonNumber's own contract, and is scoped to exactly this message so
|
|
53
|
-
// it cannot swallow or reword any other overlay throw.
|
|
49
|
+
// emitNumber has no manifest path, so it throws a bare
|
|
50
|
+
// `JSON number out of range: ${raw}`. This rewrap adds the path without
|
|
51
|
+
// touching the local emitter's own contract, and is scoped to exactly this
|
|
52
|
+
// message so it cannot swallow or reword any other overlay throw.
|
|
54
53
|
const NUMBER_OUT_OF_RANGE_PREFIX = "JSON number out of range: ";
|
|
55
54
|
function rewrapNumberOutOfRange(cause, path) {
|
|
56
55
|
if (cause instanceof SafetyError &&
|
|
@@ -60,6 +59,12 @@ function rewrapNumberOutOfRange(cause, path) {
|
|
|
60
59
|
}
|
|
61
60
|
return cause;
|
|
62
61
|
}
|
|
62
|
+
function emitNumber(raw) {
|
|
63
|
+
if (/[.eE]/.test(raw) && !Number.isFinite(Number(raw))) {
|
|
64
|
+
throw new SafetyError("manifest-overlay", `JSON number out of range: ${raw}`);
|
|
65
|
+
}
|
|
66
|
+
return raw;
|
|
67
|
+
}
|
|
63
68
|
function setMember(entries, key, value) {
|
|
64
69
|
const index = entries.findIndex(([existing]) => existing === key);
|
|
65
70
|
if (index === -1)
|
|
@@ -69,7 +74,7 @@ function setMember(entries, key, value) {
|
|
|
69
74
|
}
|
|
70
75
|
/**
|
|
71
76
|
* Map strict-json's diagnostics onto the CPython wording the callers assert.
|
|
72
|
-
* Repository policy at `AGENTS.md:
|
|
77
|
+
* Repository policy at `AGENTS.md:89::most diagnostics are asserted as`
|
|
73
78
|
* freezes most of these diagnostics as complete-string contracts.
|
|
74
79
|
*/
|
|
75
80
|
function translate(cause, source, path) {
|
|
@@ -105,7 +110,7 @@ function emit(value, depth) {
|
|
|
105
110
|
// Order matters: isRawNumber before isRawObject, and both before Array —
|
|
106
111
|
// the branded records are objects too.
|
|
107
112
|
if (isRawNumber(value))
|
|
108
|
-
return
|
|
113
|
+
return emitNumber(value.source);
|
|
109
114
|
if (isRawObject(value))
|
|
110
115
|
return emitObject(value.entries, depth);
|
|
111
116
|
const inner = " ".repeat(depth + 1);
|