superpowers-manager 0.4.0 → 0.5.1
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 +76 -517
- package/dist/artifact-tree.js +111 -0
- package/dist/atomic.js +103 -7
- package/dist/cli-arguments.js +37 -0
- package/dist/cli.js +49 -12
- package/dist/commands/install.js +116 -17
- package/dist/commands/mutation.js +71 -0
- package/dist/commands/prepare.js +23 -17
- package/dist/commands/probe.js +164 -14
- package/dist/commands/uninstall.js +9 -5
- package/dist/commands/update.js +6 -2
- package/dist/domain/refs.js +4 -0
- package/dist/harness-compatibility.js +13 -0
- package/dist/{adapter.js → harnesses/codex/adapter.js} +17 -13
- package/dist/harnesses/codex/compatibility.js +126 -0
- package/dist/harnesses/codex/conflicts.js +99 -0
- package/dist/{generated-plugin.js → harnesses/codex/generated-plugin.js} +7 -73
- package/dist/{codex-harness.js → harnesses/codex/harness.js} +55 -5
- package/dist/{hooks.js → harnesses/codex/hooks.js} +6 -6
- package/dist/{codex-json.js → harnesses/codex/json.js} +9 -2
- package/dist/{lifecycle.js → harnesses/codex/lifecycle.js} +2 -2
- package/dist/{manifest-overlay.js → harnesses/codex/manifest-overlay.js} +5 -5
- package/dist/{codex-prepare.js → harnesses/codex/prepare.js} +72 -16
- package/dist/{codex-presentation.js → harnesses/codex/presentation.js} +27 -4
- package/dist/{codex-state.js → harnesses/codex/state.js} +3 -3
- package/dist/harnesses/pi/compatibility.js +73 -0
- package/dist/harnesses/pi/harness.js +63 -0
- package/dist/harnesses/pi/install.js +495 -0
- package/dist/harnesses/pi/native.js +81 -0
- package/dist/harnesses/pi/package.js +153 -0
- package/dist/harnesses/pi/paths.js +30 -0
- package/dist/harnesses/pi/prepare.js +111 -0
- package/dist/harnesses/pi/presentation.js +67 -0
- package/dist/harnesses/pi/settings.js +171 -0
- package/dist/harnesses/pi/shared-skills.js +413 -0
- package/dist/harnesses/pi/state.js +311 -0
- package/dist/python-text.js +16 -0
- package/dist/resource-lock.js +265 -0
- package/dist/safe-path.js +21 -5
- package/dist/skill-validation.js +52 -0
- package/dist/upstream-workspace.js +8 -0
- package/dist/validate-generated-plugin-cli.js +1 -1
- package/dist/validator.js +2 -1
- package/package.json +12 -8
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { normalizeCodexInstall, normalizeCodexInstalled
|
|
2
|
-
import { displaySource } from "
|
|
1
|
+
import { normalizeCodexInstall, normalizeCodexInstalled } from "./harness.js";
|
|
2
|
+
import { displaySource } from "../../selection.js";
|
|
3
3
|
// One ordered table drives both formats, preserving the public human and
|
|
4
4
|
// porcelain field order exposed through the harness presentation.
|
|
5
5
|
export function fields(f) {
|
|
6
6
|
return [
|
|
7
|
+
{ key: "harness", label: "harness", value: f.harness },
|
|
7
8
|
{ key: "requested_ref", label: "requested ref", value: f.requestedRef },
|
|
8
9
|
{ key: "resolved_ref", label: "resolved ref", value: f.resolvedRef },
|
|
9
10
|
{ key: "desired_commit", label: "desired commit", value: f.desiredCommit },
|
|
@@ -58,6 +59,7 @@ export function fields(f) {
|
|
|
58
59
|
];
|
|
59
60
|
}
|
|
60
61
|
const NO_FACTS = {
|
|
62
|
+
harness: "codex",
|
|
61
63
|
requestedRef: "",
|
|
62
64
|
resolvedRef: "",
|
|
63
65
|
desiredCommit: "",
|
|
@@ -114,7 +116,7 @@ export function codexInstallReceipt(missingHint, mismatchHint) {
|
|
|
114
116
|
mismatchVerificationOutput: verificationOutput("mismatch", mismatchHint),
|
|
115
117
|
};
|
|
116
118
|
}
|
|
117
|
-
// Compatibility export target for src/lifecycle.ts. Delegate to the production
|
|
119
|
+
// Compatibility export target for src/harnesses/codex/lifecycle.ts. Delegate to the production
|
|
118
120
|
// normalizers and renderer so the retained verdict cannot drift from commands.
|
|
119
121
|
export function verifyInstalledFingerprint(desiredCommit, installResult, inspectResult) {
|
|
120
122
|
const receipt = normalizeCodexInstall(installResult);
|
|
@@ -129,6 +131,7 @@ function probeFacts(facts) {
|
|
|
129
131
|
const selection = facts.selection;
|
|
130
132
|
const saved = selection.saved;
|
|
131
133
|
return {
|
|
134
|
+
harness: "codex",
|
|
132
135
|
requestedRef: selection.requestedRef,
|
|
133
136
|
resolvedRef: selection.resolvedRef,
|
|
134
137
|
desiredCommit: selection.desiredCommit,
|
|
@@ -205,7 +208,27 @@ export const codexPresentation = {
|
|
|
205
208
|
currentNotice: "manager is current",
|
|
206
209
|
renderProbe(snapshot) {
|
|
207
210
|
const facts = probeFacts(snapshot);
|
|
208
|
-
|
|
211
|
+
const conflicts = snapshot.ownership.presentationConflicts ?? [];
|
|
212
|
+
const additional = [
|
|
213
|
+
["installation_state", snapshot.installed.kind],
|
|
214
|
+
["resource_state", snapshot.resourceState ?? "idle"],
|
|
215
|
+
["compatibility", snapshot.compatibility.kind],
|
|
216
|
+
["compatibility_reason", snapshot.compatibility.reason],
|
|
217
|
+
];
|
|
218
|
+
return {
|
|
219
|
+
human: formatHuman(facts) +
|
|
220
|
+
additional
|
|
221
|
+
.map(([key, value]) => `${key.replaceAll("_", " ")}: ${value}\n`)
|
|
222
|
+
.join("") +
|
|
223
|
+
conflicts
|
|
224
|
+
.map((conflict) => `ownership conflict: ${conflict}\n`)
|
|
225
|
+
.join(""),
|
|
226
|
+
porcelain: formatPorcelain(facts) +
|
|
227
|
+
additional.map(([key, value]) => `${key}=${value}\n`).join("") +
|
|
228
|
+
conflicts
|
|
229
|
+
.map((conflict) => `ownership_conflict=${conflict}\n`)
|
|
230
|
+
.join(""),
|
|
231
|
+
};
|
|
209
232
|
},
|
|
210
233
|
renderInstallVerification(desiredCommit, receipt, inspection) {
|
|
211
234
|
if (inspection.status !== 0 || !inspection.outcome.ok) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFile, realpath } from "node:fs/promises";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
-
import { COMMIT_INPUT_RE } from "
|
|
4
|
-
import { SafetyError } from "
|
|
5
|
-
import { parseStrictJson, } from "
|
|
3
|
+
import { COMMIT_INPUT_RE } from "../../domain/refs.js";
|
|
4
|
+
import { SafetyError } from "../../safety-error.js";
|
|
5
|
+
import { parseStrictJson, } from "../../strict-json.js";
|
|
6
6
|
const INSTALLED_PROFILE = {
|
|
7
7
|
duplicateKeys: "last-wins",
|
|
8
8
|
nonStandardConstants: "reject",
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstat } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { readArtifactFile, readArtifactObject, validateNativeSkill, } from "../../artifact-tree.js";
|
|
5
|
+
import { SEMVER_RE } from "../../domain/refs.js";
|
|
6
|
+
import { validateSource } from "../../selection.js";
|
|
7
|
+
const OFFICIAL_SOURCES = new Set([
|
|
8
|
+
"https://github.com/obra/superpowers",
|
|
9
|
+
"https://github.com/obra/superpowers.git",
|
|
10
|
+
"ssh://git@github.com/obra/superpowers.git",
|
|
11
|
+
"git@github.com:obra/superpowers.git",
|
|
12
|
+
]);
|
|
13
|
+
export function samePiSource(left, right) {
|
|
14
|
+
validateSource(left);
|
|
15
|
+
validateSource(right);
|
|
16
|
+
return (left === right ||
|
|
17
|
+
(OFFICIAL_SOURCES.has(left) && OFFICIAL_SOURCES.has(right)));
|
|
18
|
+
}
|
|
19
|
+
export async function assessPiCompatibility(root, selection) {
|
|
20
|
+
try {
|
|
21
|
+
validateSource(selection.effectiveSource);
|
|
22
|
+
const official = OFFICIAL_SOURCES.has(selection.effectiveSource);
|
|
23
|
+
const pkg = await readArtifactObject(root, join(root, "package.json"));
|
|
24
|
+
if (pkg.name !== "superpowers" ||
|
|
25
|
+
pkg.type !== "module" ||
|
|
26
|
+
typeof pkg.version !== "string" ||
|
|
27
|
+
!SEMVER_RE.test(pkg.version))
|
|
28
|
+
throw new Error("package metadata");
|
|
29
|
+
const pi = pkg.pi;
|
|
30
|
+
if (!pi ||
|
|
31
|
+
typeof pi !== "object" ||
|
|
32
|
+
Array.isArray(pi) ||
|
|
33
|
+
Object.keys(pi).length !== 2 ||
|
|
34
|
+
JSON.stringify(pi.extensions) !== '["./.pi/extensions/superpowers.ts"]' ||
|
|
35
|
+
JSON.stringify(pi.skills) !== '["./skills"]')
|
|
36
|
+
throw new Error("resource declaration");
|
|
37
|
+
for (const key of [
|
|
38
|
+
"dependencies",
|
|
39
|
+
"optionalDependencies",
|
|
40
|
+
"peerDependencies",
|
|
41
|
+
"bundleDependencies",
|
|
42
|
+
"bundledDependencies",
|
|
43
|
+
]) {
|
|
44
|
+
const value = pkg[key];
|
|
45
|
+
if (value !== undefined &&
|
|
46
|
+
(value === null ||
|
|
47
|
+
typeof value !== "object" ||
|
|
48
|
+
Object.keys(value).length !== 0))
|
|
49
|
+
throw new Error("runtime dependencies");
|
|
50
|
+
}
|
|
51
|
+
await validateNativeSkill(root);
|
|
52
|
+
const bootstrap = join(root, ".pi/extensions/superpowers.ts");
|
|
53
|
+
const bytes = await readArtifactFile(root, bootstrap, 4283);
|
|
54
|
+
if ((await lstat(bootstrap)).mode & 0o111 ||
|
|
55
|
+
bytes.length !== 4283 ||
|
|
56
|
+
createHash("sha256").update(bytes).digest("hex") !==
|
|
57
|
+
"39c27000c047f8a1399a76e032e4cd239d7bdb0be168a121f46a2ac719deaae0")
|
|
58
|
+
throw new Error("bootstrap implementation");
|
|
59
|
+
return {
|
|
60
|
+
kind: official ? "supported" : "experimental",
|
|
61
|
+
generation: "pi-native-bootstrap-v1",
|
|
62
|
+
reason: official
|
|
63
|
+
? "qualified upstream-native Pi bootstrap"
|
|
64
|
+
: "qualified Pi mechanics from a custom source",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return {
|
|
69
|
+
kind: "unsupported",
|
|
70
|
+
reason: `Pi package does not match the qualified native bootstrap profile: ${root}`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { failureResult, successResult } from "../../adapter-result.js";
|
|
2
|
+
import { classifyPathNoFollow } from "../../safe-path.js";
|
|
3
|
+
import { installPi, removePi } from "./install.js";
|
|
4
|
+
import { piPaths } from "./paths.js";
|
|
5
|
+
import { inspectPiPrepared, piPreparationLocation, preparePiCandidate, readPiPrepared, } from "./prepare.js";
|
|
6
|
+
import { piPresentation } from "./presentation.js";
|
|
7
|
+
import { inspectPiControl, inspectPiInstalled, inspectPiOwnership, } from "./state.js";
|
|
8
|
+
export const piHarness = {
|
|
9
|
+
preparationLocation: piPreparationLocation,
|
|
10
|
+
mutationRoots: async (ctx) => [
|
|
11
|
+
piPaths(ctx.env ?? {}, process.cwd()).agentDir,
|
|
12
|
+
],
|
|
13
|
+
validatePreparationBeforeFetch: async () => successResult("validate-pi-preparation", null, []),
|
|
14
|
+
prepareCandidate: preparePiCandidate,
|
|
15
|
+
inspectPrepared: inspectPiPrepared,
|
|
16
|
+
readPrepared: readPiPrepared,
|
|
17
|
+
inspectOwnership: inspectPiOwnership,
|
|
18
|
+
async inspectUpdateControl(ctx) {
|
|
19
|
+
const paths = piPaths(ctx.env ?? {}, process.cwd());
|
|
20
|
+
try {
|
|
21
|
+
if ((await classifyPathNoFollow(paths.recoveryRoot)) !== "missing") {
|
|
22
|
+
const decision = {
|
|
23
|
+
kind: "blocked",
|
|
24
|
+
output: {
|
|
25
|
+
stdout: [],
|
|
26
|
+
stderr: [
|
|
27
|
+
`error: Pi recovery required; preserve material at ${paths.recoveryRoot} for manual resolution`,
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
return successResult("inspect-pi-control", {
|
|
32
|
+
probeEligibility: decision,
|
|
33
|
+
mutationEligibility: decision,
|
|
34
|
+
presentationValue: `recovery required at ${paths.recoveryRoot}`,
|
|
35
|
+
recoveryState: "required",
|
|
36
|
+
}, []);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return failureResult("inspect-pi-control", "invalid-state", `cannot inspect Pi recovery state at ${paths.recoveryRoot}`, [], []);
|
|
41
|
+
}
|
|
42
|
+
return await inspectPiControl(ctx);
|
|
43
|
+
},
|
|
44
|
+
inspectInstalled: inspectPiInstalled,
|
|
45
|
+
install: installPi,
|
|
46
|
+
remove: removePi,
|
|
47
|
+
requirements(command, env) {
|
|
48
|
+
if (command !== "install" &&
|
|
49
|
+
command !== "update" &&
|
|
50
|
+
command !== "uninstall")
|
|
51
|
+
return [];
|
|
52
|
+
const executable = env.SUPERPOWERS_PI || "pi";
|
|
53
|
+
return [
|
|
54
|
+
{
|
|
55
|
+
name: "pi",
|
|
56
|
+
executable,
|
|
57
|
+
lookup: "explicit-path-or-path",
|
|
58
|
+
missingMessage: `required command not found: ${executable} — install the Pi CLI or set SUPERPOWERS_PI`,
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
},
|
|
62
|
+
presentation: piPresentation,
|
|
63
|
+
};
|