superpowers-manager 0.10.1 → 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 +13 -13
- package/dist/cli.js +1 -0
- package/dist/harnesses/claude-code/install.js +2 -1
- package/dist/harnesses/claude-code/prepare.js +6 -3
- package/dist/harnesses/claude-code/state.js +2 -1
- package/dist/harnesses/codex/compatibility.js +6 -2
- package/dist/harnesses/codex/manifest-overlay.js +1 -1
- package/dist/harnesses/codex/prepare.js +6 -3
- package/dist/harnesses/codex/publication.js +4 -2
- package/dist/harnesses/codex/state.js +4 -2
- package/dist/harnesses/codex/stored-state.js +6 -1
- package/dist/harnesses/hermes/install.js +4 -2
- package/dist/harnesses/hermes/prepare.js +6 -3
- package/dist/harnesses/hermes/state.js +2 -1
- package/dist/harnesses/opencode/package.js +9 -38
- package/dist/harnesses/pi/compatibility.js +4 -4
- package/dist/snapshot-package.js +6 -5
- package/node_modules/smol-toml/README.md +460 -273
- package/node_modules/smol-toml/dist/date.js +50 -21
- package/node_modules/smol-toml/dist/error.d.ts +1 -2
- package/node_modules/smol-toml/dist/error.js +6 -2
- package/node_modules/smol-toml/dist/extract.js +240 -20
- package/node_modules/smol-toml/dist/index.cjs +409 -373
- package/node_modules/smol-toml/dist/index.d.ts +5 -4
- package/node_modules/smol-toml/dist/index.js +2 -1
- package/node_modules/smol-toml/dist/parse.d.ts +38 -1
- package/node_modules/smol-toml/dist/parse.js +52 -37
- package/node_modules/smol-toml/dist/primitive.js +23 -110
- package/node_modules/smol-toml/dist/stringify.d.ts +2 -1
- package/node_modules/smol-toml/dist/stringify.js +74 -36
- package/node_modules/smol-toml/dist/struct.js +76 -99
- package/node_modules/smol-toml/dist/util.d.ts +6 -1
- package/node_modules/smol-toml/dist/util.js +4 -36
- package/node_modules/smol-toml/package.json +8 -8
- package/package.json +3 -3
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#
|
|
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
|
|
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,10 +182,10 @@ 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.
|
|
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
|
|
|
189
189
|
## Contributing
|
|
190
190
|
|
|
191
|
-
See [the contributor guide](https://github.com/j7an/superpowers-manager/blob/main/
|
|
191
|
+
See [the contributor guide](https://github.com/j7an/superpowers-manager/blob/main/docs/development.md) for development, testing, packaging, CI, and repository layout. Architecture details live in the [harness interface](https://github.com/j7an/superpowers-manager/blob/main/docs/harness-interface.md) and [adapter result contract](https://github.com/j7an/superpowers-manager/blob/main/docs/adapter-result-contract.md).
|
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:
|
|
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:
|
|
104
|
+
reason: official
|
|
105
|
+
? "native Codex skill discovery"
|
|
106
|
+
: "native Codex skill discovery from a custom source",
|
|
103
107
|
};
|
|
104
108
|
}
|
|
105
109
|
catch {
|
|
@@ -56,7 +56,7 @@ function setMember(entries, key, value) {
|
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
58
|
* Map strict-json's diagnostics onto the CPython wording the callers assert.
|
|
59
|
-
* Repository policy at `AGENTS.md:
|
|
59
|
+
* Repository policy at `AGENTS.md:98::most diagnostics are asserted as`
|
|
60
60
|
* freezes most of these diagnostics as complete-string contracts.
|
|
61
61
|
*/
|
|
62
62
|
function translate(cause, source, path) {
|
|
@@ -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);
|
|
@@ -19,11 +19,16 @@ function storedError(message, cause) {
|
|
|
19
19
|
function table(value, name) {
|
|
20
20
|
if (value === undefined)
|
|
21
21
|
return {};
|
|
22
|
+
// smol-toml 1.9.0 returns null-prototype tables; class instances such as
|
|
23
|
+
// TomlDate still fail the prototype check.
|
|
24
|
+
const prototype = value !== null && typeof value === "object"
|
|
25
|
+
? Object.getPrototypeOf(value)
|
|
26
|
+
: undefined;
|
|
22
27
|
if (value === null ||
|
|
23
28
|
typeof value !== "object" ||
|
|
24
29
|
Array.isArray(value) ||
|
|
25
30
|
value instanceof TomlDate ||
|
|
26
|
-
|
|
31
|
+
(prototype !== null && prototype !== Object.prototype)) {
|
|
27
32
|
throw storedError(`Codex configuration ${name} must be a table`);
|
|
28
33
|
}
|
|
29
34
|
return value;
|
|
@@ -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:
|
|
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 {
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
? "
|
|
63
|
-
: "
|
|
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
|
|
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"
|
|
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
|
-
? "
|
|
32
|
-
: "
|
|
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
|
|
38
|
+
reason: `Pi package does not provide a native Pi bootstrap: ${root}`,
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
}
|
package/dist/snapshot-package.js
CHANGED
|
@@ -70,12 +70,13 @@ export function requireNoSnapshotDependencies(pkg) {
|
|
|
70
70
|
throw new Error("runtime dependencies");
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
|
|
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
|
-
|
|
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
|