superpowers-manager 0.10.2 → 0.11.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 CHANGED
@@ -42,7 +42,7 @@ See [version selection](https://github.com/j7an/superpowers-manager/blob/main/do
42
42
 
43
43
  ## Start with your agent
44
44
 
45
- The five harnesses share upstream selection, while each has independent prepared and installed state. Omitting `--harness` selects Codex.
45
+ The five harnesses share upstream selection, while each has independent prepared and installed state. Omitting `--harness` selects Codex. Only the official `obra/superpowers` repository is supported; other sources are experimental on every harness and need `--allow-experimental` to install, or to update when the update would install or reinstall.
46
46
 
47
47
  ### Codex
48
48
 
@@ -56,7 +56,7 @@ Codex installs the Manager-owned marketplace plugin after preparation and
56
56
  validation. Review upstream hook definitions with Codex's `/hooks` flow before
57
57
  trusting or running them; updates can change a referenced script or surface a
58
58
  new definition. The manager neither creates hooks nor changes Codex trust state.
59
- See the [Codex reference](https://github.com/j7an/superpowers-manager/blob/main/docs/codex.md#durable-storage-migration-and-recovery) for durable storage, migration, and recovery.
59
+ See the [Codex reference](https://github.com/j7an/superpowers-manager/blob/main/docs/codex.md#upstream-admission) for upstream admission, durable storage, migration, and recovery.
60
60
 
61
61
  ### Pi
62
62
 
@@ -68,8 +68,8 @@ npx superpowers-manager update --harness pi
68
68
 
69
69
  Pi installs a separately validated, Manager-owned frozen snapshot. Restart Pi
70
70
  after a successful install, activating update, or removal. See the
71
- [Pi reference](https://github.com/j7an/superpowers-manager/blob/main/docs/pi.md)
72
- for compatibility, experimental opt-in, and recovery behavior.
71
+ [Pi reference](https://github.com/j7an/superpowers-manager/blob/main/docs/pi.md#upstream-admission)
72
+ for upstream admission, compatibility, and recovery behavior.
73
73
 
74
74
  ### OpenCode
75
75
 
@@ -81,8 +81,8 @@ npx superpowers-manager update --harness opencode
81
81
 
82
82
  OpenCode installs a separately validated, Manager-owned frozen snapshot. Restart
83
83
  OpenCode after a successful install, activating update, or removal. See the
84
- [OpenCode reference](https://github.com/j7an/superpowers-manager/blob/main/docs/opencode.md)
85
- for global configuration, conflicts, and recovery behavior.
84
+ [OpenCode reference](https://github.com/j7an/superpowers-manager/blob/main/docs/opencode.md#upstream-admission)
85
+ for upstream admission, global configuration, conflicts, and recovery behavior.
86
86
 
87
87
  ### Claude Code
88
88
 
@@ -94,8 +94,8 @@ npx superpowers-manager update --harness claude-code
94
94
 
95
95
  Claude Code installs a validated, Manager-owned local marketplace snapshot.
96
96
  Restart Claude Code or run `/reload-plugins` after an install, activating update,
97
- or removal. See the [Claude Code reference](https://github.com/j7an/superpowers-manager/blob/main/docs/claude-code.md)
98
- for conflicts, hooks, and recovery.
97
+ or removal. See the [Claude Code reference](https://github.com/j7an/superpowers-manager/blob/main/docs/claude-code.md#upstream-admission)
98
+ for upstream admission, conflicts, hooks, and recovery.
99
99
 
100
100
  ### Hermes Agent
101
101
 
@@ -106,8 +106,8 @@ npx superpowers-manager update --harness hermes
106
106
  ```
107
107
 
108
108
  Restart Hermes sessions after an install, activating update, or removal. See
109
- the [Hermes Agent reference](https://github.com/j7an/superpowers-manager/blob/main/docs/hermes.md)
110
- for plugin layout, compatibility, route switching, and recovery.
109
+ the [Hermes Agent reference](https://github.com/j7an/superpowers-manager/blob/main/docs/hermes.md#upstream-admission)
110
+ for upstream admission, plugin layout, compatibility, route switching, and recovery.
111
111
 
112
112
  To remove a Manager-owned installation, choose one harness explicitly:
113
113
 
@@ -163,7 +163,7 @@ The [usage reference](https://github.com/j7an/superpowers-manager/blob/main/docs
163
163
 
164
164
  ## Requirements and platforms
165
165
 
166
- The installed package requires Node >=24. Native source, tests, and packaging tooling target latest Node 24. Every other requirement is command-specific and is checked before dispatch. This table is derived from production by `tests/bin/readme-requirements.test.ts`.
166
+ The installed package requires Node >=24. Native source, tests, and packaging tooling target latest Node 24; CI also runs the shared suite on latest Node 26. Every other requirement is command-specific and is checked before dispatch. This table is derived from production by `tests/bin/readme-requirements.test.ts`.
167
167
 
168
168
  <!-- requirements:begin -->
169
169
 
@@ -182,7 +182,7 @@ The installed package requires Node >=24. Native source, tests, and packaging to
182
182
 
183
183
  Generated Codex manifests preserve upstream JSON number tokens while rejecting non-finite decimal or exponent values.
184
184
 
185
- macOS and Linux are tested. WSL2 is supported for the established Codex path; Pi in WSL2 and native Windows remain untested. The manager runs in-process and does not require Git Bash. The [Pi reference](https://github.com/j7an/superpowers-manager/blob/main/docs/pi.md#runtime-compatibility) explains its runtime qualification and admission checks. OpenCode support currently covers 1.x; OpenCode 2.x is untested and not certified as compatible (see the [OpenCode reference](https://github.com/j7an/superpowers-manager/blob/main/docs/opencode.md)).
185
+ macOS and Linux are tested. WSL2 is supported for the established Codex path; Pi in WSL2 and native Windows remain untested. The manager runs in-process and does not require Git Bash. Each harness reference's Upstream admission section lists what is checked. OpenCode support covers the 1.x and 2.x lines (see the [OpenCode reference](https://github.com/j7an/superpowers-manager/blob/main/docs/opencode.md)).
186
186
 
187
187
  Native test versions qualify integration mechanisms; they are not runtime allowlists, and an untested runtime is not automatically certified as compatible.
188
188
 
package/dist/cli.js CHANGED
@@ -216,6 +216,7 @@ function usage() {
216
216
  " update --harness opencode [--allow-experimental]",
217
217
  " uninstall --harness opencode",
218
218
  " --harness=codex, --harness=pi, --harness=opencode, --harness=claude-code, and --harness=hermes are also accepted after the command.",
219
+ " --allow-experimental (install, update) admits a custom upstream source on any harness.",
219
220
  "",
220
221
  "Environment overrides (used by in-process commands): SUPERPOWERS_REF,",
221
222
  "SUPERPOWERS_UPSTREAM_URL, SUPERPOWERS_CODEX, SUPERPOWERS_CACHE_DIR,",
@@ -163,7 +163,8 @@ export async function installClaudeCode(artifact, ctx, deps = DEFAULTS) {
163
163
  const assessment = await readClaudeCodePackageAssessment(artifact.root);
164
164
  if (assessment.receipt.digest !== artifact.identity ||
165
165
  assessment.receipt.commit !== artifact.commit ||
166
- assessment.compatibility.kind !== "supported")
166
+ (assessment.compatibility.kind !== "supported" &&
167
+ assessment.compatibility.kind !== "experimental"))
167
168
  throw new ClaudeCodeStep("the Claude Code prepared artifact changed before activation");
168
169
  const leftovers = await leftoverPublicationMaterial(paths);
169
170
  if (leftovers.length > 0)
@@ -7,7 +7,7 @@ import { SEMVER_RE } from "../../domain/refs.js";
7
7
  import { classifyPathNoFollow } from "../../safe-path.js";
8
8
  import { SafetyError } from "../../safety-error.js";
9
9
  import { validateSource } from "../../selection.js";
10
- import { createSnapshotReceipts, } from "../../snapshot-package.js";
10
+ import { createSnapshotReceipts, isOfficialSnapshotSource, } from "../../snapshot-package.js";
11
11
  import { createSnapshotPreparation } from "../../snapshot-prepare.js";
12
12
  import { manifestVersionForRef } from "../../upstream-version.js";
13
13
  import { assertClaudeCodeStorageSafe, claudeCodePaths } from "./paths.js";
@@ -16,6 +16,7 @@ const GENERATION = "claude-code-native-plugin-v1";
16
16
  async function assessClaudeCodeCompatibility(root, selection) {
17
17
  try {
18
18
  validateSource(selection.effectiveSource);
19
+ const official = isOfficialSnapshotSource(selection.effectiveSource);
19
20
  const manifest = await readArtifactObject(root, join(root, CLAUDE_CODE_MANIFEST));
20
21
  if (manifest.name !== "superpowers" ||
21
22
  typeof manifest.version !== "string" ||
@@ -26,9 +27,11 @@ async function assessClaudeCodeCompatibility(root, selection) {
26
27
  if ((await classifyPathNoFollow(hooks)) !== "missing")
27
28
  await readArtifactObject(root, hooks);
28
29
  return {
29
- kind: "supported",
30
+ kind: official ? "supported" : "experimental",
30
31
  generation: GENERATION,
31
- reason: "native Claude Code plugin",
32
+ reason: official
33
+ ? "native Claude Code plugin"
34
+ : "native Claude Code plugin from a custom source",
32
35
  };
33
36
  }
34
37
  catch {
@@ -200,7 +200,8 @@ export async function inspectClaudeCodeInstalled(selection, ctx, run = runClaude
200
200
  return mismatch(observed);
201
201
  try {
202
202
  const prepared = await readClaudeCodePackageAssessment(facts.paths.preparedRoot);
203
- if (prepared.compatibility.kind !== "supported" ||
203
+ if ((prepared.compatibility.kind !== "supported" &&
204
+ prepared.compatibility.kind !== "experimental") ||
204
205
  prepared.receipt.commit !== selection.desiredCommit ||
205
206
  !sameSnapshotSource(prepared.receipt.source, selection.effectiveSource) ||
206
207
  facts.snapshot.receipt.commit !== prepared.receipt.commit ||
@@ -4,6 +4,7 @@ import { classifyPathNoFollow, assertExistingContained, } from "../../safe-path.
4
4
  import { classifyHooks, readManifest } from "./hooks.js";
5
5
  import { SEMVER_RE } from "../../domain/refs.js";
6
6
  import { validateSource } from "../../selection.js";
7
+ import { isOfficialSnapshotSource } from "../../snapshot-package.js";
7
8
  import { writeFile } from "node:fs/promises";
8
9
  import { ARTIFACT_DIGEST_RE, ARTIFACT_RECEIPT, digestArtifactTree, readArtifactObject, } from "../../artifact-tree.js";
9
10
  import { COMMIT_RE } from "../../domain/refs.js";
@@ -78,6 +79,7 @@ export async function readCodexAssessment(root) {
78
79
  export async function assessCodexCompatibility(root, selection) {
79
80
  try {
80
81
  validateSource(selection.effectiveSource);
82
+ const official = isOfficialSnapshotSource(selection.effectiveSource);
81
83
  await validateNativeSkill(root);
82
84
  for (const path of [
83
85
  ".codex/superpowers-codex",
@@ -97,9 +99,11 @@ export async function assessCodexCompatibility(root, selection) {
97
99
  await classifyHooks(manifest, "upstream", root);
98
100
  }
99
101
  return {
100
- kind: "supported",
102
+ kind: official ? "supported" : "experimental",
101
103
  generation: "codex-native-skills-v1",
102
- reason: "native Codex skill discovery",
104
+ reason: official
105
+ ? "native Codex skill discovery"
106
+ : "native Codex skill discovery from a custom source",
103
107
  };
104
108
  }
105
109
  catch {
@@ -173,7 +173,8 @@ export async function prepareCodexCandidate(input, ctx) {
173
173
  return { status: built.status, outcome: built.outcome };
174
174
  }
175
175
  const compatibility = await assessCodexCompatibility(input.upstreamRoot, input.selection);
176
- if (compatibility.kind === "supported") {
176
+ if (compatibility.kind === "supported" ||
177
+ compatibility.kind === "experimental") {
177
178
  try {
178
179
  await writeCodexAssessment(input.candidateRoot, input.selection, (await isFile(join(input.upstreamRoot, ".codex-plugin/plugin.json")))
179
180
  ? "upstream"
@@ -205,7 +206,8 @@ export async function inspectCodexPrepared(selection, ctx) {
205
206
  const source = await readStrictProvenanceField(join(artifact.root, ".superpowers-upstream.json"), "source");
206
207
  if (artifact.commit === selection.desiredCommit &&
207
208
  source === selection.effectiveSource) {
208
- if (artifact.compatibility.kind === "supported")
209
+ if (artifact.compatibility.kind === "supported" ||
210
+ artifact.compatibility.kind === "experimental")
209
211
  return successResult("inspect-prepared", {
210
212
  kind: "current",
211
213
  artifact,
@@ -243,7 +245,8 @@ export async function readCodexPrepared(ctx) {
243
245
  }
244
246
  try {
245
247
  const artifact = await readCodexAssessment(codexPreparationLocation(ctx).destinationRoot);
246
- if (artifact.compatibility.kind !== "supported")
248
+ if (artifact.compatibility.kind !== "supported" &&
249
+ artifact.compatibility.kind !== "experimental")
247
250
  throw new Error("unsupported profile");
248
251
  return successResult("read-prepared", artifact, []);
249
252
  }
@@ -213,7 +213,8 @@ async function verifyPrepared(artifact, expectedRoot) {
213
213
  const assessed = await readCodexAssessment(artifact.root);
214
214
  if (assessed.root !== artifact.root ||
215
215
  assessed.commit !== artifact.commit ||
216
- assessed.compatibility.kind !== "supported") {
216
+ (assessed.compatibility.kind !== "supported" &&
217
+ assessed.compatibility.kind !== "experimental")) {
217
218
  throw new Error("Codex prepared artifact changed");
218
219
  }
219
220
  return await digestArtifactTree(artifact.root);
@@ -325,7 +326,8 @@ async function verifyActivated(pending, dependencies, ctx, messages) {
325
326
  }
326
327
  const active = await readCodexAssessment(native.activeRoot);
327
328
  if (active.commit !== published.artifact.commit ||
328
- active.compatibility.kind !== "supported" ||
329
+ (active.compatibility.kind !== "supported" &&
330
+ active.compatibility.kind !== "experimental") ||
329
331
  (await digestArtifactTree(active.root)) !==
330
332
  (await digestArtifactTree(published.artifact.root))) {
331
333
  throw new Error("active Codex plugin differs from publication");
@@ -138,7 +138,8 @@ export async function inspectCodexInstallation(selection, ctx, readNative = code
138
138
  return mismatch("durable Codex marketplace is missing", messages, observedIdentity);
139
139
  try {
140
140
  if (marketplace.artifact.commit !== selection.desiredCommit ||
141
- marketplace.artifact.compatibility.kind !== "supported" ||
141
+ (marketplace.artifact.compatibility.kind !== "supported" &&
142
+ marketplace.artifact.compatibility.kind !== "experimental") ||
142
143
  !(await matchesSelection(marketplace.artifact.root, selection))) {
143
144
  return mismatch("durable Codex marketplace differs from selection", messages, observedIdentity);
144
145
  }
@@ -185,7 +186,8 @@ export async function inspectCodexInstallation(selection, ctx, readNative = code
185
186
  }
186
187
  try {
187
188
  if (active.commit !== selection.desiredCommit ||
188
- active.compatibility.kind !== "supported" ||
189
+ (active.compatibility.kind !== "supported" &&
190
+ active.compatibility.kind !== "experimental") ||
189
191
  !(await matchesSelection(active.root, selection)) ||
190
192
  activeDigest !== durableDigest) {
191
193
  return mismatch("active Codex plugin payload differs from durable marketplace", messages, active.commit);
@@ -105,7 +105,8 @@ export async function installHermes(artifact, ctx, deps = DEFAULTS) {
105
105
  const assessment = await readHermesPackageAssessment(artifact.root);
106
106
  if (assessment.receipt.digest !== artifact.identity ||
107
107
  assessment.receipt.commit !== artifact.commit ||
108
- assessment.compatibility.kind !== "supported")
108
+ (assessment.compatibility.kind !== "supported" &&
109
+ assessment.compatibility.kind !== "experimental"))
109
110
  throw new HermesStep("the Hermes prepared artifact changed before activation");
110
111
  previouslyEnabled =
111
112
  (await readHermesStatus(paths, ctx.env ?? {})) === "enabled";
@@ -130,7 +131,8 @@ export async function installHermes(artifact, ctx, deps = DEFAULTS) {
130
131
  const staged = await readHermesPackageAssessment(pending.stage);
131
132
  if (staged.receipt.digest !== artifact.identity ||
132
133
  staged.receipt.commit !== artifact.commit ||
133
- staged.compatibility.kind !== "supported")
134
+ (staged.compatibility.kind !== "supported" &&
135
+ staged.compatibility.kind !== "experimental"))
134
136
  throw new HermesStep("the staged Hermes artifact changed");
135
137
  // Keep backups outside plugins so Hermes discovery sees only the live copy.
136
138
  if (ownership.kind === "owned") {
@@ -5,7 +5,7 @@ import { readArtifactFile, validateNativeSkill } from "../../artifact-tree.js";
5
5
  import { assertNoFollowType, classifyPathNoFollow } from "../../safe-path.js";
6
6
  import { SafetyError } from "../../safety-error.js";
7
7
  import { validateSource } from "../../selection.js";
8
- import { createSnapshotReceipts, } from "../../snapshot-package.js";
8
+ import { createSnapshotReceipts, isOfficialSnapshotSource, } from "../../snapshot-package.js";
9
9
  import { createSnapshotPreparation } from "../../snapshot-prepare.js";
10
10
  import { assertHermesStorageSafe, hermesPaths } from "./paths.js";
11
11
  async function flattenHermesPlugin(root) {
@@ -26,6 +26,7 @@ async function flattenHermesPlugin(root) {
26
26
  async function assessHermesCompatibility(root, selection) {
27
27
  try {
28
28
  validateSource(selection.effectiveSource);
29
+ const official = isOfficialSnapshotSource(selection.effectiveSource);
29
30
  await readArtifactFile(root, join(root, "__pycache__"), 0);
30
31
  for (const relative of [
31
32
  "plugin.yaml",
@@ -35,9 +36,11 @@ async function assessHermesCompatibility(root, selection) {
35
36
  await assertNoFollowType(join(root, relative), ["regular-file"]);
36
37
  await validateNativeSkill(root);
37
38
  return {
38
- kind: "supported",
39
+ kind: official ? "supported" : "experimental",
39
40
  generation: "hermes-flat-plugin-v1",
40
- reason: "native Hermes plugin",
41
+ reason: official
42
+ ? "native Hermes plugin"
43
+ : "native Hermes plugin from a custom source",
41
44
  };
42
45
  }
43
46
  catch {
@@ -97,7 +97,8 @@ export async function inspectHermesInstalled(selection, ctx) {
97
97
  return mismatch();
98
98
  try {
99
99
  const prepared = await readHermesPackageAssessment(facts.paths.preparedRoot);
100
- if (prepared.compatibility.kind !== "supported" ||
100
+ if ((prepared.compatibility.kind !== "supported" &&
101
+ prepared.compatibility.kind !== "experimental") ||
101
102
  prepared.receipt.commit !== selection.desiredCommit ||
102
103
  !sameSnapshotSource(prepared.receipt.source, selection.effectiveSource) ||
103
104
  facts.ownership.receipt.commit !== selection.desiredCommit ||
@@ -1,36 +1,11 @@
1
1
  import { join } from "node:path";
2
2
  import { readArtifactObject, validateNativeSkill, } from "../../artifact-tree.js";
3
3
  import { SEMVER_RE } from "../../domain/refs.js";
4
- import { assertNoFollowType } from "../../safe-path.js";
4
+ import { classifyPathNoFollow } from "../../safe-path.js";
5
5
  import { validateSource } from "../../selection.js";
6
6
  import { createSnapshotReceipts, isOfficialSnapshotSource, requireNoSnapshotDependencies, requireSnapshotBootstrap, } from "../../snapshot-package.js";
7
7
  const BOOTSTRAP = ".opencode/plugins/superpowers.js";
8
8
  const ENTRYPOINT = "index.js";
9
- // Exact-byte allowlist, matched as a unit: a bootstrap and the V2 entrypoint
10
- // that shipped with it were qualified together in the native container. Adding
11
- // either half alone would admit a combination no container ever ran. Every
12
- // upstream change needs re-qualification before its bytes are added here.
13
- const QUALIFIED_PROFILES = [
14
- {
15
- // obra/superpowers v6.0.0-v6.3.0 — no V2 entrypoint shipped
16
- bootstrap: {
17
- size: 5464,
18
- sha256: "a5c5e1dbb0abfbd6ec3322b724b9a7b3318bbbb3d83f9a661c56ae0ed0a3adb8",
19
- },
20
- entrypoint: null,
21
- },
22
- {
23
- // obra/superpowers v6.4.1
24
- bootstrap: {
25
- size: 17617,
26
- sha256: "c979fe5a9fd6fddc9bc9730b34b25989f9d53939eed7d594c4564f6e47495f26",
27
- },
28
- entrypoint: {
29
- size: 536,
30
- sha256: "f0132fd5339befeb99ba903b1619fd9530969e5b4a1ea8cecf0b0a7f0213a099",
31
- },
32
- },
33
- ];
34
9
  export async function assessOpenCodeCompatibility(root, selection) {
35
10
  try {
36
11
  validateSource(selection.effectiveSource);
@@ -43,15 +18,11 @@ export async function assessOpenCodeCompatibility(root, selection) {
43
18
  throw new Error("package metadata");
44
19
  requireNoSnapshotDependencies(pkg);
45
20
  await validateNativeSkill(root);
46
- const matched = await Promise.any(QUALIFIED_PROFILES.map(async (profile) => {
47
- await requireSnapshotBootstrap(root, BOOTSTRAP, profile.bootstrap.size, profile.bootstrap.sha256);
48
- if (profile.entrypoint === null)
49
- await assertNoFollowType(join(root, ENTRYPOINT), ["missing"]);
50
- else
51
- await requireSnapshotBootstrap(root, ENTRYPOINT, profile.entrypoint.size, profile.entrypoint.sha256);
52
- return profile;
53
- }));
54
- const generation = matched.entrypoint === null
21
+ await requireSnapshotBootstrap(root, BOOTSTRAP);
22
+ const v1 = (await classifyPathNoFollow(join(root, ENTRYPOINT))) === "missing";
23
+ if (!v1)
24
+ await requireSnapshotBootstrap(root, ENTRYPOINT);
25
+ const generation = v1
55
26
  ? "opencode-native-bootstrap-v1"
56
27
  : "opencode-native-bootstrap-v2";
57
28
  const official = isOfficialSnapshotSource(selection.effectiveSource);
@@ -59,14 +30,14 @@ export async function assessOpenCodeCompatibility(root, selection) {
59
30
  kind: official ? "supported" : "experimental",
60
31
  generation,
61
32
  reason: official
62
- ? "qualified upstream-native OpenCode bootstrap"
63
- : "qualified OpenCode mechanics from a custom source",
33
+ ? "upstream-native OpenCode bootstrap"
34
+ : "upstream-native OpenCode bootstrap from a custom source",
64
35
  };
65
36
  }
66
37
  catch {
67
38
  return {
68
39
  kind: "unsupported",
69
- reason: `OpenCode package does not match the qualified native bootstrap profile: ${root}`,
40
+ reason: `OpenCode package does not provide a native OpenCode bootstrap: ${root}`,
70
41
  };
71
42
  }
72
43
  }
@@ -23,19 +23,19 @@ export async function assessPiCompatibility(root, selection) {
23
23
  throw new Error("resource declaration");
24
24
  requireNoSnapshotDependencies(pkg);
25
25
  await validateNativeSkill(root);
26
- await requireSnapshotBootstrap(root, ".pi/extensions/superpowers.ts", 4283, "39c27000c047f8a1399a76e032e4cd239d7bdb0be168a121f46a2ac719deaae0");
26
+ await requireSnapshotBootstrap(root, ".pi/extensions/superpowers.ts");
27
27
  return {
28
28
  kind: official ? "supported" : "experimental",
29
29
  generation: "pi-native-bootstrap-v1",
30
30
  reason: official
31
- ? "qualified upstream-native Pi bootstrap"
32
- : "qualified Pi mechanics from a custom source",
31
+ ? "upstream-native Pi bootstrap"
32
+ : "upstream-native Pi bootstrap from a custom source",
33
33
  };
34
34
  }
35
35
  catch {
36
36
  return {
37
37
  kind: "unsupported",
38
- reason: `Pi package does not match the qualified native bootstrap profile: ${root}`,
38
+ reason: `Pi package does not provide a native Pi bootstrap: ${root}`,
39
39
  };
40
40
  }
41
41
  }
@@ -70,12 +70,13 @@ export function requireNoSnapshotDependencies(pkg) {
70
70
  throw new Error("runtime dependencies");
71
71
  }
72
72
  }
73
- export async function requireSnapshotBootstrap(root, relativePath, expectedSize, expectedDigest) {
73
+ // Ceiling: structural admission cannot catch an upstream bootstrap whose
74
+ // behavior breaks the integration; add a behavior-level check if that happens
75
+ // in the field.
76
+ export async function requireSnapshotBootstrap(root, relativePath) {
74
77
  const bootstrap = join(root, relativePath);
75
- const bytes = await readArtifactFile(root, bootstrap, expectedSize);
76
- if ((await lstat(bootstrap)).mode & 0o111 ||
77
- bytes.length !== expectedSize ||
78
- createHash("sha256").update(bytes).digest("hex") !== expectedDigest)
78
+ await readArtifactFile(root, bootstrap);
79
+ if ((await lstat(bootstrap)).mode & 0o111)
79
80
  throw new Error("bootstrap implementation");
80
81
  }
81
82
  // strictGeneration=false admits a generation on unknown/unsupported
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superpowers-manager",
3
- "version": "0.10.2",
3
+ "version": "0.11.0",
4
4
  "description": "Install obra/superpowers directly from upstream for Codex, Pi, OpenCode, Claude Code, and Hermes Agent. Stay on a known-good release or commit, try a branch, and upgrade when you choose.",
5
5
  "keywords": [
6
6
  "superpowers",
@@ -38,7 +38,7 @@
38
38
  ],
39
39
  "license": "MIT",
40
40
  "type": "module",
41
- "packageManager": "pnpm@12.9.1",
41
+ "packageManager": "pnpm@12.17.0",
42
42
  "scripts": {
43
43
  "lint": "oxlint src/ tests/",
44
44
  "format": "prettier --write src/ tests/",