superpowers-manager 0.5.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 +38 -8
- package/dist/artifact-tree.js +2 -1
- package/dist/cli.js +2 -2
- package/dist/commands/install.js +2 -2
- package/dist/commands/prepare.js +4 -4
- package/dist/commands/probe.js +4 -4
- package/dist/commands/uninstall.js +3 -3
- package/dist/commands/update.js +1 -1
- package/dist/domain/refs.js +4 -0
- package/dist/{adapter.js → harnesses/codex/adapter.js} +10 -10
- package/dist/{codex-compatibility.js → harnesses/codex/compatibility.js} +7 -7
- package/dist/{codex-conflicts.js → harnesses/codex/conflicts.js} +1 -1
- package/dist/{generated-plugin.js → harnesses/codex/generated-plugin.js} +7 -73
- package/dist/{codex-harness.js → harnesses/codex/harness.js} +5 -5
- package/dist/{hooks.js → harnesses/codex/hooks.js} +6 -6
- package/dist/{codex-json.js → harnesses/codex/json.js} +2 -2
- package/dist/{lifecycle.js → harnesses/codex/lifecycle.js} +2 -2
- package/dist/{manifest-overlay.js → harnesses/codex/manifest-overlay.js} +4 -4
- package/dist/{codex-prepare.js → harnesses/codex/prepare.js} +7 -7
- package/dist/{codex-presentation.js → harnesses/codex/presentation.js} +3 -3
- package/dist/{codex-state.js → harnesses/codex/state.js} +3 -3
- package/dist/{pi-compatibility.js → harnesses/pi/compatibility.js} +3 -3
- package/dist/{pi-harness.js → harnesses/pi/harness.js} +7 -7
- package/dist/{pi-install.js → harnesses/pi/install.js} +8 -8
- package/dist/{pi-native.js → harnesses/pi/native.js} +4 -4
- package/dist/{pi-package.js → harnesses/pi/package.js} +6 -6
- package/dist/{pi-prepare.js → harnesses/pi/prepare.js} +6 -6
- package/dist/{pi-settings.js → harnesses/pi/settings.js} +3 -3
- package/dist/{pi-shared-skills.js → harnesses/pi/shared-skills.js} +1 -1
- package/dist/{pi-state.js → harnesses/pi/state.js} +8 -8
- package/dist/python-text.js +16 -0
- package/dist/skill-validation.js +52 -0
- package/dist/validate-generated-plugin-cli.js +1 -1
- package/package.json +4 -3
- /package/dist/{pi-paths.js → harnesses/pi/paths.js} +0 -0
- /package/dist/{pi-presentation.js → harnesses/pi/presentation.js} +0 -0
package/README.md
CHANGED
|
@@ -1,9 +1,45 @@
|
|
|
1
1
|
# Superpowers Manager
|
|
2
2
|
|
|
3
|
-
Install
|
|
3
|
+
Install [`obra/superpowers`](https://github.com/obra/superpowers) directly from upstream for Codex and the Pi coding agent. Stay on a known-good release or commit, try a branch, and upgrade when you choose—without waiting for a marketplace copy to catch up.
|
|
4
4
|
|
|
5
5
|
> Unofficial community integration. Not affiliated with the `obra/superpowers` maintainers.
|
|
6
6
|
|
|
7
|
+
## Choose what you run
|
|
8
|
+
|
|
9
|
+
Start with the latest stable release by default, or choose the upstream version that works for you. There is no background updater.
|
|
10
|
+
|
|
11
|
+
| What you want | How to choose it |
|
|
12
|
+
|---|---|
|
|
13
|
+
| Stay on a known-good version | `pin` a release tag or full 40-character commit SHA |
|
|
14
|
+
| Try an upstream fix before its release | Set `SUPERPOWERS_REF` to a branch or other resolvable ref for that invocation |
|
|
15
|
+
| Return to the latest stable release | Save `track-latest`, then explicitly install or update |
|
|
16
|
+
|
|
17
|
+
For example, save a release pin and install it for your agent:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npx superpowers-manager pin v6.1.1
|
|
21
|
+
npx superpowers-manager install --harness codex
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Use `--harness pi` for Pi. The pin is shared by both agents, but each installation changes only when you run its `install` or `update` command. A saved pin keeps subsequent installs and updates on that version unless you change or override the selection.
|
|
25
|
+
|
|
26
|
+
To try a branch, replace `feature/foo` with an existing upstream branch:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
SUPERPOWERS_REF=feature/foo npx superpowers-manager install --harness codex
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This override applies only to that command and leaves your saved selection unchanged; the installed version remains until you explicitly change it. Branches can move. Persistent `pin` accepts exact version tags (including prereleases) and full commit SHAs.
|
|
33
|
+
|
|
34
|
+
When you are ready to return to the latest stable release:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npx superpowers-manager track-latest
|
|
38
|
+
npx superpowers-manager update --harness codex
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
See [version selection](https://github.com/j7an/superpowers-manager/blob/main/docs/usage.md#choosing-a-source) for source overrides, precedence, and `unpin`.
|
|
42
|
+
|
|
7
43
|
## Start with your agent
|
|
8
44
|
|
|
9
45
|
The two harnesses share upstream selection, while each has independent prepared and installed state. Omitting `--harness` selects Codex.
|
|
@@ -53,7 +89,7 @@ The manager never removes another Superpowers provider. See the [ownership guida
|
|
|
53
89
|
|
|
54
90
|
## Shared selection and lifecycle
|
|
55
91
|
|
|
56
|
-
|
|
92
|
+
The [selection commands above](#choose-what-you-run) save shared upstream intent. They do not activate Codex or Pi; run the selected harness's `install` or `update` to apply that choice. `unpin` restores the packaged fallback policy.
|
|
57
93
|
|
|
58
94
|
| Command | Purpose |
|
|
59
95
|
|---|---|
|
|
@@ -65,12 +101,6 @@ Use `pin`, `track-latest`, or `unpin` to save shared upstream intent. They do no
|
|
|
65
101
|
|
|
66
102
|
Use `--harness codex` or `--harness pi` with each targeted lifecycle command.
|
|
67
103
|
|
|
68
|
-
```sh
|
|
69
|
-
npx superpowers-manager pin v6.1.1
|
|
70
|
-
npx superpowers-manager track-latest
|
|
71
|
-
npx superpowers-manager unpin
|
|
72
|
-
```
|
|
73
|
-
|
|
74
104
|
```text
|
|
75
105
|
shared selection -> prepare / inspect / activate Codex
|
|
76
106
|
-> prepare / inspect / activate Pi
|
package/dist/artifact-tree.js
CHANGED
|
@@ -4,7 +4,8 @@ import { join } from "node:path";
|
|
|
4
4
|
import { assertExistingContained, assertNoFollowType, assertSymlinkTargetContained, } from "./safe-path.js";
|
|
5
5
|
import { SafetyError } from "./safety-error.js";
|
|
6
6
|
import { parseStrictJson } from "./strict-json.js";
|
|
7
|
-
import { compareByCodePoint
|
|
7
|
+
import { compareByCodePoint } from "./python-text.js";
|
|
8
|
+
import { DEFAULT_FS_DEPS, validateSkillFrontmatter, } from "./skill-validation.js";
|
|
8
9
|
export const ARTIFACT_RECEIPT = ".superpowers-manager.json";
|
|
9
10
|
export const ARTIFACT_DIGEST_RE = /^[a-f0-9]{64}$/;
|
|
10
11
|
const DECODER = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { extractHarnessOptions, oneLine, UsageError } from "./cli-arguments.js";
|
|
5
|
-
import { codexHarness } from "./codex
|
|
6
|
-
import { piHarness } from "./pi
|
|
5
|
+
import { codexHarness } from "./harnesses/codex/harness.js";
|
|
6
|
+
import { piHarness } from "./harnesses/pi/harness.js";
|
|
7
7
|
import { createResourceCoordinator } from "./resource-lock.js";
|
|
8
8
|
import { runInstall } from "./commands/install.js";
|
|
9
9
|
import { runPin } from "./commands/pin.js";
|
package/dist/commands/install.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Ports scripts/install. The shell sourced common.sh, provenance.sh,
|
|
2
2
|
// status.sh, lifecycle.sh and adapter.sh; the predicates now live in
|
|
3
|
-
// src/lifecycle.ts, the generated-metadata read lives in src/provenance.ts,
|
|
3
|
+
// src/harnesses/codex/lifecycle.ts, the generated-metadata read lives in src/provenance.ts,
|
|
4
4
|
// and the adapter arrives through ctx.adapter.
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { oneLine } from "../cli-arguments.js";
|
|
@@ -163,7 +163,7 @@ async function gatherInstallStages(ctx, selection, artifact) {
|
|
|
163
163
|
// is what turns a failed inspection into "error: installed manager
|
|
164
164
|
// fingerprint inspection failed after install."
|
|
165
165
|
// renderInstallVerification's failed-inspection arm
|
|
166
|
-
// (`src/codex
|
|
166
|
+
// (`src/harnesses/codex/presentation.ts:329::if (inspection.status !== 0 || !inspection.outcome.ok) {`)
|
|
167
167
|
// exists for this result-bearing path; the lifecycle compatibility
|
|
168
168
|
// export delegates through the same arm. Returning
|
|
169
169
|
// failed() instead reported the adapter's own generic diagnostic and
|
package/dist/commands/prepare.js
CHANGED
|
@@ -14,7 +14,7 @@ import { runWithMutation } from "./mutation.js";
|
|
|
14
14
|
// Every message this module writes is hand-written here. The cause is attached
|
|
15
15
|
// for debuggability and never reaches a stream: oneLine (src/cli-arguments.ts)
|
|
16
16
|
// reads .message only. Same arrangement as hookError
|
|
17
|
-
// (`src/hooks.ts:44::function hookError`).
|
|
17
|
+
// (`src/harnesses/codex/hooks.ts:44::function hookError`).
|
|
18
18
|
function prepareError(message, cause) {
|
|
19
19
|
return new SafetyError("prepare", message, { cause });
|
|
20
20
|
}
|
|
@@ -341,8 +341,8 @@ async function performPrepare(argv, ctx) {
|
|
|
341
341
|
// failure (its cause is the shared runner's (src/validator.ts) captured
|
|
342
342
|
// spawn error, which oneLine never reads -- it takes .message only);
|
|
343
343
|
// readManifest's three hookError messages
|
|
344
|
-
// (`src/hooks.ts:113-138::readManifest`), pinned by
|
|
345
|
-
// `tests/unit/hooks.test.ts:95::void test("readManifest diagnostics` as carrying no reader vocabulary or
|
|
344
|
+
// (`src/harnesses/codex/hooks.ts:113-138::readManifest`), pinned by
|
|
345
|
+
// `tests/unit/harnesses/codex/hooks.test.ts:95::void test("readManifest diagnostics` as carrying no reader vocabulary or
|
|
346
346
|
// errno; and SafetyErrors from gitSafeSource, writeProvenance, and
|
|
347
347
|
// withWorkspace.
|
|
348
348
|
//
|
|
@@ -382,7 +382,7 @@ async function performPrepare(argv, ctx) {
|
|
|
382
382
|
// only, so it bounds how much of that text lands, not what it may
|
|
383
383
|
// contain.
|
|
384
384
|
//
|
|
385
|
-
// runAdapter's closing `throw cause` (src/adapter.ts) does NOT arrive
|
|
385
|
+
// runAdapter's closing `throw cause` (src/harnesses/codex/adapter.ts) does NOT arrive
|
|
386
386
|
// here -- the call site catches it and converts it to a hand-written
|
|
387
387
|
// message per AGENTS.md's reader-diagnostics rule.
|
|
388
388
|
//
|
package/dist/commands/probe.js
CHANGED
|
@@ -11,7 +11,7 @@ export const PROBE_USAGE = "error: usage: superpowers-manager probe [--porcelain
|
|
|
11
11
|
// silent diagnostics regression, not a simplification.
|
|
12
12
|
//
|
|
13
13
|
// Interpolating error.message and each hint is the sanctioned form (AGENTS.md):
|
|
14
|
-
// src/adapter.ts has 52 `fail()` sites and none interpolates a caught error's
|
|
14
|
+
// src/harnesses/codex/adapter.ts has 52 `fail()` sites and none interpolates a caught error's
|
|
15
15
|
// message, so the callee owns every failure reachable on this path. Those two
|
|
16
16
|
// writes now live in writeAdapterFailure (src/adapter-result.ts), which
|
|
17
17
|
// validates all three strings before the first of them reaches the stream.
|
|
@@ -121,7 +121,7 @@ function validControl(value) {
|
|
|
121
121
|
// evidence and report it as success.
|
|
122
122
|
//
|
|
123
123
|
// It does still THROW for a non-AdapterFailure cause (runAdapter's closing
|
|
124
|
-
// `throw cause`, src/adapter.ts). That is caught here rather than in runProbe's
|
|
124
|
+
// `throw cause`, src/harnesses/codex/adapter.ts). That is caught here rather than in runProbe's
|
|
125
125
|
// outer catch, because the two need different diagnostics -- see spec §3.3a.
|
|
126
126
|
//
|
|
127
127
|
// Reached through ctx.adapter, not a direct module-level dependency on the
|
|
@@ -377,9 +377,9 @@ export async function runProbe(argv, ctx) {
|
|
|
377
377
|
// which catches every failure and returns "".
|
|
378
378
|
//
|
|
379
379
|
// A non-AdapterFailure re-thrown by runAdapter's closing `throw cause`
|
|
380
|
-
// (src/adapter.ts) does NOT reach here: inspect() catches it and converts
|
|
380
|
+
// (src/harnesses/codex/adapter.ts) does NOT reach here: inspect() catches it and converts
|
|
381
381
|
// it to a hand-written message per AGENTS.md's reader-diagnostics rule -- a
|
|
382
|
-
// rethrown cause is exactly the failure src/adapter.ts declined to own, so
|
|
382
|
+
// rethrown cause is exactly the failure src/harnesses/codex/adapter.ts declined to own, so
|
|
383
383
|
// its text must never reach this stream. See §3.3a.
|
|
384
384
|
//
|
|
385
385
|
// gatherProbe performs no writes of its own, so this catch cannot also be
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Ports scripts/uninstall. The shell sourced common.sh, provenance.sh,
|
|
2
|
-
// lifecycle.sh and adapter.sh; the predicates now live in src/lifecycle.ts
|
|
2
|
+
// lifecycle.sh and adapter.sh; the predicates now live in src/harnesses/codex/lifecycle.ts
|
|
3
3
|
// and the adapter arrives through ctx.adapter.
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { oneLine } from "../cli-arguments.js";
|
|
@@ -126,7 +126,7 @@ async function gatherUninstall(ctx) {
|
|
|
126
126
|
// Reachable only for mkdtemp failure, with nothing collected yet: this
|
|
127
127
|
// callback never throws -- every ctx.adapter throw is already caught
|
|
128
128
|
// inside invoke(), and presenceFlag/verifyUninstalledResources/
|
|
129
|
-
// reportLegacyState are pure (src/lifecycle.ts's header comment) -- so a
|
|
129
|
+
// reportLegacyState are pure (src/harnesses/codex/lifecycle.ts's header comment) -- so a
|
|
130
130
|
// post-success cleanup failure is handled by onCleanupFailure above and
|
|
131
131
|
// cannot reach here. Wrapping with `outcomes` anyway keeps the class
|
|
132
132
|
// total over its declared contract rather than assuming the callback's
|
|
@@ -163,7 +163,7 @@ async function performUninstall(argv, ctx) {
|
|
|
163
163
|
// blindly.
|
|
164
164
|
//
|
|
165
165
|
// A cause outside ctx.adapter's AdapterFailure guard
|
|
166
|
-
// (`src/adapter.ts:973-999::async function runCodexOperation(`) does NOT
|
|
166
|
+
// (`src/harnesses/codex/adapter.ts:973-999::async function runCodexOperation(`) does NOT
|
|
167
167
|
// reach here: invoke() catches it inside gatherUninstall and converts it
|
|
168
168
|
// to a hand-written message carried as UninstallOutcome data, exactly as
|
|
169
169
|
// src/commands/probe.ts's inspect() does for the same cause.
|
package/dist/commands/update.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Ports scripts/update. The shell sourced common.sh and lifecycle.sh; the
|
|
2
|
-
// predicates now live in src/lifecycle.ts. Unlike install and uninstall,
|
|
2
|
+
// predicates now live in src/harnesses/codex/lifecycle.ts. Unlike install and uninstall,
|
|
3
3
|
// update issues no ctx.adapter call of its own: every adapter interaction
|
|
4
4
|
// this module needs already happens inside gatherProbe, runPrepare or
|
|
5
5
|
// runInstall, each of which owns its own §4.2a-conformant invoke() gate. That
|
package/dist/domain/refs.js
CHANGED
|
@@ -3,6 +3,10 @@ const SEMVER_BASE_SOURCE = "(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0
|
|
|
3
3
|
"(?:\\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?";
|
|
4
4
|
export const SEMVER_BASE_RE = new RegExp(`^${SEMVER_BASE_SOURCE}$`);
|
|
5
5
|
export const TAG_RE = new RegExp(`^v${SEMVER_BASE_SOURCE}$`);
|
|
6
|
+
// The Python's own SEMVER_RE (:15-22). Deliberately not SEMVER_BASE_SOURCE
|
|
7
|
+
// from this module, which omits the `+build` component; widening the shared
|
|
8
|
+
// constant would change TAG_RE and is out of scope.
|
|
9
|
+
export const SEMVER_RE = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
6
10
|
export const COMMIT_RE = /^[0-9a-f]{40}$/;
|
|
7
11
|
export const COMMIT_INPUT_RE = /^[0-9A-Fa-f]{40}$/;
|
|
8
12
|
export function isTagRef(value) {
|
|
@@ -4,17 +4,17 @@ import { constants } from "node:fs";
|
|
|
4
4
|
import { access, copyFile, mkdir, readFile, realpath, stat, writeFile, } from "node:fs/promises";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { delimiter, isAbsolute, join } from "node:path";
|
|
7
|
-
import { AdapterMessageLog, failureResult, successResult, } from "
|
|
8
|
-
import { activePluginVersionFromJson, installedListingHas, marketplaceRootFromJson, } from "./
|
|
9
|
-
import { CODEX_LEGACY_PLUGIN_ID, CODEX_MANAGER_PLUGIN_ID, inspectCodexConflicts, } from "./
|
|
10
|
-
import { oneLine } from "
|
|
11
|
-
import { installedCommitFromRoot, installedRootForVersion, pathsEqual, } from "./
|
|
7
|
+
import { AdapterMessageLog, failureResult, successResult, } from "../../adapter-result.js";
|
|
8
|
+
import { activePluginVersionFromJson, installedListingHas, marketplaceRootFromJson, } from "./json.js";
|
|
9
|
+
import { CODEX_LEGACY_PLUGIN_ID, CODEX_MANAGER_PLUGIN_ID, inspectCodexConflicts, } from "./conflicts.js";
|
|
10
|
+
import { oneLine } from "../../cli-arguments.js";
|
|
11
|
+
import { installedCommitFromRoot, installedRootForVersion, pathsEqual, } from "./state.js";
|
|
12
12
|
import { validateGeneratedPlugin } from "./generated-plugin.js";
|
|
13
13
|
import { classifyHooks, materializeHooks, readManifest, } from "./hooks.js";
|
|
14
14
|
import { applyManifestOverlay } from "./manifest-overlay.js";
|
|
15
|
-
import { readCodexBuildSource } from "
|
|
16
|
-
import { isAcceptedSplitValue } from "
|
|
17
|
-
import { withWorkspace, workspaceRemovalFailure } from "
|
|
15
|
+
import { readCodexBuildSource } from "../../provenance.js";
|
|
16
|
+
import { isAcceptedSplitValue } from "../../validate-generated-plugin-cli.js";
|
|
17
|
+
import { withWorkspace, workspaceRemovalFailure } from "../../workspace.js";
|
|
18
18
|
const PLUGIN_ID = CODEX_MANAGER_PLUGIN_ID;
|
|
19
19
|
const MARKETPLACE_NAME = "superpowers-manager";
|
|
20
20
|
const LEGACY_PLUGIN_ID = CODEX_LEGACY_PLUGIN_ID;
|
|
@@ -89,7 +89,7 @@ function runCommand(file, args, env) {
|
|
|
89
89
|
});
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
|
-
// Exported only so tests/unit/adapter.test.
|
|
92
|
+
// Exported only so tests/unit/harnesses/codex/adapter.test.ts can assert the env scrub
|
|
93
93
|
// directly. No production caller uses this name.
|
|
94
94
|
export { runCommand as runCommandForTest };
|
|
95
95
|
function commandFailed(result) {
|
|
@@ -281,7 +281,7 @@ async function runBuild(input, env, log) {
|
|
|
281
281
|
// path — three are the frozen CPython wording, and the fourth (the
|
|
282
282
|
// numeric-overflow diagnostic, which has no CPython oracle wording
|
|
283
283
|
// to match) now carries the path via its own rewrap in
|
|
284
|
-
// src/manifest-overlay.ts. Emit as-is, with no added prefix — a
|
|
284
|
+
// src/harnesses/codex/manifest-overlay.ts. Emit as-is, with no added prefix — a
|
|
285
285
|
// prefix here would double up the path these messages already
|
|
286
286
|
// name.
|
|
287
287
|
log.appendText("stderr", oneLine(cause));
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { validateNativeSkill } from "
|
|
3
|
-
import { classifyPathNoFollow, assertExistingContained } from "
|
|
2
|
+
import { validateNativeSkill } from "../../artifact-tree.js";
|
|
3
|
+
import { classifyPathNoFollow, assertExistingContained, } from "../../safe-path.js";
|
|
4
4
|
import { classifyHooks, readManifest } from "./hooks.js";
|
|
5
|
-
import { SEMVER_RE } from "
|
|
6
|
-
import { validateSource } from "
|
|
5
|
+
import { SEMVER_RE } from "../../domain/refs.js";
|
|
6
|
+
import { validateSource } from "../../selection.js";
|
|
7
7
|
import { writeFile } from "node:fs/promises";
|
|
8
|
-
import { ARTIFACT_DIGEST_RE, ARTIFACT_RECEIPT, digestArtifactTree, readArtifactObject, } from "
|
|
9
|
-
import { COMMIT_RE } from "
|
|
8
|
+
import { ARTIFACT_DIGEST_RE, ARTIFACT_RECEIPT, digestArtifactTree, readArtifactObject, } from "../../artifact-tree.js";
|
|
9
|
+
import { COMMIT_RE } from "../../domain/refs.js";
|
|
10
10
|
import { validateGeneratedPlugin } from "./generated-plugin.js";
|
|
11
|
-
import { SafetyError } from "
|
|
11
|
+
import { SafetyError } from "../../safety-error.js";
|
|
12
12
|
export async function writeCodexAssessment(root, selection, manifestSource) {
|
|
13
13
|
const receipt = {
|
|
14
14
|
schema: 1,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { lstat, readFile, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { codexInstalledPluginsFromJson, } from "./
|
|
3
|
+
import { codexInstalledPluginsFromJson, } from "./json.js";
|
|
4
4
|
export const CODEX_MANAGER_PLUGIN_ID = "superpowers@superpowers-manager";
|
|
5
5
|
export const CODEX_LEGACY_PLUGIN_ID = "superpowers@superpowers-wrapper";
|
|
6
6
|
const SUPERPOWERS_PLUGIN_PREFIX = "superpowers@";
|
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
import { lstat, readdir, readFile, readlink, stat } from "node:fs/promises";
|
|
2
1
|
import { homedir } from "node:os";
|
|
3
2
|
import { posix } from "node:path";
|
|
4
|
-
import { COMMIT_RE } from "
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
lstat,
|
|
9
|
-
stat,
|
|
10
|
-
readdir,
|
|
11
|
-
readlink,
|
|
12
|
-
readFile,
|
|
13
|
-
};
|
|
14
|
-
// The Python's own SEMVER_RE (:15-22). Deliberately not SEMVER_BASE_SOURCE
|
|
15
|
-
// from src/domain/refs.ts, which omits the `+build` component; widening the
|
|
16
|
-
// shared constant would change TAG_RE and is out of scope.
|
|
17
|
-
export const SEMVER_RE = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
3
|
+
import { COMMIT_RE, SEMVER_RE } from "../../domain/refs.js";
|
|
4
|
+
import { compareByCodePoint, pythonStrip } from "../../python-text.js";
|
|
5
|
+
import { DEFAULT_FS_DEPS, validateSkillFrontmatter, } from "../../skill-validation.js";
|
|
6
|
+
import { parseStrictJson } from "../../strict-json.js";
|
|
18
7
|
const MANIFEST_JSON_PROFILE = {
|
|
19
8
|
duplicateKeys: "last-wins",
|
|
20
9
|
nonStandardConstants: "reject",
|
|
@@ -443,61 +432,6 @@ const REQUIRED_FILES = [
|
|
|
443
432
|
"README.md",
|
|
444
433
|
"CODE_OF_CONDUCT.md",
|
|
445
434
|
];
|
|
446
|
-
/** Python `sorted()` orders by code point; JavaScript's default sort does not. */
|
|
447
|
-
export function compareByCodePoint(left, right) {
|
|
448
|
-
// `Array.from` splits by code point, exactly as spreading would; oxlint's
|
|
449
|
-
// `no-misused-spread` rejects the spread form, and grapheme segmentation is
|
|
450
|
-
// the wrong unit here — Python compares code points.
|
|
451
|
-
const leftPoints = Array.from(left);
|
|
452
|
-
const rightPoints = Array.from(right);
|
|
453
|
-
const shared = Math.min(leftPoints.length, rightPoints.length);
|
|
454
|
-
for (let index = 0; index < shared; index += 1) {
|
|
455
|
-
const a = leftPoints[index].codePointAt(0);
|
|
456
|
-
const b = rightPoints[index].codePointAt(0);
|
|
457
|
-
if (a !== b)
|
|
458
|
-
return a - b;
|
|
459
|
-
}
|
|
460
|
-
return leftPoints.length - rightPoints.length;
|
|
461
|
-
}
|
|
462
|
-
export async function validateSkillFrontmatter(skillMd, skillName, errors, deps) {
|
|
463
|
-
let contents;
|
|
464
|
-
try {
|
|
465
|
-
contents = STRICT_DECODER.decode(await deps.readFile(skillMd));
|
|
466
|
-
}
|
|
467
|
-
catch {
|
|
468
|
-
errors.push(`skill \`${skillName}\` has unreadable UTF-8 \`SKILL.md\``);
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
if (contents === "") {
|
|
472
|
-
errors.push(`skill \`${skillName}\` has empty \`SKILL.md\``);
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
const lines = pythonSplitlines(contents);
|
|
476
|
-
if (lines.length === 0 || lines[0] !== "---") {
|
|
477
|
-
errors.push(`skill \`${skillName}\` must start with \`---\``);
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
const closingIndex = lines.indexOf("---", 1);
|
|
481
|
-
if (closingIndex === -1) {
|
|
482
|
-
errors.push(`skill \`${skillName}\` frontmatter is not closed`);
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
const frontmatter = lines.slice(1, closingIndex);
|
|
486
|
-
for (const key of ["name", "description"]) {
|
|
487
|
-
const matches = frontmatter.filter((line) => line.startsWith(`${key}:`));
|
|
488
|
-
if (matches.length !== 1) {
|
|
489
|
-
errors.push(`skill \`${skillName}\` frontmatter must contain exactly one top-level \`${key}:\``);
|
|
490
|
-
continue;
|
|
491
|
-
}
|
|
492
|
-
const value = pythonStrip(matches[0].slice(matches[0].indexOf(":") + 1));
|
|
493
|
-
if (value === "" ||
|
|
494
|
-
value === "''" ||
|
|
495
|
-
value === '""' ||
|
|
496
|
-
value.startsWith("#")) {
|
|
497
|
-
errors.push(`skill \`${skillName}\` frontmatter field \`${key}\` must be non-empty`);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
435
|
async function validateHookSubtree(pluginRoot, hooksRoot, errors, deps) {
|
|
502
436
|
let resolvedRoot;
|
|
503
437
|
try {
|
|
@@ -513,9 +447,9 @@ async function validateHookSubtree(pluginRoot, hooksRoot, errors, deps) {
|
|
|
513
447
|
isLink = (await inspectLink(path, deps)) === "symlink";
|
|
514
448
|
}
|
|
515
449
|
catch {
|
|
516
|
-
// The `src/generated-plugin.ts:
|
|
517
|
-
// The three probes sharing the *subtree* string are `src/generated-plugin.ts:
|
|
518
|
-
// `src/generated-plugin.ts:
|
|
450
|
+
// The `src/harnesses/codex/generated-plugin.ts:583::isLink =` probe reaches the first catch bounded by `src/harnesses/codex/generated-plugin.ts:592::let rawTarget`; the `src/harnesses/codex/generated-plugin.ts:594::rawTarget = decodePathBytes` readlink reaches the same site text.
|
|
451
|
+
// The three probes sharing the *subtree* string are `src/harnesses/codex/generated-plugin.ts:627::resolvedDirectory =`, `src/harnesses/codex/generated-plugin.ts:637::children =`, and
|
|
452
|
+
// `src/harnesses/codex/generated-plugin.ts:646::deps, true)) === "directory"`, not this one.
|
|
519
453
|
errors.push(`generated hook symlink could not be inspected: ${path}`);
|
|
520
454
|
return false;
|
|
521
455
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { join, resolve } from "node:path";
|
|
2
|
-
import { failureResult, hasTerminalControl, successResult, } from "
|
|
2
|
+
import { failureResult, hasTerminalControl, successResult, } from "../../adapter-result.js";
|
|
3
3
|
import { codexInspect, codexInstall, codexRemove, } from "./adapter.js";
|
|
4
|
-
import { codexPreparationLocation, inspectCodexPrepared, prepareCodexCandidate, readCodexPrepared, validateCodexPreparationBeforeFetch, } from "./
|
|
5
|
-
import { codexInstallReceipt, codexPresentation
|
|
4
|
+
import { codexPreparationLocation, inspectCodexPrepared, prepareCodexCandidate, readCodexPrepared, validateCodexPreparationBeforeFetch, } from "./prepare.js";
|
|
5
|
+
import { codexInstallReceipt, codexPresentation } from "./presentation.js";
|
|
6
6
|
import { reportLegacyState, requireManagedUpdateControl, requireNoLegacyState, verifyUninstalledResources, } from "./lifecycle.js";
|
|
7
|
-
import { SafetyError } from "
|
|
8
|
-
import { commitMatches } from "
|
|
7
|
+
import { SafetyError } from "../../safety-error.js";
|
|
8
|
+
import { commitMatches } from "../../status.js";
|
|
9
9
|
function preserveFailure(result) {
|
|
10
10
|
if (result.outcome.ok) {
|
|
11
11
|
throw new Error("cannot preserve a successful adapter result as failure");
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { copyFile, cp, mkdir, readdir, readFile, readlink, stat, symlink, } from "node:fs/promises";
|
|
2
2
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
-
import { assertExistingContained, assertProspectiveContained, classifyPathNoFollow, } from "
|
|
4
|
-
import { SafetyError } from "
|
|
5
|
-
import { parseStrictJson } from "
|
|
3
|
+
import { assertExistingContained, assertProspectiveContained, classifyPathNoFollow, } from "../../safe-path.js";
|
|
4
|
+
import { SafetyError } from "../../safety-error.js";
|
|
5
|
+
import { parseStrictJson } from "../../strict-json.js";
|
|
6
6
|
// Annotated, not `as const`, matching
|
|
7
7
|
// `src/provenance.ts:20::export const PROVENANCE_STRICT_PROFILE` and
|
|
8
8
|
// `src/selection-store.ts:19::const SELECTION_JSON_PROFILE`. The annotation
|
|
@@ -77,11 +77,11 @@ export async function readManifest(path) {
|
|
|
77
77
|
catch {
|
|
78
78
|
// Deliberately drops the cause on both branches. `detail(cause)` on the
|
|
79
79
|
// read branch surfaced a raw errno (`ENOENT: … open '<path>'`), which the
|
|
80
|
-
// `hook classification failed:` site in src/adapter.ts re-emits onto the
|
|
80
|
+
// `hook classification failed:` site in src/harnesses/codex/adapter.ts re-emits onto the
|
|
81
81
|
// terminal these commands write to; the parse branch surfaced
|
|
82
|
-
// strict-json's own wording under a prefix src/manifest-overlay.ts also
|
|
82
|
+
// strict-json's own wording under a prefix src/harnesses/codex/manifest-overlay.ts also
|
|
83
83
|
// uses with CPython wording.
|
|
84
|
-
// Same text as the `cannot read manifest JSON in` site in src/adapter.ts.
|
|
84
|
+
// Same text as the `cannot read manifest JSON in` site in src/harnesses/codex/adapter.ts.
|
|
85
85
|
throw hookError(`cannot read manifest JSON in ${path}`);
|
|
86
86
|
}
|
|
87
87
|
let parsed;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SafetyError } from "
|
|
2
|
-
import { parseStrictJson, } from "
|
|
1
|
+
import { SafetyError } from "../../safety-error.js";
|
|
2
|
+
import { parseStrictJson, } from "../../strict-json.js";
|
|
3
3
|
const ACCEPT_CONSTANTS = {
|
|
4
4
|
duplicateKeys: "last-wins",
|
|
5
5
|
nonStandardConstants: "accept",
|
|
@@ -52,7 +52,7 @@ export function reportLegacyState(identityState) {
|
|
|
52
52
|
}
|
|
53
53
|
return unknownState(identityState);
|
|
54
54
|
}
|
|
55
|
-
export { verifyInstalledFingerprint, } from "./
|
|
55
|
+
export { verifyInstalledFingerprint, } from "./presentation.js";
|
|
56
56
|
// Ported from
|
|
57
57
|
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/lifecycle.sh:62-70::spw_require_managed_update_control`.
|
|
58
58
|
// Both refusals reached spw_die in the shell, so both carry the `error: ` prefix
|
|
@@ -101,7 +101,7 @@ export function verifyUninstalledResources(inspectResult) {
|
|
|
101
101
|
// rather than getting its own message.
|
|
102
102
|
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/adapter.sh:70::expected`
|
|
103
103
|
// emits "expected Boolean adapter result at resources.plugin" for input {} —
|
|
104
|
-
// the input tests/unit/lifecycle.test.
|
|
104
|
+
// the input tests/unit/harnesses/codex/lifecycle.test.ts's "a non-object resources falls
|
|
105
105
|
// through to the Boolean message" exercises — so a distinct "not an object"
|
|
106
106
|
// message here would be a port-only divergence. Parity, not divergence. Spec
|
|
107
107
|
// §6.2.3 item 3.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { escapeNonAscii, formatPythonNumber } from "
|
|
2
|
-
import { SafetyError } from "
|
|
3
|
-
import { isRawNumber, isRawObject, parseStrictJsonPreservingNumbers, } from "
|
|
1
|
+
import { escapeNonAscii, formatPythonNumber, } from "../../python-json-format.js";
|
|
2
|
+
import { SafetyError } from "../../safety-error.js";
|
|
3
|
+
import { isRawNumber, isRawObject, parseStrictJsonPreservingNumbers, } from "../../strict-json.js";
|
|
4
4
|
const OVERLAY_PROFILE = {
|
|
5
5
|
duplicateKeys: "last-wins",
|
|
6
6
|
nonStandardConstants: "reject",
|
|
@@ -12,7 +12,7 @@ const OVERLAY_PROFILE = {
|
|
|
12
12
|
* Byte-for-byte equivalent to the CPython implementation this replaces:
|
|
13
13
|
* `json.dump(data, f, indent=2, allow_nan=False)` plus a trailing newline.
|
|
14
14
|
* The byte-parity evidence lives in
|
|
15
|
-
* `tests/baseline/manifest-overlay-parity.test.
|
|
15
|
+
* `tests/baseline/harnesses/codex/manifest-overlay-parity.test.ts` (tracked as
|
|
16
16
|
* `MANIFEST-READER-OVERLAY-01` in `docs/baseline/traceability.md`) against
|
|
17
17
|
* the fixtures under `tests/fixtures/baseline/overlay-parity/`. Known,
|
|
18
18
|
* deliberate divergences from strict byte parity are recorded in
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { cp, mkdir, rm, stat } from "node:fs/promises";
|
|
2
2
|
import { isAbsolute, join, resolve } from "node:path";
|
|
3
|
-
import { failureResult, successResult, } from "
|
|
3
|
+
import { failureResult, successResult, } from "../../adapter-result.js";
|
|
4
4
|
import { codexBuild } from "./adapter.js";
|
|
5
|
-
import { ARTIFACT_RECEIPT } from "
|
|
6
|
-
import { assessCodexCompatibility, readCodexAssessment, writeCodexAssessment, } from "./
|
|
5
|
+
import { ARTIFACT_RECEIPT } from "../../artifact-tree.js";
|
|
6
|
+
import { assessCodexCompatibility, readCodexAssessment, writeCodexAssessment, } from "./compatibility.js";
|
|
7
7
|
import { readManifest } from "./hooks.js";
|
|
8
|
-
import { readGeneratedCommitLenient, readStrictProvenanceField, writeProvenance, } from "
|
|
9
|
-
import { classifyPathNoFollow } from "
|
|
10
|
-
import { SafetyError } from "
|
|
11
|
-
import { manifestVersionForRef } from "
|
|
8
|
+
import { readGeneratedCommitLenient, readStrictProvenanceField, writeProvenance, } from "../../provenance.js";
|
|
9
|
+
import { classifyPathNoFollow } from "../../safe-path.js";
|
|
10
|
+
import { SafetyError } from "../../safety-error.js";
|
|
11
|
+
import { manifestVersionForRef } from "../../upstream-version.js";
|
|
12
12
|
// Order is inherited from the original prepare command; the first miss wins.
|
|
13
13
|
const REQUIRED_UPSTREAM = [
|
|
14
14
|
{ path: "skills", label: "skills/" },
|
|
@@ -1,5 +1,5 @@
|
|
|
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) {
|
|
@@ -116,7 +116,7 @@ export function codexInstallReceipt(missingHint, mismatchHint) {
|
|
|
116
116
|
mismatchVerificationOutput: verificationOutput("mismatch", mismatchHint),
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
|
-
// 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
|
|
120
120
|
// normalizers and renderer so the retained verdict cannot drift from commands.
|
|
121
121
|
export function verifyInstalledFingerprint(desiredCommit, installResult, inspectResult) {
|
|
122
122
|
const receipt = normalizeCodexInstall(installResult);
|
|
@@ -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",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { lstat } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { readArtifactFile, readArtifactObject, validateNativeSkill, } from "
|
|
5
|
-
import { SEMVER_RE } from "
|
|
6
|
-
import { validateSource } from "
|
|
4
|
+
import { readArtifactFile, readArtifactObject, validateNativeSkill, } from "../../artifact-tree.js";
|
|
5
|
+
import { SEMVER_RE } from "../../domain/refs.js";
|
|
6
|
+
import { validateSource } from "../../selection.js";
|
|
7
7
|
const OFFICIAL_SOURCES = new Set([
|
|
8
8
|
"https://github.com/obra/superpowers",
|
|
9
9
|
"https://github.com/obra/superpowers.git",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { failureResult, successResult } from "
|
|
2
|
-
import { classifyPathNoFollow } from "
|
|
3
|
-
import { installPi, removePi } from "./
|
|
4
|
-
import { piPaths } from "./
|
|
5
|
-
import { inspectPiPrepared, piPreparationLocation, preparePiCandidate, readPiPrepared, } from "./
|
|
6
|
-
import { piPresentation } from "./
|
|
7
|
-
import { inspectPiControl, inspectPiInstalled, inspectPiOwnership, } from "./
|
|
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
8
|
export const piHarness = {
|
|
9
9
|
preparationLocation: piPreparationLocation,
|
|
10
10
|
mutationRoots: async (ctx) => [
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { cp, lstat, mkdir, open, readFile, readdir, rm, rmdir, unlink, } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join } from "node:path";
|
|
4
|
-
import { failureResult, successResult, } from "
|
|
5
|
-
import { atomicWriteFile, beginDirectoryPublication, } from "
|
|
6
|
-
import { normalizePiRuntimeVersion, runPi } from "./
|
|
7
|
-
import { digestPiTree, readPiPackageAssessment, readPiReceipt, } from "./
|
|
8
|
-
import { piPaths } from "./
|
|
9
|
-
import { readPiSettings, resolveCanonicalPiLocalSource, } from "./
|
|
10
|
-
import { inspectPiControl, inspectPiOwnership, } from "./
|
|
11
|
-
import { assertNoFollowType, canonicalizeProspectivePath, classifyPathNoFollow, } from "
|
|
4
|
+
import { failureResult, successResult, } from "../../adapter-result.js";
|
|
5
|
+
import { atomicWriteFile, beginDirectoryPublication, } from "../../atomic.js";
|
|
6
|
+
import { normalizePiRuntimeVersion, runPi } from "./native.js";
|
|
7
|
+
import { digestPiTree, readPiPackageAssessment, readPiReceipt, } from "./package.js";
|
|
8
|
+
import { piPaths } from "./paths.js";
|
|
9
|
+
import { readPiSettings, resolveCanonicalPiLocalSource, } from "./settings.js";
|
|
10
|
+
import { inspectPiControl, inspectPiOwnership, } from "./state.js";
|
|
11
|
+
import { assertNoFollowType, canonicalizeProspectivePath, classifyPathNoFollow, } from "../../safe-path.js";
|
|
12
12
|
const DEFAULTS = {
|
|
13
13
|
run: runPi,
|
|
14
14
|
beginPublication: beginDirectoryPublication,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { tmpdir } from "node:os";
|
|
2
2
|
import { isAbsolute, resolve, sep } from "node:path";
|
|
3
|
-
import { AdapterMessageLog, failureResult, successResult, } from "
|
|
4
|
-
import { SEMVER_RE } from "
|
|
5
|
-
import { BOUNDED_EXECUTABLE, runValidator as runBoundedCommand, } from "
|
|
6
|
-
import { withWorkspace } from "
|
|
3
|
+
import { AdapterMessageLog, failureResult, successResult, } from "../../adapter-result.js";
|
|
4
|
+
import { SEMVER_RE } from "../../domain/refs.js";
|
|
5
|
+
import { BOUNDED_EXECUTABLE, runValidator as runBoundedCommand, } from "../../validator.js";
|
|
6
|
+
import { withWorkspace } from "../../workspace.js";
|
|
7
7
|
function selectedExecutable(env, invocationCwd) {
|
|
8
8
|
const configured = env.SUPERPOWERS_PI || "pi";
|
|
9
9
|
return configured.includes(sep) && !isAbsolute(configured)
|
|
@@ -3,12 +3,12 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { chmod, mkdir, symlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
|
-
import { ARTIFACT_DIGEST_RE, ARTIFACT_RECEIPT, addArtifactHashField, digestArtifactTree, readArtifactObject, } from "
|
|
7
|
-
import { COMMIT_RE } from "
|
|
8
|
-
import { assertNoFollowType, assertSymlinkTargetContained, } from "
|
|
9
|
-
import { SafetyError } from "
|
|
10
|
-
import { validateSource } from "
|
|
11
|
-
import { assessPiCompatibility } from "./
|
|
6
|
+
import { ARTIFACT_DIGEST_RE, ARTIFACT_RECEIPT, addArtifactHashField, digestArtifactTree, readArtifactObject, } from "../../artifact-tree.js";
|
|
7
|
+
import { COMMIT_RE } from "../../domain/refs.js";
|
|
8
|
+
import { assertNoFollowType, assertSymlinkTargetContained, } from "../../safe-path.js";
|
|
9
|
+
import { SafetyError } from "../../safety-error.js";
|
|
10
|
+
import { validateSource } from "../../selection.js";
|
|
11
|
+
import { assessPiCompatibility } from "./compatibility.js";
|
|
12
12
|
const exec = promisify(execFile);
|
|
13
13
|
const DECODER = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
14
14
|
// A consistency checksum, not authentication. A coherent manual rewrite of
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { failureResult, successResult, } from "
|
|
4
|
-
import { ARTIFACT_RECEIPT } from "
|
|
5
|
-
import { piPaths } from "./
|
|
6
|
-
import { assessPiCompatibility, samePiSource } from "./
|
|
7
|
-
import { digestPiTree, materializePiTree, readPiPackageAssessment, piReceiptBinding, } from "./
|
|
8
|
-
import { classifyPathNoFollow } from "
|
|
3
|
+
import { failureResult, successResult, } from "../../adapter-result.js";
|
|
4
|
+
import { ARTIFACT_RECEIPT } from "../../artifact-tree.js";
|
|
5
|
+
import { piPaths } from "./paths.js";
|
|
6
|
+
import { assessPiCompatibility, samePiSource } from "./compatibility.js";
|
|
7
|
+
import { digestPiTree, materializePiTree, readPiPackageAssessment, piReceiptBinding, } from "./package.js";
|
|
8
|
+
import { classifyPathNoFollow } from "../../safe-path.js";
|
|
9
9
|
export function piPreparationLocation(ctx) {
|
|
10
10
|
return {
|
|
11
11
|
destinationRoot: piPaths(ctx.env ?? {}, process.cwd()).preparedRoot,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { canonicalizeProspectivePath } from "
|
|
5
|
-
import { SafetyError } from "
|
|
6
|
-
import { parseStrictJson, } from "
|
|
4
|
+
import { canonicalizeProspectivePath } from "../../safe-path.js";
|
|
5
|
+
import { SafetyError } from "../../safety-error.js";
|
|
6
|
+
import { parseStrictJson, } from "../../strict-json.js";
|
|
7
7
|
const SETTINGS_PROFILE = {
|
|
8
8
|
duplicateKeys: "last-wins",
|
|
9
9
|
nonStandardConstants: "reject",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdir, readFile, readlink, realpath } from "node:fs/promises";
|
|
2
2
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep, } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { classifyPathNoFollow } from "
|
|
4
|
+
import { classifyPathNoFollow } from "../../safe-path.js";
|
|
5
5
|
const PI_IGNORE_FILES = [".gitignore", ".ignore", ".fdignore"];
|
|
6
6
|
function toPosixPath(path) {
|
|
7
7
|
return path.split(sep).join("/");
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { sharedPiSkillsActivity } from "./
|
|
3
|
-
import { failureResult, successResult, } from "
|
|
4
|
-
import { readArtifactObject } from "
|
|
5
|
-
import { samePiSource } from "./
|
|
6
|
-
import { digestPiTree, readPiPackageAssessment, readPiReceipt, } from "./
|
|
7
|
-
import { piPaths } from "./
|
|
8
|
-
import { readPiSettings, resolveCanonicalPiLocalSource, resolvePiLocalSource, } from "./
|
|
9
|
-
import { classifyPathNoFollow } from "
|
|
2
|
+
import { sharedPiSkillsActivity } from "./shared-skills.js";
|
|
3
|
+
import { failureResult, successResult, } from "../../adapter-result.js";
|
|
4
|
+
import { readArtifactObject } from "../../artifact-tree.js";
|
|
5
|
+
import { samePiSource } from "./compatibility.js";
|
|
6
|
+
import { digestPiTree, readPiPackageAssessment, readPiReceipt, } from "./package.js";
|
|
7
|
+
import { piPaths } from "./paths.js";
|
|
8
|
+
import { readPiSettings, resolveCanonicalPiLocalSource, resolvePiLocalSource, } from "./settings.js";
|
|
9
|
+
import { classifyPathNoFollow } from "../../safe-path.js";
|
|
10
10
|
// Verified remote spellings only: Pi 0.85.1 accepts http:// as Git. Unprefixed
|
|
11
11
|
// github: and git+ forms stay local; git:-wrapped aliases and explicit transports
|
|
12
12
|
// normalize only an exact GitHub authority and repository path (native source
|
package/dist/python-text.js
CHANGED
|
@@ -72,3 +72,19 @@ export function pythonSplitlines(value) {
|
|
|
72
72
|
lines.push(value.slice(start));
|
|
73
73
|
return lines;
|
|
74
74
|
}
|
|
75
|
+
/** Python `sorted()` orders by code point; JavaScript's default sort does not. */
|
|
76
|
+
export function compareByCodePoint(left, right) {
|
|
77
|
+
// `Array.from` splits by code point, exactly as spreading would; oxlint's
|
|
78
|
+
// `no-misused-spread` rejects the spread form, and grapheme segmentation is
|
|
79
|
+
// the wrong unit here — Python compares code points.
|
|
80
|
+
const leftPoints = Array.from(left);
|
|
81
|
+
const rightPoints = Array.from(right);
|
|
82
|
+
const shared = Math.min(leftPoints.length, rightPoints.length);
|
|
83
|
+
for (let index = 0; index < shared; index += 1) {
|
|
84
|
+
const a = leftPoints[index].codePointAt(0);
|
|
85
|
+
const b = rightPoints[index].codePointAt(0);
|
|
86
|
+
if (a !== b)
|
|
87
|
+
return a - b;
|
|
88
|
+
}
|
|
89
|
+
return leftPoints.length - rightPoints.length;
|
|
90
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { lstat, readdir, readFile, readlink, stat } from "node:fs/promises";
|
|
2
|
+
import { pythonSplitlines, pythonStrip } from "./python-text.js";
|
|
3
|
+
export const DEFAULT_FS_DEPS = {
|
|
4
|
+
lstat,
|
|
5
|
+
stat,
|
|
6
|
+
readdir,
|
|
7
|
+
readlink,
|
|
8
|
+
readFile,
|
|
9
|
+
};
|
|
10
|
+
const STRICT_DECODER = new TextDecoder("utf-8", {
|
|
11
|
+
fatal: true,
|
|
12
|
+
ignoreBOM: true,
|
|
13
|
+
});
|
|
14
|
+
export async function validateSkillFrontmatter(skillMd, skillName, errors, deps) {
|
|
15
|
+
let contents;
|
|
16
|
+
try {
|
|
17
|
+
contents = STRICT_DECODER.decode(await deps.readFile(skillMd));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
errors.push(`skill \`${skillName}\` has unreadable UTF-8 \`SKILL.md\``);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (contents === "") {
|
|
24
|
+
errors.push(`skill \`${skillName}\` has empty \`SKILL.md\``);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const lines = pythonSplitlines(contents);
|
|
28
|
+
if (lines.length === 0 || lines[0] !== "---") {
|
|
29
|
+
errors.push(`skill \`${skillName}\` must start with \`---\``);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const closingIndex = lines.indexOf("---", 1);
|
|
33
|
+
if (closingIndex === -1) {
|
|
34
|
+
errors.push(`skill \`${skillName}\` frontmatter is not closed`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const frontmatter = lines.slice(1, closingIndex);
|
|
38
|
+
for (const key of ["name", "description"]) {
|
|
39
|
+
const matches = frontmatter.filter((line) => line.startsWith(`${key}:`));
|
|
40
|
+
if (matches.length !== 1) {
|
|
41
|
+
errors.push(`skill \`${skillName}\` frontmatter must contain exactly one top-level \`${key}:\``);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const value = pythonStrip(matches[0].slice(matches[0].indexOf(":") + 1));
|
|
45
|
+
if (value === "" ||
|
|
46
|
+
value === "''" ||
|
|
47
|
+
value === '""' ||
|
|
48
|
+
value.startsWith("#")) {
|
|
49
|
+
errors.push(`skill \`${skillName}\` frontmatter field \`${key}\` must be non-empty`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { realpathSync } from "node:fs";
|
|
2
2
|
import { oneLine, parseFlags, UsageError } from "./cli-arguments.js";
|
|
3
|
-
import { validateGeneratedPlugin, } from "./generated-plugin.js";
|
|
3
|
+
import { validateGeneratedPlugin, } from "./harnesses/codex/generated-plugin.js";
|
|
4
4
|
const FLAGS = [
|
|
5
5
|
"plugin-root",
|
|
6
6
|
"source",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superpowers-manager",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "Install
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "Install obra/superpowers directly from upstream for Codex and Pi. 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",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"installer",
|
|
17
17
|
"updater",
|
|
18
18
|
"pi",
|
|
19
|
-
"pi-coding-agent"
|
|
19
|
+
"pi-coding-agent",
|
|
20
|
+
"version-pinning"
|
|
20
21
|
],
|
|
21
22
|
"license": "MIT",
|
|
22
23
|
"type": "module",
|
|
File without changes
|
|
File without changes
|