superpowers-manager 0.3.0 → 0.5.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 +63 -325
- package/dist/adapter.js +91 -46
- package/dist/artifact-tree.js +110 -0
- package/dist/atomic.js +103 -7
- package/dist/cli-arguments.js +37 -0
- package/dist/cli.js +97 -40
- package/dist/codex-compatibility.js +126 -0
- package/dist/codex-conflicts.js +99 -0
- package/dist/codex-harness.js +321 -0
- package/dist/codex-json.js +7 -0
- package/dist/codex-prepare.js +255 -0
- package/dist/codex-presentation.js +282 -0
- package/dist/commands/install.js +187 -173
- package/dist/commands/mutation.js +71 -0
- package/dist/commands/prepare.js +87 -188
- package/dist/commands/probe.js +191 -176
- package/dist/commands/uninstall.js +52 -139
- package/dist/commands/update.js +33 -88
- package/dist/generated-plugin.js +3 -3
- package/dist/harness-compatibility.js +13 -0
- package/dist/harness.js +1 -0
- package/dist/lifecycle.js +1 -86
- package/dist/manifest-overlay.js +2 -2
- package/dist/pi-compatibility.js +73 -0
- package/dist/pi-harness.js +63 -0
- package/dist/pi-install.js +495 -0
- package/dist/pi-native.js +81 -0
- package/dist/pi-package.js +153 -0
- package/dist/pi-paths.js +30 -0
- package/dist/pi-prepare.js +111 -0
- package/dist/pi-presentation.js +67 -0
- package/dist/pi-settings.js +171 -0
- package/dist/pi-shared-skills.js +413 -0
- package/dist/pi-state.js +311 -0
- package/dist/resource-lock.js +265 -0
- package/dist/safe-path.js +21 -5
- package/dist/strict-json.js +1 -1
- package/dist/upstream-workspace.js +8 -0
- package/dist/validator.js +3 -2
- package/package.json +19 -18
- package/bin/superpowers-manager.js +0 -16
package/dist/commands/prepare.js
CHANGED
|
@@ -1,39 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname, isAbsolute, join,
|
|
1
|
+
import { mkdir, stat } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, join, sep } from "node:path";
|
|
3
3
|
import { atomicReplaceDir } from "../atomic.js";
|
|
4
4
|
import { oneLine } from "../cli-arguments.js";
|
|
5
5
|
import { computeEffectiveSelection } from "../effective-selection.js";
|
|
6
6
|
import { runGit } from "../git.js";
|
|
7
|
-
import { readManifest } from "../hooks.js";
|
|
8
|
-
import { writeProvenance } from "../provenance.js";
|
|
9
7
|
import { SafetyError } from "../safety-error.js";
|
|
10
|
-
import { manifestVersionForRef } from "../upstream-version.js";
|
|
11
8
|
import { fetchExactCommit, gitSafeSource } from "../upstream.js";
|
|
9
|
+
import { upstreamCacheRoot } from "../upstream-workspace.js";
|
|
12
10
|
import { BOUNDED_EXECUTABLE, UNBOUNDED_LEGACY, launchFailureMessage, resolveValidator, runValidator, displayPath, } from "../validator.js";
|
|
13
11
|
import { withWorkspace, workspaceRemovalFailure } from "../workspace.js";
|
|
14
12
|
import { replayOutcome } from "./probe.js";
|
|
15
|
-
|
|
16
|
-
// the first miss wins.
|
|
17
|
-
const REQUIRED_UPSTREAM = [
|
|
18
|
-
{ path: "skills", label: "skills/" },
|
|
19
|
-
{ path: "LICENSE", label: "LICENSE" },
|
|
20
|
-
{ path: "README.md", label: "README.md" },
|
|
21
|
-
{ path: "CODE_OF_CONDUCT.md", label: "CODE_OF_CONDUCT.md" },
|
|
22
|
-
];
|
|
23
|
-
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/prepare:73-77::spw_copy_path_if_present "$cache/skills`. Same names on both sides.
|
|
24
|
-
const COPY_PATHS = [
|
|
25
|
-
"skills",
|
|
26
|
-
"assets",
|
|
27
|
-
"LICENSE",
|
|
28
|
-
"README.md",
|
|
29
|
-
"CODE_OF_CONDUCT.md",
|
|
30
|
-
];
|
|
31
|
-
const RESOLUTION_KINDS = [
|
|
32
|
-
"latest-release",
|
|
33
|
-
"tag",
|
|
34
|
-
"ref",
|
|
35
|
-
"raw-commit",
|
|
36
|
-
];
|
|
13
|
+
import { runWithMutation } from "./mutation.js";
|
|
37
14
|
// Every message this module writes is hand-written here. The cause is attached
|
|
38
15
|
// for debuggability and never reaches a stream: oneLine (src/cli-arguments.ts)
|
|
39
16
|
// reads .message only. Same arrangement as hookError
|
|
@@ -41,28 +18,10 @@ const RESOLUTION_KINDS = [
|
|
|
41
18
|
function prepareError(message, cause) {
|
|
42
19
|
return new SafetyError("prepare", message, { cause });
|
|
43
20
|
}
|
|
44
|
-
// `[ -
|
|
45
|
-
// Two call sites, each mirroring a distinct `-e` in the shell: the
|
|
46
|
-
// REQUIRED_UPSTREAM loop
|
|
47
|
-
// (`git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/common.sh:53-59::spw_require_upstream_path`
|
|
48
|
-
// — e.g. skills/ is a directory, not a regular file) and copyPathIfPresent's
|
|
49
|
-
// guard (`git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/common.sh:44-51::spw_copy_path_if_present`). The `.git`
|
|
50
|
-
// check below is NOT a third site: it needs `-d` (F1), not `-e`, and uses
|
|
51
|
-
// directoryExists instead.
|
|
52
|
-
async function pathExists(path) {
|
|
53
|
-
try {
|
|
54
|
-
await stat(path);
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// `[ -f ]` — regular file. `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/prepare:42::missing`, :80, and :108 all use -f, and
|
|
21
|
+
// `[ -f ]` — regular file. `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/prepare:108::[ -f "$additional_validator` uses -f, and
|
|
62
22
|
// tests/baseline/cli-parity.test.js's "CLI-ENV-MANIFEST-TEMPLATE-01 fallback
|
|
63
|
-
// template bytes and non-file rejection"
|
|
64
|
-
//
|
|
65
|
-
// stat-only predicate would accept it.
|
|
23
|
+
// template bytes and non-file rejection" also covers the separately extracted
|
|
24
|
+
// fallback check.
|
|
66
25
|
async function regularFileExists(path) {
|
|
67
26
|
try {
|
|
68
27
|
return (await stat(path)).isFile();
|
|
@@ -84,8 +43,8 @@ async function directoryExists(path) {
|
|
|
84
43
|
return false;
|
|
85
44
|
}
|
|
86
45
|
}
|
|
87
|
-
// mkdir
|
|
88
|
-
//
|
|
46
|
+
// mkdir throws raw ErrnoExceptions. Every command-owned call goes through here
|
|
47
|
+
// so the message on the stream is this module's, not Node's.
|
|
89
48
|
async function owned(message, fn) {
|
|
90
49
|
try {
|
|
91
50
|
return await fn();
|
|
@@ -94,50 +53,6 @@ async function owned(message, fn) {
|
|
|
94
53
|
throw prepareError(message, cause);
|
|
95
54
|
}
|
|
96
55
|
}
|
|
97
|
-
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/common.sh:44-51::spw_copy_path_if_present`.
|
|
98
|
-
// `cp -R` copies symlinks AS symlinks;
|
|
99
|
-
// fs.cp's default rewrites relative link targets against the destination,
|
|
100
|
-
// which would change what the adapter's containment checks see.
|
|
101
|
-
async function copyPathIfPresent(source, destination) {
|
|
102
|
-
if (!(await pathExists(source)))
|
|
103
|
-
return;
|
|
104
|
-
await owned(`cannot clear candidate path: ${destination}`, () => rm(destination, { recursive: true, force: true }));
|
|
105
|
-
await owned(`cannot copy upstream path into candidate: ${source}`, () => cp(source, destination, { recursive: true, verbatimSymlinks: true }));
|
|
106
|
-
}
|
|
107
|
-
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/prepare:17-24::case "$cache_parent`. Only cache_parent and plugin_root are resolved against
|
|
108
|
-
// the invocation cwd; manifest_template (:14) is not.
|
|
109
|
-
function resolveFromCwd(value, cwd) {
|
|
110
|
-
return isAbsolute(value) ? value : resolve(cwd, value);
|
|
111
|
-
}
|
|
112
|
-
// readManifest (`src/hooks.ts:113::readManifest`) owns the read and the parse completely: byte
|
|
113
|
-
// read so invalid UTF-8 is rejected rather than replaced, cause dropped, three
|
|
114
|
-
// hand-written messages naming the path, object check included. Its
|
|
115
|
-
// diagnostics are pinned by
|
|
116
|
-
// `tests/unit/hooks.test.js:108::void test("readManifest diagnostics`. This wrapper adds only
|
|
117
|
-
// the `version` type check.
|
|
118
|
-
export async function readUpstreamManifestVersion(path) {
|
|
119
|
-
const manifest = await readManifest(path);
|
|
120
|
-
const value = manifest.version;
|
|
121
|
-
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/provenance.sh:56-62::for part in dotted_key.split`
|
|
122
|
-
// — absent key and JSON null both yield "".
|
|
123
|
-
if (value === undefined || value === null)
|
|
124
|
-
return "";
|
|
125
|
-
if (typeof value !== "string") {
|
|
126
|
-
// The shell stringified any other type through Python's print()
|
|
127
|
-
// (`git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/provenance.sh:62::print(value`), so `"version": 6` became "6" and flowed into both the
|
|
128
|
-
// provenance record and --upstream-manifest-version. Fail closed instead.
|
|
129
|
-
// Spec divergence 7.
|
|
130
|
-
throw prepareError(`upstream manifest version is not a string: ${path}`);
|
|
131
|
-
}
|
|
132
|
-
return value;
|
|
133
|
-
}
|
|
134
|
-
function asResolutionKind(value) {
|
|
135
|
-
for (const kind of RESOLUTION_KINDS) {
|
|
136
|
-
if (kind === value)
|
|
137
|
-
return kind;
|
|
138
|
-
}
|
|
139
|
-
throw prepareError(`unknown upstream resolution kind: ${value}`);
|
|
140
|
-
}
|
|
141
56
|
// Each stream's marker goes to that stream's own destination, so a reader of either
|
|
142
57
|
// sees its own truncation and neither is silently short.
|
|
143
58
|
function withTruncationMarker(captured, stream) {
|
|
@@ -167,34 +82,66 @@ function disclosureLine(resolution) {
|
|
|
167
82
|
return `[superpowers-manager: running external validator ${configured}${via} -> ${displayPath(resolution.resolved)}]\n`;
|
|
168
83
|
}
|
|
169
84
|
const NO_VALIDATOR_OUTPUT = { stdout: "", stderr: "" };
|
|
85
|
+
function validStagingLeaf(value) {
|
|
86
|
+
return (value.length > 0 &&
|
|
87
|
+
value !== "." &&
|
|
88
|
+
value !== ".." &&
|
|
89
|
+
!value.includes("/") &&
|
|
90
|
+
!value.includes("\\"));
|
|
91
|
+
}
|
|
170
92
|
async function gatherPrepare(ctx) {
|
|
171
93
|
const env = ctx.env;
|
|
172
94
|
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/prepare:16::invocation_root=` — captured before the two case statements below.
|
|
173
95
|
// getcwd(3) returns the physical path, so this matches `pwd -P` without a
|
|
174
96
|
// realpath call.
|
|
175
97
|
const cwd = process.cwd();
|
|
176
|
-
const
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
|
|
98
|
+
const cache = upstreamCacheRoot(ctx.root, env, cwd);
|
|
99
|
+
const cacheParent = dirname(cache);
|
|
100
|
+
const adapterContext = { root: ctx.root, env };
|
|
101
|
+
let location;
|
|
102
|
+
try {
|
|
103
|
+
location = ctx.adapter.preparationLocation(adapterContext);
|
|
104
|
+
}
|
|
105
|
+
catch (cause) {
|
|
106
|
+
throw prepareError("cannot determine preparation location", cause);
|
|
107
|
+
}
|
|
108
|
+
if (!isAbsolute(location.destinationRoot)) {
|
|
109
|
+
throw prepareError("adapter returned a non-absolute preparation destination");
|
|
110
|
+
}
|
|
111
|
+
if (!validStagingLeaf(location.stagingLeaf)) {
|
|
112
|
+
throw prepareError("adapter returned an invalid preparation staging leaf");
|
|
113
|
+
}
|
|
114
|
+
const pluginRoot = location.destinationRoot;
|
|
180
115
|
const additionalValidator = env.SUPERPOWERS_VALIDATOR || "";
|
|
181
116
|
const executableValidator = env.SUPERPOWERS_VALIDATOR_EXECUTABLE || "";
|
|
182
|
-
const cache = join(cacheParent, "superpowers");
|
|
183
117
|
const tmpParent = dirname(pluginRoot);
|
|
184
118
|
await owned(`cannot create directory: ${tmpParent}`, () => mkdir(tmpParent, { recursive: true }));
|
|
185
119
|
let cleanupWarning = null;
|
|
186
120
|
const outcome = await withWorkspace(tmpParent, ".superpowers.prepare.", async (workspace) => {
|
|
121
|
+
const outcomes = [];
|
|
187
122
|
const failed = (message) => ({
|
|
188
123
|
kind: "failed",
|
|
189
|
-
outcomes
|
|
124
|
+
outcomes,
|
|
190
125
|
validator: NO_VALIDATOR_OUTPUT,
|
|
191
126
|
message,
|
|
192
127
|
});
|
|
193
|
-
const candidate = join(workspace,
|
|
194
|
-
const selection = await computeEffectiveSelection(ctx.root, env);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
128
|
+
const candidate = join(workspace, location.stagingLeaf);
|
|
129
|
+
const selection = ctx.selection ?? (await computeEffectiveSelection(ctx.root, env));
|
|
130
|
+
let prefetch;
|
|
131
|
+
try {
|
|
132
|
+
prefetch =
|
|
133
|
+
await ctx.adapter.validatePreparationBeforeFetch(adapterContext);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return failed(ctx.adapter.presentation.callFailure("prepare", adapterContext)
|
|
137
|
+
.unexpected);
|
|
138
|
+
}
|
|
139
|
+
outcomes.push(prefetch.outcome);
|
|
140
|
+
if (!prefetch.outcome.ok)
|
|
141
|
+
return failed(null);
|
|
142
|
+
if (prefetch.status !== 0) {
|
|
143
|
+
return failed(ctx.adapter.presentation.callFailure("prepare", adapterContext)
|
|
144
|
+
.invalidStatus);
|
|
198
145
|
}
|
|
199
146
|
await owned(`cannot create directory: ${cacheParent}`, () => mkdir(cacheParent, { recursive: true }));
|
|
200
147
|
if (selection.selectionMode === "pinned") {
|
|
@@ -237,86 +184,34 @@ async function gatherPrepare(ctx) {
|
|
|
237
184
|
if (checkedOut.status !== 0) {
|
|
238
185
|
return failed(`cannot check out upstream commit: ${selection.desiredCommit}`);
|
|
239
186
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
187
|
+
let prepared;
|
|
188
|
+
try {
|
|
189
|
+
prepared = await ctx.adapter.prepareCandidate({
|
|
190
|
+
upstreamRoot: cache,
|
|
191
|
+
workspaceRoot: workspace,
|
|
192
|
+
candidateRoot: candidate,
|
|
193
|
+
selection,
|
|
194
|
+
}, adapterContext);
|
|
246
195
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
await copyPathIfPresent(join(cache, name), join(candidate, name));
|
|
196
|
+
catch {
|
|
197
|
+
return failed(ctx.adapter.presentation.callFailure("prepare", adapterContext)
|
|
198
|
+
.unexpected);
|
|
251
199
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
200
|
+
outcomes.push(prepared.outcome);
|
|
201
|
+
if (prepared.status !== 0 || !prepared.outcome.ok) {
|
|
202
|
+
return failed(prepared.outcome.ok
|
|
203
|
+
? ctx.adapter.presentation.callFailure("prepare", adapterContext)
|
|
204
|
+
.invalidStatus
|
|
205
|
+
: null);
|
|
258
206
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
requested_ref: selection.requestedRef,
|
|
262
|
-
resolved_ref: selection.resolvedRef,
|
|
263
|
-
commit: selection.desiredCommit,
|
|
264
|
-
upstream_manifest_version: upstreamManifestVersion,
|
|
265
|
-
});
|
|
266
|
-
const managerVersion = manifestVersionForRef({
|
|
267
|
-
requestedRef: selection.requestedRef,
|
|
268
|
-
resolutionKind: asResolutionKind(selection.resolutionKind),
|
|
269
|
-
resolvedRef: selection.resolvedRef,
|
|
270
|
-
commit: selection.desiredCommit,
|
|
271
|
-
});
|
|
272
|
-
let built;
|
|
273
|
-
try {
|
|
274
|
-
built = await ctx.adapter([
|
|
275
|
-
"build",
|
|
276
|
-
"--upstream-root",
|
|
277
|
-
cache,
|
|
278
|
-
"--candidate-root",
|
|
279
|
-
candidate,
|
|
280
|
-
"--requested-ref",
|
|
281
|
-
selection.requestedRef,
|
|
282
|
-
"--resolved-ref",
|
|
283
|
-
selection.resolvedRef,
|
|
284
|
-
"--commit",
|
|
285
|
-
selection.desiredCommit,
|
|
286
|
-
"--manager-version",
|
|
287
|
-
managerVersion,
|
|
288
|
-
"--upstream-manifest-version",
|
|
289
|
-
upstreamManifestVersion,
|
|
290
|
-
"--fallback-manifest",
|
|
291
|
-
manifestTemplate,
|
|
292
|
-
], { root: ctx.root, env });
|
|
207
|
+
if (prepared.outcome.result.root !== candidate) {
|
|
208
|
+
return failed("adapter returned an unexpected preparation root");
|
|
293
209
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
// (`src/adapter-result.ts:32-35::export interface AdapterResult`) but still THROWS for a
|
|
297
|
-
// non-AdapterFailure cause (runAdapter's closing `throw cause`,
|
|
298
|
-
// src/adapter.ts). That cause is by construction the one failure
|
|
299
|
-
// src/adapter.ts declined to own, so its text must never reach
|
|
300
|
-
// ctx.stderr. Caught here rather than in runPrepare's outer catch,
|
|
301
|
-
// the same treatment
|
|
302
|
-
// `src/commands/probe.ts:205-228::It does still THROW for a non-AdapterFailure cause`
|
|
303
|
-
// gives it.
|
|
304
|
-
return failed("cannot build the generated plugin candidate");
|
|
210
|
+
if (prepared.outcome.result.commit !== selection.desiredCommit) {
|
|
211
|
+
return failed("adapter returned an unexpected preparation commit");
|
|
305
212
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return {
|
|
309
|
-
kind: "failed",
|
|
310
|
-
outcomes,
|
|
311
|
-
validator: NO_VALIDATOR_OUTPUT,
|
|
312
|
-
// replayOutcome emits the adapter's own error and hints; a second
|
|
313
|
-
// line here would duplicate them. The `ok && status !== 0`
|
|
314
|
-
// combination cannot arise from successResult/failureResult, so it
|
|
315
|
-
// gets its own hand-written message rather than a silent replay.
|
|
316
|
-
message: built.outcome.ok
|
|
317
|
-
? "adapter reported failure without an error outcome"
|
|
318
|
-
: null,
|
|
319
|
-
};
|
|
213
|
+
if (prepared.outcome.result.compatibility.kind === "unsupported") {
|
|
214
|
+
return failed(prepared.outcome.result.compatibility.reason);
|
|
320
215
|
}
|
|
321
216
|
let validator = NO_VALIDATOR_OUTPUT;
|
|
322
217
|
if (additionalValidator.length > 0) {
|
|
@@ -391,11 +286,12 @@ async function gatherPrepare(ctx) {
|
|
|
391
286
|
// The swap must run inside the workspace callback: withWorkspace removes
|
|
392
287
|
// the workspace on return, and the candidate lives in it.
|
|
393
288
|
//
|
|
394
|
-
// atomicReplaceDir
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
// callee's message
|
|
289
|
+
// atomicReplaceDir delegates to beginDirectoryPublication, whose outer
|
|
290
|
+
// catch (`src/atomic.ts:341-348::if (cause`) wraps every non-SafetyError
|
|
291
|
+
// into a SafetyError, so the callee owns every failure on this path and
|
|
292
|
+
// re-emitting its own diagnostic is the sanctioned form of interpolation.
|
|
293
|
+
// The hand-written prefix carries the live root, which the callee's message
|
|
294
|
+
// does not.
|
|
399
295
|
try {
|
|
400
296
|
await atomicReplaceDir(candidate, pluginRoot);
|
|
401
297
|
}
|
|
@@ -426,6 +322,9 @@ async function gatherPrepare(ctx) {
|
|
|
426
322
|
return { outcome, cleanupWarning };
|
|
427
323
|
}
|
|
428
324
|
export async function runPrepare(argv, ctx) {
|
|
325
|
+
return await runWithMutation("prepare", ctx, async (scoped) => performPrepare(argv, scoped));
|
|
326
|
+
}
|
|
327
|
+
async function performPrepare(argv, ctx) {
|
|
429
328
|
// scripts/prepare never reads "$@", so extra arguments are ignored. This is a
|
|
430
329
|
// deliberate asymmetry with probe, whose shell original rejected unknown
|
|
431
330
|
// arguments and whose arity therefore moved into parseArgs in slice 2.
|
|
@@ -443,7 +342,7 @@ export async function runPrepare(argv, ctx) {
|
|
|
443
342
|
// spawn error, which oneLine never reads -- it takes .message only);
|
|
444
343
|
// readManifest's three hookError messages
|
|
445
344
|
// (`src/hooks.ts:113-138::readManifest`), pinned by
|
|
446
|
-
// `tests/unit/hooks.test.
|
|
345
|
+
// `tests/unit/hooks.test.ts:95::void test("readManifest diagnostics` as carrying no reader vocabulary or
|
|
447
346
|
// errno; and SafetyErrors from gitSafeSource, writeProvenance, and
|
|
448
347
|
// withWorkspace.
|
|
449
348
|
//
|
|
@@ -454,7 +353,7 @@ export async function runPrepare(argv, ctx) {
|
|
|
454
353
|
// This is the DEFAULT invocation -- plain `prepare`, `track-latest`,
|
|
455
354
|
// and any non-40-hex SUPERPOWERS_REF -- not an exotic corner. Pinned
|
|
456
355
|
// by
|
|
457
|
-
// `tests/unit/upstream.test.
|
|
356
|
+
// `tests/unit/upstream.test.ts:453-462::void test("resolveRef reports a query failure for latest`,
|
|
458
357
|
// :471-481, and :483-501.
|
|
459
358
|
// 2. fetchExactCommit splices the same combined stdout+stderr into its
|
|
460
359
|
// own text on the PINNED path (both of its own splice sites in
|
|
@@ -519,7 +418,7 @@ export async function runPrepare(argv, ctx) {
|
|
|
519
418
|
// completed before cleanup ran, so it is not being reported as unverified
|
|
520
419
|
// -- but something did still go wrong, and AGENTS.md's fail-closed rule
|
|
521
420
|
// extends to it. Mirrors
|
|
522
|
-
// `src/commands/install.ts:
|
|
421
|
+
// `src/commands/install.ts:546-554::if (cleanupWarning`.
|
|
523
422
|
ctx.stderr.write(`error: ${cleanupWarning}\n`);
|
|
524
423
|
return 1;
|
|
525
424
|
}
|