superpowers-manager 0.7.3 → 0.9.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 +36 -16
- package/dist/atomic.js +1 -3
- package/dist/cli-arguments.js +4 -1
- package/dist/cli.js +14 -43
- package/dist/commands/adapter-call.js +24 -0
- package/dist/commands/install.js +16 -63
- package/dist/commands/mutation.js +5 -5
- package/dist/commands/pin.js +10 -5
- package/dist/commands/prepare.js +42 -108
- package/dist/commands/probe.js +33 -19
- package/dist/commands/uninstall.js +18 -46
- package/dist/commands/unpin.js +2 -1
- package/dist/commands/update.js +8 -29
- package/dist/domain/refs.js +0 -3
- package/dist/effective-selection.js +11 -15
- package/dist/git-tree.js +4 -6
- package/dist/harness-command-result.js +36 -5
- package/dist/harnesses/claude-code/harness.js +39 -0
- package/dist/harnesses/claude-code/install.js +380 -0
- package/dist/harnesses/claude-code/native.js +79 -0
- package/dist/harnesses/claude-code/paths.js +35 -0
- package/dist/harnesses/claude-code/prepare.js +117 -0
- package/dist/harnesses/claude-code/presentation.js +25 -0
- package/dist/harnesses/claude-code/state.js +253 -0
- package/dist/harnesses/codex/adapter.js +27 -68
- package/dist/harnesses/codex/conflicts.js +3 -8
- package/dist/harnesses/codex/generated-plugin.js +4 -11
- package/dist/harnesses/codex/harness.js +4 -26
- package/dist/harnesses/codex/hooks.js +11 -27
- package/dist/harnesses/codex/lifecycle.js +22 -107
- package/dist/harnesses/codex/manifest-overlay.js +9 -27
- package/dist/harnesses/codex/paths.js +29 -0
- package/dist/harnesses/codex/prepare.js +9 -43
- package/dist/harnesses/codex/presentation.js +1 -1
- package/dist/harnesses/codex/publication.js +5 -40
- package/dist/harnesses/codex/state.js +1 -6
- package/dist/harnesses/codex/stored-state.js +1 -6
- package/dist/harnesses/opencode/config.js +105 -50
- package/dist/harnesses/opencode/discovery.js +11 -15
- package/dist/harnesses/opencode/install.js +57 -102
- package/dist/harnesses/opencode/native.js +28 -49
- package/dist/harnesses/opencode/package.js +45 -67
- package/dist/harnesses/opencode/prepare.js +2 -7
- package/dist/harnesses/opencode/presentation.js +3 -7
- package/dist/harnesses/opencode/state.js +1 -0
- package/dist/harnesses/pi/install.js +31 -120
- package/dist/harnesses/pi/native.js +9 -34
- package/dist/harnesses/pi/package.js +9 -50
- package/dist/harnesses/pi/prepare.js +3 -8
- package/dist/harnesses/pi/presentation.js +2 -7
- package/dist/harnesses/pi/settings.js +5 -9
- package/dist/harnesses/pi/shared-skills.js +23 -53
- package/dist/harnesses/pi/state.js +3 -10
- package/dist/provenance.js +3 -19
- package/dist/resource-lock.js +11 -16
- package/dist/safe-path.js +28 -1
- package/dist/selection.js +15 -19
- package/dist/snapshot-journal.js +101 -0
- package/dist/snapshot-package.js +62 -1
- package/dist/snapshot-prepare.js +9 -2
- package/dist/snapshot-presentation.js +9 -0
- package/dist/upstream.js +8 -12
- package/dist/validator.js +26 -23
- package/dist/workspace.js +29 -35
- package/package.json +10 -9
- package/dist/python-json.js +0 -4
- package/dist/upstream-workspace.js +0 -8
package/dist/snapshot-package.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { lstat } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { addArtifactHashField, digestArtifactTree, readArtifactFile, } from "./artifact-tree.js";
|
|
4
|
+
import { ARTIFACT_DIGEST_RE, ARTIFACT_RECEIPT, addArtifactHashField, digestArtifactTree, readArtifactFile, readArtifactObject, } from "./artifact-tree.js";
|
|
5
|
+
import { COMMIT_RE } from "./domain/refs.js";
|
|
5
6
|
import { classifyPathNoFollow } from "./safe-path.js";
|
|
7
|
+
import { SafetyError } from "./safety-error.js";
|
|
6
8
|
import { validateSource } from "./selection.js";
|
|
7
9
|
const OFFICIAL_SOURCES = new Set([
|
|
8
10
|
"https://github.com/obra/superpowers",
|
|
@@ -76,3 +78,62 @@ export async function requireSnapshotBootstrap(root, relativePath, expectedSize,
|
|
|
76
78
|
createHash("sha256").update(bytes).digest("hex") !== expectedDigest)
|
|
77
79
|
throw new Error("bootstrap implementation");
|
|
78
80
|
}
|
|
81
|
+
// strictGeneration=false admits a generation on unknown/unsupported
|
|
82
|
+
// compatibility. Only Pi passes false: its readers keep that recorded policy
|
|
83
|
+
// (tests/unit/harnesses/pi/package.test.ts, "receipt readers retain their
|
|
84
|
+
// distinct unsupported-generation policy").
|
|
85
|
+
function isReceiptCompatibility(value, strictGeneration) {
|
|
86
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
87
|
+
return false;
|
|
88
|
+
const compatibility = value;
|
|
89
|
+
if (typeof compatibility.reason !== "string")
|
|
90
|
+
return false;
|
|
91
|
+
if (compatibility.kind === "supported" ||
|
|
92
|
+
compatibility.kind === "experimental")
|
|
93
|
+
return typeof compatibility.generation === "string";
|
|
94
|
+
return ((compatibility.kind === "unknown" ||
|
|
95
|
+
compatibility.kind === "unsupported") &&
|
|
96
|
+
(!strictGeneration || compatibility.generation === undefined));
|
|
97
|
+
}
|
|
98
|
+
export function createSnapshotReceipts(rules) {
|
|
99
|
+
const module = `${rules.harness}-package`;
|
|
100
|
+
async function readReceipt(root) {
|
|
101
|
+
const path = join(root, ARTIFACT_RECEIPT);
|
|
102
|
+
try {
|
|
103
|
+
const value = await readArtifactObject(root, path);
|
|
104
|
+
if (value.schema !== 1 ||
|
|
105
|
+
value.manager !== "superpowers-manager" ||
|
|
106
|
+
value.harness !== rules.harness ||
|
|
107
|
+
typeof value.source !== "string" ||
|
|
108
|
+
typeof value.commit !== "string" ||
|
|
109
|
+
!COMMIT_RE.test(value.commit) ||
|
|
110
|
+
typeof value.digest !== "string" ||
|
|
111
|
+
!ARTIFACT_DIGEST_RE.test(value.digest) ||
|
|
112
|
+
typeof value.binding !== "string" ||
|
|
113
|
+
!ARTIFACT_DIGEST_RE.test(value.binding))
|
|
114
|
+
throw new Error("receipt fields");
|
|
115
|
+
validateSource(value.source);
|
|
116
|
+
if (snapshotReceiptBinding(value) !==
|
|
117
|
+
value.binding)
|
|
118
|
+
throw new Error("receipt binding");
|
|
119
|
+
if (!isReceiptCompatibility(value.compatibility, rules.strictGeneration))
|
|
120
|
+
throw new Error("receipt compatibility");
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
catch (cause) {
|
|
124
|
+
throw new SafetyError(module, `invalid ${rules.label} artifact receipt: ${path}`, { cause });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function readAssessment(root) {
|
|
128
|
+
const receipt = await readReceipt(root);
|
|
129
|
+
if ((await digestArtifactTree(root)) !== receipt.digest)
|
|
130
|
+
throw new SafetyError(module, `${rules.label} artifact digest mismatch: ${root}`);
|
|
131
|
+
return {
|
|
132
|
+
receipt,
|
|
133
|
+
compatibility: await rules.assessCompatibility(root, {
|
|
134
|
+
effectiveSource: receipt.source,
|
|
135
|
+
}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return { readReceipt, readAssessment };
|
|
139
|
+
}
|
package/dist/snapshot-prepare.js
CHANGED
|
@@ -6,6 +6,10 @@ import { materializeGitTree } from "./git-tree.js";
|
|
|
6
6
|
import { classifyPathNoFollow } from "./safe-path.js";
|
|
7
7
|
import { sameSnapshotSource, snapshotReceiptBinding, } from "./snapshot-package.js";
|
|
8
8
|
export function createSnapshotPreparation(b) {
|
|
9
|
+
const preparationLocation = (ctx) => ({
|
|
10
|
+
destinationRoot: b.paths(ctx.env ?? {}, process.cwd()).preparedRoot,
|
|
11
|
+
stagingLeaf: "superpowers",
|
|
12
|
+
});
|
|
9
13
|
const artifactFrom = (root, a) => ({
|
|
10
14
|
root,
|
|
11
15
|
commit: a.receipt.commit,
|
|
@@ -13,6 +17,7 @@ export function createSnapshotPreparation(b) {
|
|
|
13
17
|
compatibility: a.compatibility,
|
|
14
18
|
});
|
|
15
19
|
return {
|
|
20
|
+
preparationLocation,
|
|
16
21
|
async prepareCandidate(input) {
|
|
17
22
|
try {
|
|
18
23
|
await materializeGitTree(input.upstreamRoot, input.selection.desiredCommit, input.candidateRoot);
|
|
@@ -20,6 +25,8 @@ export function createSnapshotPreparation(b) {
|
|
|
20
25
|
if (compatibility.kind === "unsupported" ||
|
|
21
26
|
compatibility.kind === "unknown")
|
|
22
27
|
return failureResult("prepare", "unsupported", compatibility.reason, [], []);
|
|
28
|
+
if (b.rewrite !== undefined)
|
|
29
|
+
await b.rewrite(input.candidateRoot, input.selection);
|
|
23
30
|
const digest = await digestArtifactTree(input.candidateRoot);
|
|
24
31
|
const identity = {
|
|
25
32
|
schema: 1,
|
|
@@ -42,7 +49,7 @@ export function createSnapshotPreparation(b) {
|
|
|
42
49
|
}
|
|
43
50
|
},
|
|
44
51
|
async inspectPrepared(selection, ctx) {
|
|
45
|
-
const root =
|
|
52
|
+
const root = preparationLocation(ctx).destinationRoot;
|
|
46
53
|
const unknown = {
|
|
47
54
|
kind: "unknown",
|
|
48
55
|
reason: `${b.label} prepared compatibility evidence is missing`,
|
|
@@ -83,7 +90,7 @@ export function createSnapshotPreparation(b) {
|
|
|
83
90
|
}
|
|
84
91
|
},
|
|
85
92
|
async readPrepared(ctx) {
|
|
86
|
-
const root =
|
|
93
|
+
const root = preparationLocation(ctx).destinationRoot;
|
|
87
94
|
try {
|
|
88
95
|
const assessment = await b.readAssessment(root);
|
|
89
96
|
if (assessment.compatibility.kind !== "supported" &&
|
|
@@ -42,3 +42,12 @@ export function createSnapshotPresentation(label, renderProbe, wasAbsent) {
|
|
|
42
42
|
},
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
+
// Each harness chooses its fields and applies its own escaping before calling.
|
|
46
|
+
export function probeOutput(fields) {
|
|
47
|
+
return {
|
|
48
|
+
human: fields
|
|
49
|
+
.map(([key, value]) => `${key.replaceAll("_", " ")}: ${value || "not present"}\n`)
|
|
50
|
+
.join(""),
|
|
51
|
+
porcelain: fields.map(([key, value]) => `${key}=${value}\n`).join(""),
|
|
52
|
+
};
|
|
53
|
+
}
|
package/dist/upstream.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { readFile
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
3
|
import { COMMIT_INPUT_RE, compareStable, parseStableTag, } from "./domain/refs.js";
|
|
4
4
|
import { runGit } from "./git.js";
|
|
5
5
|
import { SafetyError } from "./safety-error.js";
|
|
6
|
+
import { isDirectory } from "./safe-path.js";
|
|
6
7
|
import { displaySource } from "./selection.js";
|
|
7
8
|
import { withWorkspace } from "./workspace.js";
|
|
8
9
|
const UNAVAILABLE_OBJECT_RE = /not our ref|unadvertised object|couldn't find remote ref/;
|
|
@@ -35,16 +36,6 @@ function failed(result) {
|
|
|
35
36
|
function combined(result) {
|
|
36
37
|
return `${result.stdout}${result.stderr}`.replace(/\n+$/, "");
|
|
37
38
|
}
|
|
38
|
-
// Mirrors `[ -d "$repository/.git" ]`: a regular file, a broken symlink, or
|
|
39
|
-
// an unreadable path is *not* a directory, so the cache is re-initialized.
|
|
40
|
-
async function isDirectory(path) {
|
|
41
|
-
try {
|
|
42
|
-
return (await stat(path)).isDirectory();
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
39
|
export function parseLsRemote(output) {
|
|
49
40
|
const entries = [];
|
|
50
41
|
for (const line of output.split("\n")) {
|
|
@@ -251,3 +242,8 @@ export async function fetchExactCommit(source, commit, repository, workspacePare
|
|
|
251
242
|
}
|
|
252
243
|
});
|
|
253
244
|
}
|
|
245
|
+
// An explicit relative SUPERPOWERS_CACHE_DIR resolves against the
|
|
246
|
+
// invocation's working directory.
|
|
247
|
+
export function upstreamCacheRoot(root, env) {
|
|
248
|
+
return resolve(env.SUPERPOWERS_CACHE_DIR || join(root, ".cache", "upstream"), "superpowers");
|
|
249
|
+
}
|
package/dist/validator.js
CHANGED
|
@@ -198,14 +198,21 @@ export async function resolveValidator(configured) {
|
|
|
198
198
|
// runs whatever PATH selects -- the same divergence that makes realpath unsafe
|
|
199
199
|
// here.
|
|
200
200
|
const pathLike = configured.includes(sep);
|
|
201
|
+
if (!pathLike) {
|
|
202
|
+
return {
|
|
203
|
+
configured,
|
|
204
|
+
resolved: null,
|
|
205
|
+
isSymlink: false,
|
|
206
|
+
isDirectory: false,
|
|
207
|
+
exists: false,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
201
210
|
let isSymlink = false;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
/* spawn reports the real reason */
|
|
208
|
-
}
|
|
211
|
+
try {
|
|
212
|
+
isSymlink = (await lstat(configured)).isSymbolicLink();
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
/* spawn reports the real reason */
|
|
209
216
|
}
|
|
210
217
|
// Resolve ONLY when the configured value contains a path separator -- which is
|
|
211
218
|
// exactly when spawn bypasses PATH and the two agree about the target. For a
|
|
@@ -214,25 +221,21 @@ export async function resolveValidator(configured) {
|
|
|
214
221
|
// interpreter and spawn("node") ran another. Resolving a bare name would let the
|
|
215
222
|
// manager confidently disclose a file it is not about to run.
|
|
216
223
|
let resolved = null;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
/* unresolvable; stays null and is never claimed */
|
|
223
|
-
}
|
|
224
|
+
try {
|
|
225
|
+
resolved = await realpath(configured);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
/* unresolvable; stays null and is never claimed */
|
|
224
229
|
}
|
|
225
230
|
let isDirectory = false;
|
|
226
231
|
let exists = false;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
/* spawn reports the real reason */
|
|
235
|
-
}
|
|
232
|
+
try {
|
|
233
|
+
const s = await stat(configured);
|
|
234
|
+
isDirectory = s.isDirectory();
|
|
235
|
+
exists = true;
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
/* spawn reports the real reason */
|
|
236
239
|
}
|
|
237
240
|
return { configured, resolved, isSymlink, isDirectory, exists };
|
|
238
241
|
}
|
package/dist/workspace.js
CHANGED
|
@@ -3,17 +3,11 @@ import { mkdtemp, rm } from "node:fs/promises";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { SafetyError } from "./safety-error.js";
|
|
5
5
|
const MANAGED_SIGNALS = ["SIGHUP", "SIGINT", "SIGTERM"];
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
// diagnostic straight to process.stderr, unconditionally; that raw write is
|
|
12
|
-
// the only thing actually visible to a caller. Previously the signal path
|
|
13
|
-
// called the module-private cleanup and swallowed the rejection in
|
|
14
|
-
// Promise.allSettled, so a signal-time failure was silent entirely — this is
|
|
15
|
-
// the state PR 11.4 removed from the normal path.
|
|
16
|
-
const active = new Map();
|
|
6
|
+
// Workspaces the signal path must remove. A signal death never builds a
|
|
7
|
+
// result outcome (see src/adapter-result.ts), so the signal path writes its
|
|
8
|
+
// hand-written diagnostic straight to process.stderr; that write is the only
|
|
9
|
+
// report a caller can observe.
|
|
10
|
+
const active = new Set();
|
|
17
11
|
let exiting = false;
|
|
18
12
|
const handlers = new Map();
|
|
19
13
|
// One phrasing for one condition. The adapter re-emits this for a suppressed
|
|
@@ -40,29 +34,18 @@ function cleanupForSignal(signal) {
|
|
|
40
34
|
if (exiting)
|
|
41
35
|
return;
|
|
42
36
|
exiting = true;
|
|
43
|
-
for (const
|
|
37
|
+
for (const path of active) {
|
|
44
38
|
try {
|
|
45
39
|
rmSync(path, { recursive: true, force: true });
|
|
46
40
|
}
|
|
47
41
|
catch {
|
|
48
|
-
// The reporter is invoked for parity with the normal path, but nothing
|
|
49
|
-
// ever reads it back here — no outcome is built before the process
|
|
50
|
-
// dies — so the diagnostic is also written straight to stderr,
|
|
51
|
-
// unconditionally; that write is the only thing actually observable.
|
|
52
42
|
// The diagnostic is hand-written and names the workspace; the caught
|
|
53
|
-
// cause is not interpolated.
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
// workspaces' cleanup, deregistration, and the re-raise below never run.
|
|
43
|
+
// cause is not interpolated. Guarded: if the write throws — plausibly
|
|
44
|
+
// EPIPE after SIGHUP — the remaining workspaces' cleanup,
|
|
45
|
+
// deregistration, and the re-raise below must still run. Node documents
|
|
46
|
+
// pipe writes as asynchronous on some platforms (notably macOS); small
|
|
47
|
+
// buffers like this one are written inline.
|
|
59
48
|
try {
|
|
60
|
-
if (report)
|
|
61
|
-
report(path);
|
|
62
|
-
// Node documents pipe writes as asynchronous on some platforms
|
|
63
|
-
// (notably macOS); a saturated pipe could drop this write. Small
|
|
64
|
-
// buffers like this one are written inline, which is why the
|
|
65
|
-
// covering test observes it reliably.
|
|
66
49
|
process.stderr.write(`${workspaceRemovalFailure(path)}\n`);
|
|
67
50
|
}
|
|
68
51
|
catch {
|
|
@@ -96,7 +79,7 @@ function deregisterCoordinator() {
|
|
|
96
79
|
process.off(signal, handler);
|
|
97
80
|
handlers.clear();
|
|
98
81
|
}
|
|
99
|
-
|
|
82
|
+
async function runInWorkspace(parent, prefix, fn, options, reportCleanupFailure) {
|
|
100
83
|
const remove = options.cleanup ?? cleanup;
|
|
101
84
|
let workspace;
|
|
102
85
|
try {
|
|
@@ -105,35 +88,46 @@ export async function withWorkspace(parent, prefix, fn, options = {}) {
|
|
|
105
88
|
catch (cause) {
|
|
106
89
|
throw new SafetyError("workspace", "cannot create workspace", { cause });
|
|
107
90
|
}
|
|
108
|
-
active.
|
|
91
|
+
active.add(workspace);
|
|
109
92
|
registerCoordinator();
|
|
110
93
|
try {
|
|
111
|
-
let
|
|
94
|
+
let value;
|
|
112
95
|
let failed = false;
|
|
113
96
|
let callbackError;
|
|
114
97
|
try {
|
|
115
|
-
|
|
98
|
+
value = await fn(workspace);
|
|
116
99
|
}
|
|
117
100
|
catch (error) {
|
|
118
101
|
failed = true;
|
|
119
102
|
callbackError = error;
|
|
120
103
|
}
|
|
104
|
+
let cleanupWarning = null;
|
|
121
105
|
try {
|
|
122
106
|
await remove(workspace);
|
|
123
107
|
}
|
|
124
108
|
catch (cleanupError) {
|
|
125
109
|
if (failed)
|
|
126
110
|
throw callbackError;
|
|
127
|
-
if (
|
|
111
|
+
if (!reportCleanupFailure)
|
|
128
112
|
throw cleanupError;
|
|
129
|
-
|
|
113
|
+
cleanupWarning = workspaceRemovalFailure(workspace);
|
|
130
114
|
}
|
|
131
115
|
if (failed)
|
|
132
116
|
throw callbackError;
|
|
133
|
-
return
|
|
117
|
+
return { value, cleanupWarning };
|
|
134
118
|
}
|
|
135
119
|
finally {
|
|
136
120
|
active.delete(workspace);
|
|
137
121
|
deregisterCoordinator();
|
|
138
122
|
}
|
|
139
123
|
}
|
|
124
|
+
export async function withWorkspace(parent, prefix, fn, options = {}) {
|
|
125
|
+
return (await runInWorkspace(parent, prefix, fn, options, false)).value;
|
|
126
|
+
}
|
|
127
|
+
// Like withWorkspace, except a cleanup failure after a SUCCESSFUL callback is
|
|
128
|
+
// returned as `cleanupWarning` instead of thrown, so the caller keeps the value
|
|
129
|
+
// it already computed and reports the leak itself. A callback throw still
|
|
130
|
+
// propagates, and a cleanup failure never masks it.
|
|
131
|
+
export function withWorkspaceReporting(parent, prefix, fn, options = {}) {
|
|
132
|
+
return runInWorkspace(parent, prefix, fn, options, true);
|
|
133
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superpowers-manager",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Install obra/superpowers directly from upstream for Codex, Pi, and
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Install obra/superpowers directly from upstream for Codex, Pi, OpenCode, and Claude Code. Stay on a known-good release or commit, try a branch, and upgrade when you choose.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"superpowers",
|
|
7
7
|
"obra-superpowers",
|
|
@@ -18,21 +18,22 @@
|
|
|
18
18
|
"pi",
|
|
19
19
|
"pi-coding-agent",
|
|
20
20
|
"opencode",
|
|
21
|
+
"claude-code",
|
|
22
|
+
"claude-code-plugin",
|
|
21
23
|
"version-pinning"
|
|
22
24
|
],
|
|
23
25
|
"license": "MIT",
|
|
24
26
|
"type": "module",
|
|
25
|
-
"packageManager": "pnpm@12.4.
|
|
27
|
+
"packageManager": "pnpm@12.4.2",
|
|
26
28
|
"scripts": {
|
|
27
|
-
"lint": "oxlint src/ tests/
|
|
28
|
-
"format": "prettier --write src/ tests/
|
|
29
|
-
"format:check": "prettier --check src/ tests/
|
|
30
|
-
"test": "
|
|
31
|
-
"test:unit": "node tests/run-node-suites.ts --group unit",
|
|
32
|
-
"test:integration": "node tests/run-node-suites.ts --group integration",
|
|
29
|
+
"lint": "oxlint src/ tests/",
|
|
30
|
+
"format": "prettier --write src/ tests/",
|
|
31
|
+
"format:check": "prettier --check src/ tests/",
|
|
32
|
+
"test": "sh tests/run.sh",
|
|
33
33
|
"test:harness:codex": "sh tests/container.sh harness-codex",
|
|
34
34
|
"test:harness:pi": "sh tests/container.sh harness-pi",
|
|
35
35
|
"test:harness:opencode": "sh tests/container.sh harness-opencode",
|
|
36
|
+
"test:harness:claude-code": "sh tests/container.sh harness-claude-code",
|
|
36
37
|
"test:acceptance": "sh tests/acceptance.sh",
|
|
37
38
|
"check:static": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run knip",
|
|
38
39
|
"check": "pnpm run check:static && pnpm run test",
|
package/dist/python-json.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
2
|
-
export function upstreamCacheRoot(root, env, cwd) {
|
|
3
|
-
const configured = env.SUPERPOWERS_CACHE_DIR || join(root, ".cache", "upstream");
|
|
4
|
-
const cacheParent = isAbsolute(configured)
|
|
5
|
-
? configured
|
|
6
|
-
: resolve(cwd, configured);
|
|
7
|
-
return join(cacheParent, "superpowers");
|
|
8
|
-
}
|