superpowers-manager 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +36 -16
  2. package/dist/atomic.js +1 -3
  3. package/dist/cli-arguments.js +4 -1
  4. package/dist/cli.js +14 -43
  5. package/dist/commands/adapter-call.js +24 -0
  6. package/dist/commands/install.js +16 -63
  7. package/dist/commands/mutation.js +5 -5
  8. package/dist/commands/pin.js +10 -5
  9. package/dist/commands/prepare.js +42 -108
  10. package/dist/commands/probe.js +33 -19
  11. package/dist/commands/uninstall.js +18 -46
  12. package/dist/commands/unpin.js +2 -1
  13. package/dist/commands/update.js +8 -29
  14. package/dist/domain/refs.js +0 -3
  15. package/dist/effective-selection.js +11 -15
  16. package/dist/git-tree.js +4 -6
  17. package/dist/harness-command-result.js +33 -3
  18. package/dist/harnesses/claude-code/harness.js +39 -0
  19. package/dist/harnesses/claude-code/install.js +380 -0
  20. package/dist/harnesses/claude-code/native.js +79 -0
  21. package/dist/harnesses/claude-code/paths.js +35 -0
  22. package/dist/harnesses/claude-code/prepare.js +117 -0
  23. package/dist/harnesses/claude-code/presentation.js +25 -0
  24. package/dist/harnesses/claude-code/state.js +253 -0
  25. package/dist/harnesses/codex/adapter.js +27 -68
  26. package/dist/harnesses/codex/conflicts.js +3 -8
  27. package/dist/harnesses/codex/generated-plugin.js +4 -11
  28. package/dist/harnesses/codex/harness.js +4 -26
  29. package/dist/harnesses/codex/hooks.js +11 -27
  30. package/dist/harnesses/codex/lifecycle.js +22 -107
  31. package/dist/harnesses/codex/manifest-overlay.js +9 -27
  32. package/dist/harnesses/codex/paths.js +29 -0
  33. package/dist/harnesses/codex/prepare.js +9 -43
  34. package/dist/harnesses/codex/presentation.js +1 -1
  35. package/dist/harnesses/codex/publication.js +5 -40
  36. package/dist/harnesses/codex/state.js +1 -6
  37. package/dist/harnesses/codex/stored-state.js +1 -6
  38. package/dist/harnesses/opencode/config.js +2 -4
  39. package/dist/harnesses/opencode/discovery.js +1 -5
  40. package/dist/harnesses/opencode/install.js +12 -93
  41. package/dist/harnesses/opencode/native.js +28 -49
  42. package/dist/harnesses/opencode/package.js +11 -55
  43. package/dist/harnesses/opencode/prepare.js +2 -7
  44. package/dist/harnesses/opencode/presentation.js +2 -7
  45. package/dist/harnesses/pi/install.js +31 -120
  46. package/dist/harnesses/pi/native.js +9 -34
  47. package/dist/harnesses/pi/package.js +9 -50
  48. package/dist/harnesses/pi/prepare.js +3 -8
  49. package/dist/harnesses/pi/presentation.js +2 -7
  50. package/dist/harnesses/pi/settings.js +5 -9
  51. package/dist/harnesses/pi/shared-skills.js +23 -53
  52. package/dist/harnesses/pi/state.js +3 -10
  53. package/dist/provenance.js +3 -19
  54. package/dist/resource-lock.js +11 -16
  55. package/dist/safe-path.js +28 -1
  56. package/dist/selection.js +15 -19
  57. package/dist/snapshot-journal.js +101 -0
  58. package/dist/snapshot-package.js +62 -1
  59. package/dist/snapshot-prepare.js +9 -2
  60. package/dist/snapshot-presentation.js +9 -0
  61. package/dist/upstream.js +8 -12
  62. package/dist/validator.js +26 -23
  63. package/dist/workspace.js +29 -35
  64. package/package.json +10 -9
  65. package/dist/python-json.js +0 -4
  66. package/dist/upstream-workspace.js +0 -8
package/dist/workspace.js CHANGED
@@ -3,17 +3,11 @@ import { mkdtemp, rm } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { SafetyError } from "./safety-error.js";
5
5
  const MANAGED_SIGNALS = ["SIGHUP", "SIGINT", "SIGTERM"];
6
- // Each active workspace carries its caller's failure reporter. The signal
7
- // path still invokes it, for parity with the normal path, but a signal death
8
- // never builds a result outcome (see src/adapter-result.ts), so a report
9
- // that only lands in the adapter's buffered log would never become
10
- // observable — the signal path therefore also writes the hand-written
11
- // diagnostic straight to process.stderr, unconditionally; that raw write is
12
- // the only thing actually visible to a caller. Previously the signal path
13
- // called the module-private cleanup and swallowed the rejection in
14
- // Promise.allSettled, so a signal-time failure was silent entirely — this is
15
- // the state PR 11.4 removed from the normal path.
16
- const active = new Map();
6
+ // Workspaces the signal path must remove. A signal death never builds a
7
+ // result outcome (see src/adapter-result.ts), so the signal path writes its
8
+ // hand-written diagnostic straight to process.stderr; that write is the only
9
+ // report a caller can observe.
10
+ const active = new Set();
17
11
  let exiting = false;
18
12
  const handlers = new Map();
19
13
  // One phrasing for one condition. The adapter re-emits this for a suppressed
@@ -40,29 +34,18 @@ function cleanupForSignal(signal) {
40
34
  if (exiting)
41
35
  return;
42
36
  exiting = true;
43
- for (const [path, report] of active) {
37
+ for (const path of active) {
44
38
  try {
45
39
  rmSync(path, { recursive: true, force: true });
46
40
  }
47
41
  catch {
48
- // The reporter is invoked for parity with the normal path, but nothing
49
- // ever reads it back here — no outcome is built before the process
50
- // dies — so the diagnostic is also written straight to stderr,
51
- // unconditionally; that write is the only thing actually observable.
52
42
  // The diagnostic is hand-written and names the workspace; the caught
53
- // cause is not interpolated.
54
- //
55
- // Reporting is guarded: if either call throws — plausibly EPIPE after
56
- // SIGHUP, when the process on the other end of the pipe is already
57
- // gone — the exception must not escape this loop, or the remaining
58
- // workspaces' cleanup, deregistration, and the re-raise below never run.
43
+ // cause is not interpolated. Guarded: if the write throws — plausibly
44
+ // EPIPE after SIGHUP — the remaining workspaces' cleanup,
45
+ // deregistration, and the re-raise below must still run. Node documents
46
+ // pipe writes as asynchronous on some platforms (notably macOS); small
47
+ // buffers like this one are written inline.
59
48
  try {
60
- if (report)
61
- report(path);
62
- // Node documents pipe writes as asynchronous on some platforms
63
- // (notably macOS); a saturated pipe could drop this write. Small
64
- // buffers like this one are written inline, which is why the
65
- // covering test observes it reliably.
66
49
  process.stderr.write(`${workspaceRemovalFailure(path)}\n`);
67
50
  }
68
51
  catch {
@@ -96,7 +79,7 @@ function deregisterCoordinator() {
96
79
  process.off(signal, handler);
97
80
  handlers.clear();
98
81
  }
99
- export async function withWorkspace(parent, prefix, fn, options = {}) {
82
+ async function runInWorkspace(parent, prefix, fn, options, reportCleanupFailure) {
100
83
  const remove = options.cleanup ?? cleanup;
101
84
  let workspace;
102
85
  try {
@@ -105,35 +88,46 @@ export async function withWorkspace(parent, prefix, fn, options = {}) {
105
88
  catch (cause) {
106
89
  throw new SafetyError("workspace", "cannot create workspace", { cause });
107
90
  }
108
- active.set(workspace, options.onCleanupFailure);
91
+ active.add(workspace);
109
92
  registerCoordinator();
110
93
  try {
111
- let result;
94
+ let value;
112
95
  let failed = false;
113
96
  let callbackError;
114
97
  try {
115
- result = await fn(workspace);
98
+ value = await fn(workspace);
116
99
  }
117
100
  catch (error) {
118
101
  failed = true;
119
102
  callbackError = error;
120
103
  }
104
+ let cleanupWarning = null;
121
105
  try {
122
106
  await remove(workspace);
123
107
  }
124
108
  catch (cleanupError) {
125
109
  if (failed)
126
110
  throw callbackError;
127
- if (options.onCleanupFailure === undefined)
111
+ if (!reportCleanupFailure)
128
112
  throw cleanupError;
129
- options.onCleanupFailure(workspace);
113
+ cleanupWarning = workspaceRemovalFailure(workspace);
130
114
  }
131
115
  if (failed)
132
116
  throw callbackError;
133
- return result;
117
+ return { value, cleanupWarning };
134
118
  }
135
119
  finally {
136
120
  active.delete(workspace);
137
121
  deregisterCoordinator();
138
122
  }
139
123
  }
124
+ export async function withWorkspace(parent, prefix, fn, options = {}) {
125
+ return (await runInWorkspace(parent, prefix, fn, options, false)).value;
126
+ }
127
+ // Like withWorkspace, except a cleanup failure after a SUCCESSFUL callback is
128
+ // returned as `cleanupWarning` instead of thrown, so the caller keeps the value
129
+ // it already computed and reports the leak itself. A callback throw still
130
+ // propagates, and a cleanup failure never masks it.
131
+ export function withWorkspaceReporting(parent, prefix, fn, options = {}) {
132
+ return runInWorkspace(parent, prefix, fn, options, true);
133
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "superpowers-manager",
3
- "version": "0.8.0",
4
- "description": "Install obra/superpowers directly from upstream for Codex, Pi, and OpenCode. Stay on a known-good release or commit, try a branch, and upgrade when you choose.",
3
+ "version": "0.9.0",
4
+ "description": "Install obra/superpowers directly from upstream for Codex, Pi, OpenCode, and Claude Code. Stay on a known-good release or commit, try a branch, and upgrade when you choose.",
5
5
  "keywords": [
6
6
  "superpowers",
7
7
  "obra-superpowers",
@@ -18,21 +18,22 @@
18
18
  "pi",
19
19
  "pi-coding-agent",
20
20
  "opencode",
21
+ "claude-code",
22
+ "claude-code-plugin",
21
23
  "version-pinning"
22
24
  ],
23
25
  "license": "MIT",
24
26
  "type": "module",
25
- "packageManager": "pnpm@12.4.0",
27
+ "packageManager": "pnpm@12.4.2",
26
28
  "scripts": {
27
- "lint": "oxlint src/ tests/bin/ tests/unit/ tests/baseline/ tests/lib/ tests/tools/ tests/container/ tests/run-node-suites.ts tests/assert-matcher-gate.ts",
28
- "format": "prettier --write src/ tests/bin/ tests/unit/ tests/baseline/ tests/lib/ tests/tools/ tests/container/ tests/run-node-suites.ts tests/assert-matcher-gate.ts",
29
- "format:check": "prettier --check src/ tests/bin/ tests/unit/ tests/baseline/ tests/lib/ tests/tools/ tests/container/ tests/run-node-suites.ts tests/assert-matcher-gate.ts",
30
- "test": "node tests/run-node-suites.ts",
31
- "test:unit": "node tests/run-node-suites.ts --group unit",
32
- "test:integration": "node tests/run-node-suites.ts --group integration",
29
+ "lint": "oxlint src/ tests/",
30
+ "format": "prettier --write src/ tests/",
31
+ "format:check": "prettier --check src/ tests/",
32
+ "test": "sh tests/run.sh",
33
33
  "test:harness:codex": "sh tests/container.sh harness-codex",
34
34
  "test:harness:pi": "sh tests/container.sh harness-pi",
35
35
  "test:harness:opencode": "sh tests/container.sh harness-opencode",
36
+ "test:harness:claude-code": "sh tests/container.sh harness-claude-code",
36
37
  "test:acceptance": "sh tests/acceptance.sh",
37
38
  "check:static": "pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run knip",
38
39
  "check": "pnpm run check:static && pnpm run test",
@@ -1,4 +0,0 @@
1
- import { escapeNonAscii } from "./python-json-format.js";
2
- export function escapePythonJsonString(value) {
3
- return escapeNonAscii(JSON.stringify(value).slice(1, -1));
4
- }
@@ -1,8 +0,0 @@
1
- import { isAbsolute, join, resolve } from "node:path";
2
- export function upstreamCacheRoot(root, env, cwd) {
3
- const configured = env.SUPERPOWERS_CACHE_DIR || join(root, ".cache", "upstream");
4
- const cacheParent = isAbsolute(configured)
5
- ? configured
6
- : resolve(cwd, configured);
7
- return join(cacheParent, "superpowers");
8
- }