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/probe.js
CHANGED
|
@@ -1,115 +1,7 @@
|
|
|
1
1
|
import { assertFailureWritable, writeAdapterFailure, } from "../adapter-result.js";
|
|
2
2
|
import { oneLine } from "../cli-arguments.js";
|
|
3
3
|
import { computeEffectiveSelection } from "../effective-selection.js";
|
|
4
|
-
import { generatedCommitOrEmpty } from "../provenance.js";
|
|
5
|
-
import { displaySource } from "../selection.js";
|
|
6
|
-
import { statusForCommits } from "../status.js";
|
|
7
4
|
export const PROBE_USAGE = "error: usage: superpowers-manager probe [--porcelain]\n";
|
|
8
|
-
// One ordered table drives both formats, so the porcelain key order
|
|
9
|
-
// (`git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/probe:43-59::printf 'requested_ref`) and the human label order (`git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/probe:61-77::printf 'requested ref`) cannot
|
|
10
|
-
// drift apart. Two parallel lists could each stay self-consistent while
|
|
11
|
-
// disagreeing with one another.
|
|
12
|
-
function fields(f) {
|
|
13
|
-
return [
|
|
14
|
-
{ key: "requested_ref", label: "requested ref", value: f.requestedRef },
|
|
15
|
-
{ key: "resolved_ref", label: "resolved ref", value: f.resolvedRef },
|
|
16
|
-
{ key: "desired_commit", label: "desired commit", value: f.desiredCommit },
|
|
17
|
-
{
|
|
18
|
-
key: "generated_commit",
|
|
19
|
-
label: "generated plugin commit",
|
|
20
|
-
value: f.generatedCommit,
|
|
21
|
-
absent: "not present",
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
key: "installed_commit",
|
|
25
|
-
label: "installed manager commit or fingerprint",
|
|
26
|
-
value: f.installedCommit,
|
|
27
|
-
absent: "not detected",
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
key: "identity_state",
|
|
31
|
-
label: "Codex identity state",
|
|
32
|
-
value: f.identityState,
|
|
33
|
-
},
|
|
34
|
-
{ key: "status", label: "status", value: f.status },
|
|
35
|
-
{
|
|
36
|
-
key: "selection_origin",
|
|
37
|
-
label: "selection origin",
|
|
38
|
-
value: f.selectionOrigin,
|
|
39
|
-
},
|
|
40
|
-
{ key: "selection_mode", label: "selection mode", value: f.selectionMode },
|
|
41
|
-
{
|
|
42
|
-
key: "upstream_source_origin",
|
|
43
|
-
label: "upstream source origin",
|
|
44
|
-
value: f.upstreamSourceOrigin,
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
key: "effective_source",
|
|
48
|
-
label: "effective source",
|
|
49
|
-
value: f.effectiveSource,
|
|
50
|
-
},
|
|
51
|
-
{ key: "saved_mode", label: "saved mode", value: f.savedMode },
|
|
52
|
-
{ key: "saved_source", label: "saved source", value: f.savedSource },
|
|
53
|
-
{
|
|
54
|
-
key: "saved_requested_ref",
|
|
55
|
-
label: "saved requested ref",
|
|
56
|
-
value: f.savedRequestedRef,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
key: "saved_resolved_ref",
|
|
60
|
-
label: "saved resolved ref",
|
|
61
|
-
value: f.savedResolvedRef,
|
|
62
|
-
},
|
|
63
|
-
{ key: "saved_commit", label: "saved commit", value: f.savedCommit },
|
|
64
|
-
{ key: "update_control", label: "update control", value: f.updateControl },
|
|
65
|
-
];
|
|
66
|
-
}
|
|
67
|
-
// Derived from the same fields() table, so the key list a test asserts and
|
|
68
|
-
// the key list formatPorcelain emits cannot be different lists. The facts
|
|
69
|
-
// argument is irrelevant here — fields() is total over ProbeFacts and the
|
|
70
|
-
// keys do not depend on the values.
|
|
71
|
-
const NO_FACTS = {
|
|
72
|
-
requestedRef: "",
|
|
73
|
-
resolvedRef: "",
|
|
74
|
-
desiredCommit: "",
|
|
75
|
-
generatedCommit: "",
|
|
76
|
-
installedCommit: "",
|
|
77
|
-
identityState: "",
|
|
78
|
-
status: "",
|
|
79
|
-
selectionOrigin: "",
|
|
80
|
-
selectionMode: "",
|
|
81
|
-
upstreamSourceOrigin: "",
|
|
82
|
-
effectiveSource: "",
|
|
83
|
-
savedMode: "",
|
|
84
|
-
savedSource: "",
|
|
85
|
-
savedRequestedRef: "",
|
|
86
|
-
savedResolvedRef: "",
|
|
87
|
-
savedCommit: "",
|
|
88
|
-
updateControl: "",
|
|
89
|
-
};
|
|
90
|
-
export const PROBE_PORCELAIN_KEYS = fields(NO_FACTS).map((field) => field.key);
|
|
91
|
-
export function formatPorcelain(f) {
|
|
92
|
-
return fields(f)
|
|
93
|
-
.map((field) => `${field.key}=${field.value}\n`)
|
|
94
|
-
.join("");
|
|
95
|
-
}
|
|
96
|
-
export function formatHuman(f) {
|
|
97
|
-
let text = fields(f)
|
|
98
|
-
.map((field) => {
|
|
99
|
-
const shown = field.value.length === 0 && field.absent !== undefined
|
|
100
|
-
? field.absent
|
|
101
|
-
: field.value;
|
|
102
|
-
return `${field.label}: ${shown}\n`;
|
|
103
|
-
})
|
|
104
|
-
.join("");
|
|
105
|
-
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/probe:78-81::if [ "$SPW_SELECTION_ORIGIN`.
|
|
106
|
-
if (f.selectionOrigin !== f.upstreamSourceOrigin) {
|
|
107
|
-
text +=
|
|
108
|
-
"warning: effective ref and source have mixed origins " +
|
|
109
|
-
`(ref: ${f.selectionOrigin}, source: ${f.upstreamSourceOrigin})\n`;
|
|
110
|
-
}
|
|
111
|
-
return text;
|
|
112
|
-
}
|
|
113
5
|
// Ports scripts/core/validate-adapter-response.py's replay (:235-238) and its
|
|
114
6
|
// error/hint block (:269-272). The shell ran that validator on EVERY adapter
|
|
115
7
|
// response, so adapter messages reached the operator on their declared streams
|
|
@@ -141,6 +33,86 @@ export function replayOutcome(outcome, ctx) {
|
|
|
141
33
|
// stored, so scanning it here would re-check a population that cannot fail.
|
|
142
34
|
writeAdapterFailure(ctx, outcome);
|
|
143
35
|
}
|
|
36
|
+
function record(value) {
|
|
37
|
+
return value !== null && typeof value === "object"
|
|
38
|
+
? value
|
|
39
|
+
: null;
|
|
40
|
+
}
|
|
41
|
+
export function validOutput(value) {
|
|
42
|
+
const candidate = record(value);
|
|
43
|
+
return (candidate !== null &&
|
|
44
|
+
Array.isArray(candidate.stdout) &&
|
|
45
|
+
candidate.stdout.every((line) => typeof line === "string") &&
|
|
46
|
+
Array.isArray(candidate.stderr) &&
|
|
47
|
+
candidate.stderr.every((line) => typeof line === "string"));
|
|
48
|
+
}
|
|
49
|
+
function validDecision(value) {
|
|
50
|
+
const candidate = record(value);
|
|
51
|
+
return (candidate !== null &&
|
|
52
|
+
(candidate.kind === "allowed" ||
|
|
53
|
+
(candidate.kind === "blocked" && validOutput(candidate.output))));
|
|
54
|
+
}
|
|
55
|
+
function validCompatibility(value) {
|
|
56
|
+
const candidate = record(value);
|
|
57
|
+
if (candidate === null ||
|
|
58
|
+
typeof candidate.reason !== "string" ||
|
|
59
|
+
(candidate.kind !== "unknown" &&
|
|
60
|
+
candidate.kind !== "supported" &&
|
|
61
|
+
candidate.kind !== "experimental" &&
|
|
62
|
+
candidate.kind !== "unsupported"))
|
|
63
|
+
return false;
|
|
64
|
+
return ((candidate.kind !== "supported" && candidate.kind !== "experimental") ||
|
|
65
|
+
typeof candidate.generation === "string");
|
|
66
|
+
}
|
|
67
|
+
function validPreparedArtifact(value) {
|
|
68
|
+
const candidate = record(value);
|
|
69
|
+
return (candidate !== null &&
|
|
70
|
+
typeof candidate.root === "string" &&
|
|
71
|
+
typeof candidate.commit === "string" &&
|
|
72
|
+
typeof candidate.identity === "string" &&
|
|
73
|
+
validCompatibility(candidate.compatibility));
|
|
74
|
+
}
|
|
75
|
+
function validPrepared(value) {
|
|
76
|
+
const candidate = record(value);
|
|
77
|
+
return (candidate !== null &&
|
|
78
|
+
typeof candidate.observedIdentity === "string" &&
|
|
79
|
+
validCompatibility(candidate.compatibility) &&
|
|
80
|
+
(candidate.kind === "needs-prepare" ||
|
|
81
|
+
(candidate.kind === "current" &&
|
|
82
|
+
validPreparedArtifact(candidate.artifact))));
|
|
83
|
+
}
|
|
84
|
+
export function validInstalled(value) {
|
|
85
|
+
const candidate = record(value);
|
|
86
|
+
return (candidate !== null &&
|
|
87
|
+
(candidate.kind === "current" ||
|
|
88
|
+
candidate.kind === "mismatch" ||
|
|
89
|
+
candidate.kind === "absent") &&
|
|
90
|
+
typeof candidate.observedIdentity === "string" &&
|
|
91
|
+
(candidate.kind !== "absent" || candidate.observedIdentity === ""));
|
|
92
|
+
}
|
|
93
|
+
function validOwnership(value) {
|
|
94
|
+
const candidate = record(value);
|
|
95
|
+
return (candidate !== null &&
|
|
96
|
+
validDecision(candidate.installEligibility) &&
|
|
97
|
+
Object.prototype.hasOwnProperty.call(candidate, "removalInput") &&
|
|
98
|
+
validDecision(candidate.removalVerification) &&
|
|
99
|
+
validOutput(candidate.postRemovalOutput) &&
|
|
100
|
+
typeof candidate.presentationValue === "string" &&
|
|
101
|
+
(candidate.presentationConflicts === undefined ||
|
|
102
|
+
(Array.isArray(candidate.presentationConflicts) &&
|
|
103
|
+
candidate.presentationConflicts.every((conflict) => typeof conflict === "string"))));
|
|
104
|
+
}
|
|
105
|
+
function validControl(value) {
|
|
106
|
+
const candidate = record(value);
|
|
107
|
+
return (candidate !== null &&
|
|
108
|
+
validDecision(candidate.probeEligibility) &&
|
|
109
|
+
validDecision(candidate.mutationEligibility) &&
|
|
110
|
+
typeof candidate.presentationValue === "string" &&
|
|
111
|
+
(candidate.recoveryState === undefined ||
|
|
112
|
+
(candidate.recoveryState === "required" &&
|
|
113
|
+
candidate.probeEligibility.kind === "blocked" &&
|
|
114
|
+
candidate.mutationEligibility.kind === "blocked")));
|
|
115
|
+
}
|
|
144
116
|
// `runAdapter` reports a CONTROLLED failure by RETURN VALUE, not by throwing
|
|
145
117
|
// (`src/adapter-result.ts:32-35::export interface AdapterResult`). The shell got
|
|
146
118
|
// fail-closed behaviour for free: spw_invoke_adapter returned 1 and
|
|
@@ -156,25 +128,19 @@ export function replayOutcome(outcome, ctx) {
|
|
|
156
128
|
// adapter module: src/commands/prepare.ts and this module are the two the
|
|
157
129
|
// injected double must observe, because install reaches the adapter through
|
|
158
130
|
// gatherProbe and runPrepare. Spec §4.5.
|
|
159
|
-
async function inspect(
|
|
131
|
+
async function inspect(call, unexpected, invalidStatus, valid) {
|
|
160
132
|
let result;
|
|
161
133
|
try {
|
|
162
|
-
result = await
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
134
|
+
result = await call();
|
|
135
|
+
if (!result ||
|
|
136
|
+
!Number.isInteger(result.status) ||
|
|
137
|
+
!result.outcome ||
|
|
138
|
+
typeof result.outcome.ok !== "boolean" ||
|
|
139
|
+
!Array.isArray(result.outcome.messages))
|
|
140
|
+
return { ok: false, result: null, message: invalidStatus };
|
|
166
141
|
}
|
|
167
142
|
catch {
|
|
168
|
-
|
|
169
|
-
// AdapterFailure is by construction the one failure src/adapter.ts chose
|
|
170
|
-
// not to own: free-form text of unknown provenance, which AGENTS.md bars
|
|
171
|
-
// from this stream. `view` is a bounded token -- one of three literals
|
|
172
|
-
// this function is ever called with -- so naming the input is safe.
|
|
173
|
-
return {
|
|
174
|
-
ok: false,
|
|
175
|
-
outcome: null,
|
|
176
|
-
message: `cannot inspect Codex adapter state for view ${view}`,
|
|
177
|
-
};
|
|
143
|
+
return { ok: false, result: null, message: unexpected };
|
|
178
144
|
}
|
|
179
145
|
const outcome = result.outcome;
|
|
180
146
|
if (result.status !== 0 || !outcome.ok) {
|
|
@@ -183,27 +149,19 @@ async function inspect(view, key, ctx) {
|
|
|
183
149
|
// rather than falling through to a replay that would print nothing.
|
|
184
150
|
return {
|
|
185
151
|
ok: false,
|
|
186
|
-
|
|
187
|
-
message: outcome.ok
|
|
188
|
-
? `adapter reported a failure status for inspect --view ${view}`
|
|
189
|
-
: null,
|
|
152
|
+
result,
|
|
153
|
+
message: outcome.ok ? invalidStatus : null,
|
|
190
154
|
};
|
|
191
155
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (value === null || value === undefined) {
|
|
197
|
-
return { ok: true, value: "", outcome };
|
|
156
|
+
try {
|
|
157
|
+
if (!valid(outcome.result)) {
|
|
158
|
+
return { ok: false, result, message: invalidStatus };
|
|
159
|
+
}
|
|
198
160
|
}
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
ok: false,
|
|
202
|
-
outcome,
|
|
203
|
-
message: `adapter returned a non-string ${key} for inspect --view ${view}`,
|
|
204
|
-
};
|
|
161
|
+
catch {
|
|
162
|
+
return { ok: false, result, message: invalidStatus };
|
|
205
163
|
}
|
|
206
|
-
return { ok: true,
|
|
164
|
+
return { ok: true, result: { status: result.status, outcome } };
|
|
207
165
|
}
|
|
208
166
|
// Runs every step that can throw or fail closed, returning the outcome as
|
|
209
167
|
// data. It performs no writes of its own, so nothing inside runProbe's try can
|
|
@@ -222,51 +180,107 @@ async function inspect(view, key, ctx) {
|
|
|
222
180
|
// gone.
|
|
223
181
|
export async function gatherProbe(ctx) {
|
|
224
182
|
// Order mirrors `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/probe:24-40::spw_compute_effective_selection` exactly.
|
|
225
|
-
const selection = await computeEffectiveSelection(ctx.root, ctx.env);
|
|
226
|
-
const generatedCommit = await generatedCommitOrEmpty(ctx.root);
|
|
183
|
+
const selection = ctx.selection ?? (await computeEffectiveSelection(ctx.root, ctx.env));
|
|
227
184
|
const outcomes = [];
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
185
|
+
const adapterContext = { root: ctx.root, env: ctx.env };
|
|
186
|
+
let resources;
|
|
187
|
+
try {
|
|
188
|
+
resources = [
|
|
189
|
+
ctx.adapter.preparationLocation(adapterContext).destinationRoot,
|
|
190
|
+
...(await ctx.adapter.mutationRoots(adapterContext)),
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
resources = null;
|
|
195
|
+
}
|
|
196
|
+
const before = resources === null
|
|
197
|
+
? []
|
|
198
|
+
: await ctx.coordination.observeResources(resources);
|
|
199
|
+
const collect = async (call, unexpected, invalidStatus, valid, replaySuccess = true) => {
|
|
200
|
+
const result = await inspect(call, unexpected, invalidStatus, valid);
|
|
201
|
+
if (result.result !== null && (replaySuccess || !result.result.outcome.ok))
|
|
202
|
+
outcomes.push(result.result.outcome);
|
|
232
203
|
return result;
|
|
233
204
|
};
|
|
234
|
-
const
|
|
235
|
-
if (!
|
|
236
|
-
return { status: 1, outcomes, message:
|
|
205
|
+
const prepared = await collect(() => ctx.adapter.inspectPrepared(selection, adapterContext), "cannot inspect prepared harness state", "adapter reported a failure status for prepared harness inspection", validPrepared);
|
|
206
|
+
if (!prepared.ok) {
|
|
207
|
+
return { status: 1, outcomes, message: prepared.message };
|
|
208
|
+
}
|
|
209
|
+
const failure = (site) => ctx.adapter.presentation.callFailure(site, adapterContext);
|
|
210
|
+
const installedFailure = failure("probe-installed");
|
|
211
|
+
const installed = await collect(() => ctx.adapter.inspectInstalled(selection, adapterContext), installedFailure.unexpected, installedFailure.invalidStatus, validInstalled);
|
|
212
|
+
if (!installed.ok) {
|
|
213
|
+
return { status: 1, outcomes, message: installed.message };
|
|
237
214
|
}
|
|
238
|
-
|
|
215
|
+
if (resources === null)
|
|
216
|
+
return {
|
|
217
|
+
status: 1,
|
|
218
|
+
outcomes,
|
|
219
|
+
message: "cannot determine harness observation resources",
|
|
220
|
+
};
|
|
221
|
+
const ownershipFailure = failure("probe-ownership");
|
|
222
|
+
const ownership = await collect(() => ctx.adapter.inspectOwnership(adapterContext), ownershipFailure.unexpected, ownershipFailure.invalidStatus, validOwnership);
|
|
239
223
|
if (!ownership.ok) {
|
|
240
224
|
return { status: 1, outcomes, message: ownership.message };
|
|
241
225
|
}
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
226
|
+
const controlFailure = failure("probe-control");
|
|
227
|
+
const control = await collect(() => ctx.adapter.inspectUpdateControl(adapterContext), controlFailure.unexpected, controlFailure.invalidStatus, validControl);
|
|
228
|
+
if (!control.ok) {
|
|
229
|
+
return { status: 1, outcomes, message: control.message };
|
|
245
230
|
}
|
|
246
|
-
const
|
|
231
|
+
const preparedAfter = await collect(() => ctx.adapter.inspectPrepared(selection, adapterContext), "cannot inspect prepared harness state", "adapter reported a failure status for prepared harness inspection", validPrepared, false);
|
|
232
|
+
if (!preparedAfter.ok)
|
|
233
|
+
return { status: 1, outcomes, message: preparedAfter.message };
|
|
234
|
+
const installedAfter = await collect(() => ctx.adapter.inspectInstalled(selection, adapterContext), installedFailure.unexpected, installedFailure.invalidStatus, validInstalled, false);
|
|
235
|
+
if (!installedAfter.ok)
|
|
236
|
+
return { status: 1, outcomes, message: installedAfter.message };
|
|
237
|
+
const controlAfter = await collect(() => ctx.adapter.inspectUpdateControl(adapterContext), controlFailure.unexpected, controlFailure.invalidStatus, validControl, false);
|
|
238
|
+
if (!controlAfter.ok)
|
|
239
|
+
return { status: 1, outcomes, message: controlAfter.message };
|
|
240
|
+
const after = await ctx.coordination.observeResources(resources);
|
|
241
|
+
if (JSON.stringify(before) !== JSON.stringify(after) ||
|
|
242
|
+
JSON.stringify(prepared.result.outcome.result) !==
|
|
243
|
+
JSON.stringify(preparedAfter.result.outcome.result) ||
|
|
244
|
+
JSON.stringify(installed.result.outcome.result) !==
|
|
245
|
+
JSON.stringify(installedAfter.result.outcome.result) ||
|
|
246
|
+
JSON.stringify(control.result.outcome.result) !==
|
|
247
|
+
JSON.stringify(controlAfter.result.outcome.result)) {
|
|
248
|
+
return {
|
|
249
|
+
status: 1,
|
|
250
|
+
outcomes,
|
|
251
|
+
message: "harness state changed during observation; retry for a coherent probe",
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const unavailable = after.find((observation) => observation.state === "busy" || observation.state === "uninspectable");
|
|
255
|
+
if (unavailable)
|
|
256
|
+
return {
|
|
257
|
+
status: 1,
|
|
258
|
+
outcomes,
|
|
259
|
+
message: `harness resource is ${unavailable.state}: ${unavailable.resource}`,
|
|
260
|
+
};
|
|
261
|
+
const preparedState = prepared.result.outcome.result;
|
|
262
|
+
const installedState = installed.result.outcome.result;
|
|
247
263
|
return {
|
|
248
264
|
status: 0,
|
|
249
265
|
outcomes,
|
|
250
266
|
facts: {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
savedCommit: saved.saved_commit,
|
|
269
|
-
updateControl: updateControl.value,
|
|
267
|
+
selection,
|
|
268
|
+
prepared: preparedState,
|
|
269
|
+
installed: installedState,
|
|
270
|
+
ownership: ownership.result.outcome.result,
|
|
271
|
+
control: control.result.outcome.result,
|
|
272
|
+
compatibility: preparedState.compatibility,
|
|
273
|
+
resources: after,
|
|
274
|
+
resourceState: control.result.outcome.result.recoveryState === "required"
|
|
275
|
+
? "recovery-required"
|
|
276
|
+
: after.some((observation) => observation.state === "owned")
|
|
277
|
+
? "owned"
|
|
278
|
+
: "idle",
|
|
279
|
+
status: preparedState.kind !== "current"
|
|
280
|
+
? "needs prepare"
|
|
281
|
+
: installedState.kind !== "current"
|
|
282
|
+
? "needs install"
|
|
283
|
+
: "current",
|
|
270
284
|
},
|
|
271
285
|
};
|
|
272
286
|
}
|
|
@@ -319,7 +333,7 @@ export async function runProbe(argv, ctx) {
|
|
|
319
333
|
// every unpinned run does: resolveRef's COMMIT_INPUT_RE branch returns
|
|
320
334
|
// before any runGit call, so a 40-hex requested ref splices nothing.
|
|
321
335
|
// Pinned by
|
|
322
|
-
// `tests/unit/upstream.test.
|
|
336
|
+
// `tests/unit/upstream.test.ts:453::void test("resolveRef reports a query failure for latest`,
|
|
323
337
|
// :471, and :483.
|
|
324
338
|
// 2. The
|
|
325
339
|
// `src/selection-store.ts:120-124::cause.module === "selection") {`
|
|
@@ -387,6 +401,7 @@ export async function runProbe(argv, ctx) {
|
|
|
387
401
|
}
|
|
388
402
|
return 1;
|
|
389
403
|
}
|
|
390
|
-
ctx.
|
|
391
|
-
|
|
404
|
+
const rendered = ctx.adapter.presentation.renderProbe(outcome.facts);
|
|
405
|
+
ctx.stdout.write(porcelain ? rendered.porcelain : rendered.human);
|
|
406
|
+
return outcome.facts.resourceState === "recovery-required" ? 1 : 0;
|
|
392
407
|
}
|