humanish 0.115.0 → 0.116.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/CHANGELOG.md +22 -0
- package/README.md +7 -6
- package/TELEMETRY.md +6 -0
- package/dist/actors/codex/app-server-ui.js +11 -4
- package/dist/actors/computer-use/loop/debrief.js +61 -27
- package/dist/actors/scripted-browser/steps.js +6 -5
- package/dist/analysis/analysis-limits.d.ts +35 -0
- package/dist/analysis/analysis-limits.js +41 -0
- package/dist/analysis/validation.js +14 -21
- package/dist/cli/commands/analyze.js +2 -1
- package/dist/cli/commands/notes.js +2 -1
- package/dist/cli/commands/run-signals.js +5 -1
- package/dist/cli/commands/runs.js +2 -1
- package/dist/cli/commands/setup.js +3 -3
- package/dist/cli/commands/study-format.d.ts +5 -2
- package/dist/cli/commands/study-format.js +29 -9
- package/dist/cli/commands/study-route-computer-use.js +2 -2
- package/dist/cli/commands/study-route-scripted.js +1 -1
- package/dist/cli/commands/tui.js +59 -17
- package/dist/cli/doctor.js +9 -2
- package/dist/cli/findings-text.d.ts +5 -0
- package/dist/cli/findings-text.js +169 -0
- package/dist/cli/findings.d.ts +0 -4
- package/dist/cli/findings.js +5 -167
- package/dist/cli/io.js +6 -1
- package/dist/cli/update-check.js +4 -1
- package/dist/comms/connections.js +4 -3
- package/dist/comms/setup.js +18 -12
- package/dist/evidence/redaction.js +9 -1
- package/dist/evidence/secret-scrub.d.ts +15 -5
- package/dist/evidence/secret-scrub.js +67 -9
- package/dist/feedback/draft.js +1 -1
- package/dist/feedback/export-bundle.js +18 -14
- package/dist/feedback/feedback.js +25 -7
- package/dist/guest/desktop-keys.js +59 -5
- package/dist/keys/key-resolution.d.ts +9 -0
- package/dist/keys/key-resolution.js +60 -48
- package/dist/keys/key-status.d.ts +2 -0
- package/dist/keys/key-status.js +9 -16
- package/dist/observer/pinned-files.js +1 -1
- package/dist/observer/render.js +3 -2
- package/dist/observer-app.html +7 -7
- package/dist/routes/computer-use/bundle-parts.d.ts +4 -4
- package/dist/routes/computer-use/bundle-parts.js +2 -2
- package/dist/routes/computer-use/bundle.d.ts +4 -5
- package/dist/routes/computer-use/bundle.js +1 -3
- package/dist/routes/computer-use/fanout-records.js +10 -11
- package/dist/routes/computer-use/live-phase.d.ts +1 -1
- package/dist/routes/computer-use/live-phase.js +0 -1
- package/dist/routes/computer-use/participant-execution.d.ts +3 -2
- package/dist/routes/computer-use/participant-runs.d.ts +6 -2
- package/dist/routes/computer-use/participant-runs.js +9 -5
- package/dist/routes/computer-use/plan.d.ts +3 -0
- package/dist/routes/computer-use/plan.js +14 -11
- package/dist/routes/computer-use/rerun-selection.js +2 -2
- package/dist/routes/computer-use/result.js +3 -6
- package/dist/routes/computer-use/setup.d.ts +4 -6
- package/dist/routes/computer-use/setup.js +2 -6
- package/dist/routes/computer-use/single-bundle.d.ts +4 -5
- package/dist/routes/computer-use/single-bundle.js +22 -25
- package/dist/routes/computer-use/subject-projection.d.ts +4 -8
- package/dist/routes/computer-use/subject-projection.js +9 -31
- package/dist/routes/computer-use/types.d.ts +4 -41
- package/dist/routes/scripted/scenario.js +28 -12
- package/dist/routes/scripted/session-result.js +6 -3
- package/dist/routes/shared-world/bundle-records.js +2 -8
- package/dist/routes/shared-world/participant-specs.d.ts +0 -2
- package/dist/routes/shared-world/participant-specs.js +1 -20
- package/dist/routes/shared-world/plan.d.ts +3 -0
- package/dist/routes/shared-world/plan.js +23 -3
- package/dist/routes/shared-world/setup.js +2 -2
- package/dist/routes/terminal/ledger.d.ts +9 -9
- package/dist/routes/terminal/ledger.js +63 -40
- package/dist/routes/terminal/live-finish.js +7 -10
- package/dist/routes/terminal/plan.d.ts +3 -0
- package/dist/routes/terminal/plan.js +8 -5
- package/dist/routes/terminal/token-usage.d.ts +1 -2
- package/dist/routes/terminal/token-usage.js +4 -6
- package/dist/routes/terminal/types.d.ts +9 -1
- package/dist/run/bundle-shape.js +6 -1
- package/dist/run/bundle.d.ts +5 -3
- package/dist/run/contained-output.d.ts +53 -5
- package/dist/run/contained-output.js +141 -42
- package/dist/run/detail.d.ts +2 -1
- package/dist/run/detail.js +12 -7
- package/dist/run/escape-sequences.d.ts +11 -0
- package/dist/run/escape-sequences.js +93 -0
- package/dist/run/evidence-files.d.ts +8 -8
- package/dist/run/evidence-files.js +19 -23
- package/dist/run/locate.d.ts +32 -3
- package/dist/run/locate.js +45 -40
- package/dist/run/notes.d.ts +0 -8
- package/dist/run/notes.js +5 -51
- package/dist/run/outcomes.d.ts +24 -0
- package/dist/run/outcomes.js +19 -0
- package/dist/run/paths.d.ts +2 -0
- package/dist/run/paths.js +5 -1
- package/dist/run/preflight-receipts.js +13 -8
- package/dist/run/project.js +7 -4
- package/dist/run/reclaim-outcomes.d.ts +8 -6
- package/dist/run/reclaim-outcomes.js +10 -6
- package/dist/run/reclaim.d.ts +5 -0
- package/dist/run/reclaim.js +43 -16
- package/dist/run/run-clock.d.ts +50 -0
- package/dist/run/run-clock.js +66 -0
- package/dist/run/run.js +5 -2
- package/dist/run/sandbox-ids.d.ts +7 -2
- package/dist/run/sandbox-ids.js +27 -11
- package/dist/run/secrets.d.ts +2 -1
- package/dist/run/secrets.js +18 -22
- package/dist/run/stored-runs.js +14 -7
- package/dist/run/transient-comms-secrets.js +6 -17
- package/dist/run-study.js +22 -13
- package/dist/study/adapter-scorer-loader.js +6 -4
- package/dist/study/discover.js +9 -6
- package/dist/study/init-paths.js +7 -5
- package/dist/study/persona-resolve.d.ts +2 -2
- package/dist/study/persona-resolve.js +9 -5
- package/dist/study/plan-types.d.ts +2 -2
- package/dist/study/plan.d.ts +5 -3
- package/dist/study/plan.js +18 -9
- package/dist/study/preflight-probes.js +11 -3
- package/dist/study/preflight.js +9 -5
- package/dist/study/summary.d.ts +8 -2
- package/dist/study/summary.js +20 -22
- package/dist/study/warnings.d.ts +19 -12
- package/dist/study/warnings.js +38 -17
- package/dist/substrates/e2b/connection.d.ts +12 -0
- package/dist/substrates/e2b/connection.js +35 -0
- package/dist/substrates/e2b/desktop-executor.d.ts +1 -3
- package/dist/substrates/e2b/desktop-executor.js +42 -9
- package/dist/substrates/e2b/lifetime.d.ts +24 -4
- package/dist/substrates/e2b/lifetime.js +41 -4
- package/dist/substrates/e2b/sandbox.d.ts +4 -3
- package/dist/substrates/e2b/sandbox.js +13 -18
- package/dist/substrates/e2b/sdk.d.ts +20 -9
- package/dist/substrates/local/capacity.d.ts +18 -2
- package/dist/substrates/local/capacity.js +21 -2
- package/dist/substrates/local/runtime-host.js +21 -14
- package/dist/substrates/local/runtime.js +3 -11
- package/dist/tui/contract.d.ts +30 -5
- package/dist/tui/contract.js +4 -1
- package/dist/tui-app.js +149 -147
- package/dist/verify/artifacts.d.ts +9 -2
- package/dist/verify/artifacts.js +37 -13
- package/dist/verify/liveness.js +19 -5
- package/dist/verify/verify.js +54 -27
- package/docs/architecture/guest-desktop.md +7 -2
- package/docs/architecture/state-driven-executor.md +4 -2
- package/docs/contracts/run-bundle.md +18 -10
- package/docs/contracts/schemas.md +27 -18
- package/docs/contracts/study-analysis.md +10 -1
- package/docs/product/automatic-analysis.md +7 -1
- package/package.json +2 -2
|
@@ -57,7 +57,7 @@ function refuseCua(args, dryRun, code, message) {
|
|
|
57
57
|
dryRun,
|
|
58
58
|
runId: args.options.runId ?? "not-created",
|
|
59
59
|
});
|
|
60
|
-
writeResult(args.command, args.io, result, formatCuaStudyHuman);
|
|
60
|
+
writeResult(args.command, args.io, result, (value) => formatCuaStudyHuman(value, args.config.subject));
|
|
61
61
|
args.io.setExitCode(2);
|
|
62
62
|
}
|
|
63
63
|
/** Parses the options, or writes the refusal and returns undefined. */
|
|
@@ -236,7 +236,7 @@ async function reportCuaRun(args, prepared, result, live) {
|
|
|
236
236
|
],
|
|
237
237
|
};
|
|
238
238
|
}
|
|
239
|
-
writeResult(args.command, args.io, output, formatCuaStudyHuman);
|
|
239
|
+
writeResult(args.command, args.io, output, (value) => formatCuaStudyHuman(value, args.config.subject));
|
|
240
240
|
args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
|
|
241
241
|
await writeRunFindings(args.command, args.io, args.options.cwd, result);
|
|
242
242
|
if (server && (result.observer?.ok || attachedObserver)) {
|
|
@@ -34,7 +34,7 @@ export function scriptedRouteRun(args) {
|
|
|
34
34
|
throw new Error(`Expected the scripted route, got ${outcome.route}.`);
|
|
35
35
|
}
|
|
36
36
|
const result = outcome.result;
|
|
37
|
-
writeResult(args.command, args.io, result, formatScriptedStudyHuman);
|
|
37
|
+
writeResult(args.command, args.io, result, (value) => formatScriptedStudyHuman(value, args.config.subject));
|
|
38
38
|
args.io.setExitCode(result.ok && automaticAnalysisSucceeded(result) ? 0 : 2);
|
|
39
39
|
await writeRunFindings(args.command, args.io, args.options.cwd, result);
|
|
40
40
|
// Watch mode serves the Observer the route rendered through its finished run.
|
package/dist/cli/commands/tui.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { Command } from "commander";
|
|
4
|
-
import { setUserKey } from "../../keys/key-resolution.js";
|
|
4
|
+
import { LISTED_KEYS, setUserKey } from "../../keys/key-resolution.js";
|
|
5
|
+
import { keyStatus, keyStatusLine } from "../../keys/key-status.js";
|
|
5
6
|
import { saveCommsConnection } from "../../comms/connections.js";
|
|
6
7
|
import { readCommsSetup } from "../../comms/setup.js";
|
|
7
8
|
import { checkCommsConnection, configureCommsStudy, } from "../../comms/setup.js";
|
|
@@ -160,13 +161,11 @@ async function runTuiSession(loaded, runtime, cwd, discoveredKeys) {
|
|
|
160
161
|
runIndexCache: new RunIndexCache(),
|
|
161
162
|
observerSession: createTuiObserverSession(cwd),
|
|
162
163
|
};
|
|
163
|
-
let
|
|
164
|
+
let returnTo;
|
|
164
165
|
try {
|
|
165
166
|
for (;;) {
|
|
166
167
|
const outcome = await loaded.startTui({
|
|
167
|
-
...
|
|
168
|
-
? {}
|
|
169
|
-
: { initialScreen: "connections", connectionNotice }),
|
|
168
|
+
...returnTo,
|
|
170
169
|
cwd,
|
|
171
170
|
version: { cli: CLI_VERSION },
|
|
172
171
|
capabilities: tuiCapabilities(session),
|
|
@@ -175,9 +174,17 @@ async function runTuiSession(loaded, runtime, cwd, discoveredKeys) {
|
|
|
175
174
|
});
|
|
176
175
|
if (typeof outcome === "number")
|
|
177
176
|
return outcome;
|
|
178
|
-
|
|
177
|
+
returnTo =
|
|
178
|
+
outcome.action === "agentmail-key"
|
|
179
|
+
? { initialScreen: "connections", connectionNotice: await storeAgentmailKey(session) }
|
|
180
|
+
: outcome.action === "provider-key"
|
|
181
|
+
? {
|
|
182
|
+
initialScreen: "keys",
|
|
183
|
+
connectionNotice: await storeProviderKey(session, outcome.name),
|
|
184
|
+
}
|
|
185
|
+
: undefined;
|
|
186
|
+
if (returnTo === undefined)
|
|
179
187
|
return 1;
|
|
180
|
-
connectionNotice = await storeAgentmailKey(session);
|
|
181
188
|
}
|
|
182
189
|
}
|
|
183
190
|
finally {
|
|
@@ -197,6 +204,14 @@ function connectionEnv(session) {
|
|
|
197
204
|
function tuiCapabilities(session) {
|
|
198
205
|
const { cwd, runtime, sessionEnv, runIndexCache, observerSession } = session;
|
|
199
206
|
return {
|
|
207
|
+
keys: {
|
|
208
|
+
// The project's overlay and the stores, as `humanish keys` reads them from this directory.
|
|
209
|
+
status: async () => (await keyStatus({ cwd, env: connectionEnv(session) })).map((row) => ({
|
|
210
|
+
name: row.name,
|
|
211
|
+
set: row.source !== null,
|
|
212
|
+
line: keyStatusLine(row),
|
|
213
|
+
})),
|
|
214
|
+
},
|
|
200
215
|
comms: {
|
|
201
216
|
read: async () => ({
|
|
202
217
|
...(await readCommsSetup(cwd, connectionEnv(session))),
|
|
@@ -270,21 +285,48 @@ function tuiCapabilities(session) {
|
|
|
270
285
|
},
|
|
271
286
|
};
|
|
272
287
|
}
|
|
288
|
+
const ENTRY_CANCELLED = "Key entry cancelled. Nothing was changed.";
|
|
289
|
+
const ENTRY_FAILED = "Could not store the key. Use a single non-empty line and check key-store permissions.";
|
|
290
|
+
/**
|
|
291
|
+
* Asks for one key with hidden input, after the surface has unmounted, and stores it in the user
|
|
292
|
+
* store. Only the host reads the credential. A value that discovery filled into this session is
|
|
293
|
+
* dropped so the stored one is read; an explicit env or file value still wins.
|
|
294
|
+
*/
|
|
295
|
+
async function promptAndStoreKey(session, name, label) {
|
|
296
|
+
const { runtime, sessionEnv } = session;
|
|
297
|
+
const value = await runtime.promptSecret(label, runtime.stdin, runtime.stdout);
|
|
298
|
+
if (value === null || value === "")
|
|
299
|
+
return "cancelled";
|
|
300
|
+
setUserKey(name, value, sessionEnv);
|
|
301
|
+
if (session.discoveredKeys.has(name))
|
|
302
|
+
delete sessionEnv[name];
|
|
303
|
+
return "stored";
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* A key from the keys screen, asked for and stored as `humanish keys set <vendor>` does. Resolves to
|
|
307
|
+
* the notice the remounted keys screen shows.
|
|
308
|
+
*/
|
|
309
|
+
async function storeProviderKey(session, name) {
|
|
310
|
+
if (!LISTED_KEYS.some((key) => key.name === name))
|
|
311
|
+
return `${name} is not a key humanish keys lists. Nothing was changed.`;
|
|
312
|
+
try {
|
|
313
|
+
return (await promptAndStoreKey(session, name, `Value for ${name}`)) === "stored"
|
|
314
|
+
? `${name} stored.`
|
|
315
|
+
: ENTRY_CANCELLED;
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return ENTRY_FAILED;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
273
321
|
/**
|
|
274
322
|
* Prompts for the AgentMail key after the surface has unmounted, stores it and checks it. Resolves
|
|
275
323
|
* to the notice the remounted connections screen shows.
|
|
276
324
|
*/
|
|
277
325
|
async function storeAgentmailKey(session) {
|
|
278
|
-
const { cwd, runtime
|
|
279
|
-
// startTui has unmounted: only the host reads the credential, then remounts the view.
|
|
280
|
-
const value = await runtime.promptSecret("AgentMail API key", runtime.stdin, runtime.stdout);
|
|
281
|
-
if (value === null || value === "")
|
|
282
|
-
return "Key entry cancelled. Nothing was changed.";
|
|
326
|
+
const { cwd, runtime } = session;
|
|
283
327
|
try {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (session.discoveredKeys.has("AGENTMAIL_API_KEY"))
|
|
287
|
-
delete sessionEnv.AGENTMAIL_API_KEY;
|
|
328
|
+
if ((await promptAndStoreKey(session, "AGENTMAIL_API_KEY", "AgentMail API key")) !== "stored")
|
|
329
|
+
return ENTRY_CANCELLED;
|
|
288
330
|
const saved = await saveCommsConnection(cwd);
|
|
289
331
|
const storedNotice = saved.ok
|
|
290
332
|
? "Key stored. Project connection saved."
|
|
@@ -301,6 +343,6 @@ async function storeAgentmailKey(session) {
|
|
|
301
343
|
: `${storedNotice} ${check.message}`;
|
|
302
344
|
}
|
|
303
345
|
catch {
|
|
304
|
-
return
|
|
346
|
+
return ENTRY_FAILED;
|
|
305
347
|
}
|
|
306
348
|
}
|
package/dist/cli/doctor.js
CHANGED
|
@@ -2,8 +2,9 @@ import { readFile, readdir } from "node:fs/promises";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { probeKeySources, } from "../keys/key-resolution.js";
|
|
5
|
+
import { DOCTOR_KEYS, probeKeySources, } from "../keys/key-resolution.js";
|
|
6
6
|
import { desktopPeerAdvice, humanishInstall, } from "../substrates/e2b/peer-install.js";
|
|
7
|
+
import { describeSandboxCeiling, sandboxCeiling } from "../substrates/e2b/lifetime.js";
|
|
7
8
|
import { nodeSupportsTui, terminalSurfaceMessage, TUI_BUNDLE_URL } from "../tui/contract.js";
|
|
8
9
|
import { detectLocalAgents, localAgentDoctorMessage, localAgentReady, NO_LOCAL_AGENT_MESSAGE, } from "../actors/local-agent/cli.js";
|
|
9
10
|
import { leftoverClaudeTranscripts } from "../actors/local-agent/claude-participant.js";
|
|
@@ -95,6 +96,7 @@ export async function doctor(cwdInput, options = {}) {
|
|
|
95
96
|
const checks = [
|
|
96
97
|
...(await projectChecks(projectRoot)),
|
|
97
98
|
await desktopSdkCheck(setup),
|
|
99
|
+
sandboxCeilingCheck(env),
|
|
98
100
|
terminalSurfaceCheck(),
|
|
99
101
|
...versionCheck(env),
|
|
100
102
|
...localAgentChecks(agents),
|
|
@@ -191,6 +193,11 @@ async function runtimeIgnoreCheck(projectRoot) {
|
|
|
191
193
|
: `.gitignore does not list .humanish/; run ${cli("init --yes")}`,
|
|
192
194
|
};
|
|
193
195
|
}
|
|
196
|
+
/** The longest sandbox lifetime the E2B planners allow, and where that number came from. */
|
|
197
|
+
function sandboxCeilingCheck(env) {
|
|
198
|
+
const ceiling = sandboxCeiling(env);
|
|
199
|
+
return { name: "e2b sandbox ceiling", ok: ceiling.ok, message: describeSandboxCeiling(ceiling) };
|
|
200
|
+
}
|
|
194
201
|
/**
|
|
195
202
|
* The optional peer dep every live browser and terminal route needs. `npx -y humanish` does not
|
|
196
203
|
* pull optional peers, so without this check an adopter's first live run fails on it, safely and at
|
|
@@ -266,7 +273,7 @@ function versionCheck(env) {
|
|
|
266
273
|
}
|
|
267
274
|
/** Probes the live-run keys, plus the receiving-email key a real-comms study names. */
|
|
268
275
|
async function probeDoctorKeys(cwd, env, study, keyDeps, dotenv) {
|
|
269
|
-
const keyNames = new Set(
|
|
276
|
+
const keyNames = new Set(DOCTOR_KEYS);
|
|
270
277
|
let receivingKey = null;
|
|
271
278
|
if (study) {
|
|
272
279
|
const { resolveStudyManifest } = await import("../study/discover.js");
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AnalysisFindings } from "./findings.js";
|
|
2
|
+
/** The full findings block: the analysis, every finding with its evidence, and its limitations. */
|
|
3
|
+
export declare function formatFindings(view: AnalysisFindings): string[];
|
|
4
|
+
/** At most `limit` findings, one line each, and the command that prints all of them. */
|
|
5
|
+
export declare function formatFindingsSummary(view: AnalysisFindings, fullCommand: string, limit?: number): string[];
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// The text of a run's `humanish.analysis-findings.v1` view (findings.ts): the full block `review`
|
|
2
|
+
// and `analyze show` print, and the summary at the end of a live `run`. It reads only the view.
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { findingLead } from "../analysis/finding-lead.js";
|
|
5
|
+
import { plural } from "../run/text.js";
|
|
6
|
+
const IMPACT_TEXT = {
|
|
7
|
+
blocked_task: "blocked task",
|
|
8
|
+
friction: "friction",
|
|
9
|
+
recovery: "recovery",
|
|
10
|
+
uncertain: "uncertain",
|
|
11
|
+
};
|
|
12
|
+
const RECOVERY_TEXT = {
|
|
13
|
+
recovered: "recovered",
|
|
14
|
+
not_observed: "no recovery observed",
|
|
15
|
+
unknown: "recovery unknown",
|
|
16
|
+
};
|
|
17
|
+
/** `+1:05` from milliseconds since the first retained capture. */
|
|
18
|
+
function elapsedText(ms) {
|
|
19
|
+
const seconds = Math.floor(ms / 1000);
|
|
20
|
+
const minutes = Math.floor(seconds / 60);
|
|
21
|
+
const hours = Math.floor(minutes / 60);
|
|
22
|
+
const ss = String(seconds % 60).padStart(2, "0");
|
|
23
|
+
return hours > 0
|
|
24
|
+
? `+${hours}:${String(minutes % 60).padStart(2, "0")}:${ss}`
|
|
25
|
+
: `+${minutes}:${ss}`;
|
|
26
|
+
}
|
|
27
|
+
/** One participant's cited moments: each frame once, at its earliest cited time. */
|
|
28
|
+
function momentsText(evidence) {
|
|
29
|
+
const frames = new Map();
|
|
30
|
+
let unframed = 0;
|
|
31
|
+
for (const entry of evidence) {
|
|
32
|
+
if (entry.frame === null) {
|
|
33
|
+
unframed++;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const earlier = frames.get(entry.frame);
|
|
37
|
+
if (earlier === undefined ||
|
|
38
|
+
(entry.elapsedMs !== null && (earlier ?? Infinity) > entry.elapsedMs))
|
|
39
|
+
frames.set(entry.frame, entry.elapsedMs);
|
|
40
|
+
}
|
|
41
|
+
const parts = [...frames]
|
|
42
|
+
.sort(([a], [b]) => a - b)
|
|
43
|
+
.map(([frame, ms]) => `frame ${frame}${ms === null ? "" : ` at ${elapsedText(ms)}`}`);
|
|
44
|
+
if (unframed > 0)
|
|
45
|
+
parts.push(`${plural(unframed, "item")} with no frame`);
|
|
46
|
+
return parts.join(", ");
|
|
47
|
+
}
|
|
48
|
+
/** The capture files, relative to the run directory, in frame order. */
|
|
49
|
+
function captureFiles(evidence, runRoot) {
|
|
50
|
+
return [
|
|
51
|
+
...new Set(evidence
|
|
52
|
+
.filter((entry) => entry.capture !== null)
|
|
53
|
+
.sort((a, b) => (a.frame ?? 0) - (b.frame ?? 0))
|
|
54
|
+
.map((entry) => (runRoot ? path.relative(runRoot, entry.capture) : entry.capture))),
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
function findingLines(finding, runRoot) {
|
|
58
|
+
const cited = [...new Set(finding.evidence.map((entry) => entry.streamId))];
|
|
59
|
+
const labels = new Map(finding.affected.map((p) => [p.streamId, p.label]));
|
|
60
|
+
const affectedIds = finding.affected.map((p) => p.streamId);
|
|
61
|
+
const streams = [...affectedIds, ...cited.filter((id) => !labels.has(id))];
|
|
62
|
+
const captures = captureFiles(finding.evidence, runRoot);
|
|
63
|
+
const correction = finding.correction;
|
|
64
|
+
const lead = findingLead(finding, correction);
|
|
65
|
+
return [
|
|
66
|
+
...(lead === null
|
|
67
|
+
? [`${finding.id} ${finding.title}`]
|
|
68
|
+
: [
|
|
69
|
+
`${finding.id} ${lead.headline}`,
|
|
70
|
+
...(lead.account === null ? [] : [` ${lead.account}`]),
|
|
71
|
+
` evidence: ${finding.title}`,
|
|
72
|
+
]),
|
|
73
|
+
` impact: ${IMPACT_TEXT[finding.impact]} · confidence: ${finding.confidence} · recovery: ${RECOVERY_TEXT[finding.recovery]}`,
|
|
74
|
+
` ${finding.summary}`,
|
|
75
|
+
` affected: ${finding.affected.length} of ${plural(finding.exposedCount, "exposed participant")}`,
|
|
76
|
+
...streams.map((streamId) => {
|
|
77
|
+
const moments = momentsText(finding.evidence.filter((entry) => entry.streamId === streamId));
|
|
78
|
+
const label = `${labels.get(streamId) ?? streamId}${labels.has(streamId) ? "" : " (cited, not affected)"}`;
|
|
79
|
+
return ` - ${label}${moments ? `: ${moments}` : ""}`;
|
|
80
|
+
}),
|
|
81
|
+
...(captures.length === 0 ? [] : [` captures: ${captures.join(", ")}`]),
|
|
82
|
+
` next step: ${finding.nextStep}`,
|
|
83
|
+
...(correction === null
|
|
84
|
+
? []
|
|
85
|
+
: [
|
|
86
|
+
` human review: ${correction.status}, ${correction.reason}${correction.replacementClaim ? ` Amended claim: ${correction.replacementClaim}` : ""}`,
|
|
87
|
+
]),
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
function designFindingLines(finding, runRoot) {
|
|
91
|
+
const captures = captureFiles(finding.evidence, runRoot);
|
|
92
|
+
return [
|
|
93
|
+
`${finding.id} ${finding.severity}: ${finding.headline}`,
|
|
94
|
+
` screen: ${finding.screen}`,
|
|
95
|
+
` notice: ${finding.notice}`,
|
|
96
|
+
` why it matters: ${finding.whyItMatters}`,
|
|
97
|
+
` suggestion: ${finding.suggestion}`,
|
|
98
|
+
` seen by: ${finding.seenBy.map((p) => p.label).join(", ")} · confidence: ${finding.confidence}`,
|
|
99
|
+
...(captures.length === 0 ? [] : [` captures: ${captures.join(", ")}`]),
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
/** Nothing for an analysis written before design findings. */
|
|
103
|
+
function designSection(view) {
|
|
104
|
+
const design = view.designFindings;
|
|
105
|
+
if (design === null)
|
|
106
|
+
return [];
|
|
107
|
+
if (design.length === 0)
|
|
108
|
+
return ["", "design findings: none in the reviewed captures"];
|
|
109
|
+
return [
|
|
110
|
+
"",
|
|
111
|
+
`design findings: ${design.length}, most severe first`,
|
|
112
|
+
...design.flatMap((finding, index) => [
|
|
113
|
+
...(index === 0 ? [] : [""]),
|
|
114
|
+
...designFindingLines(finding, view.runPath ?? ""),
|
|
115
|
+
]),
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
/** The full findings block: the analysis, every finding with its evidence, and its limitations. */
|
|
119
|
+
export function formatFindings(view) {
|
|
120
|
+
if (view.state !== "ready" && view.state !== "stale")
|
|
121
|
+
return [`findings: none. ${view.message}`, ...(view.next ? [`next: ${view.next}`] : [])];
|
|
122
|
+
const model = [view.provider, view.model].filter(Boolean).join(" ");
|
|
123
|
+
const cost = view.estimatedCostUsd === null ? "" : `, estimated $${view.estimatedCostUsd.toFixed(2)}`;
|
|
124
|
+
return [
|
|
125
|
+
`findings: ${view.findings.length} from analysis ${view.analysisId} (${view.status}, ${model}${cost})`,
|
|
126
|
+
...(view.state === "stale" ? [`warning: ${view.message}`, `next: ${view.next}`] : []),
|
|
127
|
+
...(view.summary ? [view.summary] : []),
|
|
128
|
+
...view.findings.flatMap((finding) => ["", ...findingLines(finding, view.runPath ?? "")]),
|
|
129
|
+
...designSection(view),
|
|
130
|
+
...(view.limitations.length === 0
|
|
131
|
+
? []
|
|
132
|
+
: ["", "limitations:", ...view.limitations.map((limitation) => `- ${limitation}`)]),
|
|
133
|
+
...view.warnings.map((warning) => `warning: ${warning}`),
|
|
134
|
+
"",
|
|
135
|
+
`analysis: ${view.path}`,
|
|
136
|
+
];
|
|
137
|
+
}
|
|
138
|
+
/** At most `limit` findings, one line each, and the command that prints all of them. */
|
|
139
|
+
export function formatFindingsSummary(view, fullCommand, limit = 3) {
|
|
140
|
+
if (view.state !== "ready" && view.state !== "stale")
|
|
141
|
+
return [`findings: ${view.message}`, ...(view.next ? [`next: ${view.next}`] : [])];
|
|
142
|
+
const shown = view.findings.slice(0, limit);
|
|
143
|
+
const design = view.designFindings;
|
|
144
|
+
return [
|
|
145
|
+
`findings: ${view.findings.length === 0 ? "none" : view.findings.length}${view.state === "stale" ? " (stale)" : ""}`,
|
|
146
|
+
...shown.map((finding) => {
|
|
147
|
+
const qualities = `${IMPACT_TEXT[finding.impact]}, ${finding.confidence} confidence, ${RECOVERY_TEXT[finding.recovery]}`;
|
|
148
|
+
const lead = findingLead(finding, finding.correction);
|
|
149
|
+
if (lead === null)
|
|
150
|
+
return `- ${finding.id} ${qualities}: ${finding.title}`;
|
|
151
|
+
return `- ${finding.id} ${lead.headline} (${lead.corrected ? "corrected in human review" : qualities})`;
|
|
152
|
+
}),
|
|
153
|
+
...(view.findings.length > limit
|
|
154
|
+
? [`- and ${plural(view.findings.length - limit, "more finding")}`]
|
|
155
|
+
: []),
|
|
156
|
+
...(design === null
|
|
157
|
+
? []
|
|
158
|
+
: [
|
|
159
|
+
`design findings: ${design.length === 0 ? "none" : design.length}`,
|
|
160
|
+
...design
|
|
161
|
+
.slice(0, limit)
|
|
162
|
+
.map((finding) => `- ${finding.id} ${finding.severity}: ${finding.headline}`),
|
|
163
|
+
...(design.length > limit
|
|
164
|
+
? [`- and ${plural(design.length - limit, "more design finding")}`]
|
|
165
|
+
: []),
|
|
166
|
+
]),
|
|
167
|
+
`all findings: ${fullCommand}`,
|
|
168
|
+
];
|
|
169
|
+
}
|
package/dist/cli/findings.d.ts
CHANGED
|
@@ -137,10 +137,6 @@ export declare function readRunAnalysis(cwd: string, run: string, analysisId?: s
|
|
|
137
137
|
export declare function missingRunFindings(cwd: string, run: string): AnalysisFindings;
|
|
138
138
|
/** The findings of a run's selected analysis, or why it has none. */
|
|
139
139
|
export declare function readRunFindings(cwd: string, run: string, analysisId?: string): Promise<AnalysisFindings>;
|
|
140
|
-
/** The full findings block: the analysis, every finding with its evidence, and its limitations. */
|
|
141
|
-
export declare function formatFindings(view: AnalysisFindings): string[];
|
|
142
|
-
/** At most `limit` findings, one line each, and the command that prints all of them. */
|
|
143
|
-
export declare function formatFindingsSummary(view: AnalysisFindings, fullCommand: string, limit?: number): string[];
|
|
144
140
|
/**
|
|
145
141
|
* The end of a live run's human output, after its `analysis:` line: the findings of the analysis
|
|
146
142
|
* the run just completed. Prints nothing for --json, or when the run's analysis did not complete,
|
package/dist/cli/findings.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// A run's analysis findings for `review`, `analyze show` and the end of a live `run`: the
|
|
2
|
-
// `humanish.analysis-findings.v1` projection
|
|
3
|
-
// this reads the version `analyze show` selects and never
|
|
2
|
+
// `humanish.analysis-findings.v1` projection, read from the run. findings-text.ts prints it. The
|
|
3
|
+
// analysis artifact stays the record; this reads the version `analyze show` selects and never
|
|
4
|
+
// starts an analysis.
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { costRefusal } from "../analysis/admission.js";
|
|
6
7
|
import { DEFAULT_ANALYSIS_MAX_COST_USD } from "../analysis/automatic-config.js";
|
|
7
|
-
import { bySeverity
|
|
8
|
+
import { bySeverity } from "../analysis/finding-lead.js";
|
|
8
9
|
import { loadAnalysis } from "../analysis/load.js";
|
|
9
10
|
import { projectShareCheckedAnalysis } from "../analysis/sharing.js";
|
|
10
11
|
import { loadRunBundlePrepared, resolveRunPath } from "../run/locate.js";
|
|
@@ -15,6 +16,7 @@ import { routeOf } from "../study/routing.js";
|
|
|
15
16
|
import { plural } from "../run/text.js";
|
|
16
17
|
import { shellQuote } from "../substrates/shell.js";
|
|
17
18
|
import { analysisOutcomeText, wantsJson } from "./io.js";
|
|
19
|
+
import { formatFindingsSummary } from "./findings-text.js";
|
|
18
20
|
import { cli } from "./invocation.js";
|
|
19
21
|
export const ANALYSIS_FINDINGS_SCHEMA = "humanish.analysis-findings.v1";
|
|
20
22
|
/** ` --cwd <dir>`, or "" when `cwd` is the current directory. */
|
|
@@ -287,170 +289,6 @@ export async function readRunFindings(cwd, run, analysisId) {
|
|
|
287
289
|
const source = await readRunAnalysis(cwd, run, analysisId);
|
|
288
290
|
return source ? analysisFindings(source) : missingRunFindings(cwd, run);
|
|
289
291
|
}
|
|
290
|
-
const IMPACT_TEXT = {
|
|
291
|
-
blocked_task: "blocked task",
|
|
292
|
-
friction: "friction",
|
|
293
|
-
recovery: "recovery",
|
|
294
|
-
uncertain: "uncertain",
|
|
295
|
-
};
|
|
296
|
-
const RECOVERY_TEXT = {
|
|
297
|
-
recovered: "recovered",
|
|
298
|
-
not_observed: "no recovery observed",
|
|
299
|
-
unknown: "recovery unknown",
|
|
300
|
-
};
|
|
301
|
-
/** `+1:05` from milliseconds since the first retained capture. */
|
|
302
|
-
function elapsedText(ms) {
|
|
303
|
-
const seconds = Math.floor(ms / 1000);
|
|
304
|
-
const minutes = Math.floor(seconds / 60);
|
|
305
|
-
const hours = Math.floor(minutes / 60);
|
|
306
|
-
const ss = String(seconds % 60).padStart(2, "0");
|
|
307
|
-
return hours > 0
|
|
308
|
-
? `+${hours}:${String(minutes % 60).padStart(2, "0")}:${ss}`
|
|
309
|
-
: `+${minutes}:${ss}`;
|
|
310
|
-
}
|
|
311
|
-
/** One participant's cited moments: each frame once, at its earliest cited time. */
|
|
312
|
-
function momentsText(evidence) {
|
|
313
|
-
const frames = new Map();
|
|
314
|
-
let unframed = 0;
|
|
315
|
-
for (const entry of evidence) {
|
|
316
|
-
if (entry.frame === null) {
|
|
317
|
-
unframed++;
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
const earlier = frames.get(entry.frame);
|
|
321
|
-
if (earlier === undefined ||
|
|
322
|
-
(entry.elapsedMs !== null && (earlier ?? Infinity) > entry.elapsedMs))
|
|
323
|
-
frames.set(entry.frame, entry.elapsedMs);
|
|
324
|
-
}
|
|
325
|
-
const parts = [...frames]
|
|
326
|
-
.sort(([a], [b]) => a - b)
|
|
327
|
-
.map(([frame, ms]) => `frame ${frame}${ms === null ? "" : ` at ${elapsedText(ms)}`}`);
|
|
328
|
-
if (unframed > 0)
|
|
329
|
-
parts.push(`${plural(unframed, "item")} with no frame`);
|
|
330
|
-
return parts.join(", ");
|
|
331
|
-
}
|
|
332
|
-
/** The capture files, relative to the run directory, in frame order. */
|
|
333
|
-
function captureFiles(evidence, runRoot) {
|
|
334
|
-
return [
|
|
335
|
-
...new Set(evidence
|
|
336
|
-
.filter((entry) => entry.capture !== null)
|
|
337
|
-
.sort((a, b) => (a.frame ?? 0) - (b.frame ?? 0))
|
|
338
|
-
.map((entry) => (runRoot ? path.relative(runRoot, entry.capture) : entry.capture))),
|
|
339
|
-
];
|
|
340
|
-
}
|
|
341
|
-
function findingLines(finding, runRoot) {
|
|
342
|
-
const cited = [...new Set(finding.evidence.map((entry) => entry.streamId))];
|
|
343
|
-
const labels = new Map(finding.affected.map((p) => [p.streamId, p.label]));
|
|
344
|
-
const affectedIds = finding.affected.map((p) => p.streamId);
|
|
345
|
-
const streams = [...affectedIds, ...cited.filter((id) => !labels.has(id))];
|
|
346
|
-
const captures = captureFiles(finding.evidence, runRoot);
|
|
347
|
-
const correction = finding.correction;
|
|
348
|
-
const lead = findingLead(finding, correction);
|
|
349
|
-
return [
|
|
350
|
-
...(lead === null
|
|
351
|
-
? [`${finding.id} ${finding.title}`]
|
|
352
|
-
: [
|
|
353
|
-
`${finding.id} ${lead.headline}`,
|
|
354
|
-
...(lead.account === null ? [] : [` ${lead.account}`]),
|
|
355
|
-
` evidence: ${finding.title}`,
|
|
356
|
-
]),
|
|
357
|
-
` impact: ${IMPACT_TEXT[finding.impact]} · confidence: ${finding.confidence} · recovery: ${RECOVERY_TEXT[finding.recovery]}`,
|
|
358
|
-
` ${finding.summary}`,
|
|
359
|
-
` affected: ${finding.affected.length} of ${plural(finding.exposedCount, "exposed participant")}`,
|
|
360
|
-
...streams.map((streamId) => {
|
|
361
|
-
const moments = momentsText(finding.evidence.filter((entry) => entry.streamId === streamId));
|
|
362
|
-
const label = `${labels.get(streamId) ?? streamId}${labels.has(streamId) ? "" : " (cited, not affected)"}`;
|
|
363
|
-
return ` - ${label}${moments ? `: ${moments}` : ""}`;
|
|
364
|
-
}),
|
|
365
|
-
...(captures.length === 0 ? [] : [` captures: ${captures.join(", ")}`]),
|
|
366
|
-
` next step: ${finding.nextStep}`,
|
|
367
|
-
...(correction === null
|
|
368
|
-
? []
|
|
369
|
-
: [
|
|
370
|
-
` human review: ${correction.status}, ${correction.reason}${correction.replacementClaim ? ` Amended claim: ${correction.replacementClaim}` : ""}`,
|
|
371
|
-
]),
|
|
372
|
-
];
|
|
373
|
-
}
|
|
374
|
-
function designFindingLines(finding, runRoot) {
|
|
375
|
-
const captures = captureFiles(finding.evidence, runRoot);
|
|
376
|
-
return [
|
|
377
|
-
`${finding.id} ${finding.severity}: ${finding.headline}`,
|
|
378
|
-
` screen: ${finding.screen}`,
|
|
379
|
-
` notice: ${finding.notice}`,
|
|
380
|
-
` why it matters: ${finding.whyItMatters}`,
|
|
381
|
-
` suggestion: ${finding.suggestion}`,
|
|
382
|
-
` seen by: ${finding.seenBy.map((p) => p.label).join(", ")} · confidence: ${finding.confidence}`,
|
|
383
|
-
...(captures.length === 0 ? [] : [` captures: ${captures.join(", ")}`]),
|
|
384
|
-
];
|
|
385
|
-
}
|
|
386
|
-
/** Nothing for an analysis written before design findings. */
|
|
387
|
-
function designSection(view) {
|
|
388
|
-
const design = view.designFindings;
|
|
389
|
-
if (design === null)
|
|
390
|
-
return [];
|
|
391
|
-
if (design.length === 0)
|
|
392
|
-
return ["", "design findings: none in the reviewed captures"];
|
|
393
|
-
return [
|
|
394
|
-
"",
|
|
395
|
-
`design findings: ${design.length}, most severe first`,
|
|
396
|
-
...design.flatMap((finding, index) => [
|
|
397
|
-
...(index === 0 ? [] : [""]),
|
|
398
|
-
...designFindingLines(finding, view.runPath ?? ""),
|
|
399
|
-
]),
|
|
400
|
-
];
|
|
401
|
-
}
|
|
402
|
-
/** The full findings block: the analysis, every finding with its evidence, and its limitations. */
|
|
403
|
-
export function formatFindings(view) {
|
|
404
|
-
if (view.state !== "ready" && view.state !== "stale")
|
|
405
|
-
return [`findings: none. ${view.message}`, ...(view.next ? [`next: ${view.next}`] : [])];
|
|
406
|
-
const model = [view.provider, view.model].filter(Boolean).join(" ");
|
|
407
|
-
const cost = view.estimatedCostUsd === null ? "" : `, estimated $${view.estimatedCostUsd.toFixed(2)}`;
|
|
408
|
-
return [
|
|
409
|
-
`findings: ${view.findings.length} from analysis ${view.analysisId} (${view.status}, ${model}${cost})`,
|
|
410
|
-
...(view.state === "stale" ? [`warning: ${view.message}`, `next: ${view.next}`] : []),
|
|
411
|
-
...(view.summary ? [view.summary] : []),
|
|
412
|
-
...view.findings.flatMap((finding) => ["", ...findingLines(finding, view.runPath ?? "")]),
|
|
413
|
-
...designSection(view),
|
|
414
|
-
...(view.limitations.length === 0
|
|
415
|
-
? []
|
|
416
|
-
: ["", "limitations:", ...view.limitations.map((limitation) => `- ${limitation}`)]),
|
|
417
|
-
...view.warnings.map((warning) => `warning: ${warning}`),
|
|
418
|
-
"",
|
|
419
|
-
`analysis: ${view.path}`,
|
|
420
|
-
];
|
|
421
|
-
}
|
|
422
|
-
/** At most `limit` findings, one line each, and the command that prints all of them. */
|
|
423
|
-
export function formatFindingsSummary(view, fullCommand, limit = 3) {
|
|
424
|
-
if (view.state !== "ready" && view.state !== "stale")
|
|
425
|
-
return [`findings: ${view.message}`, ...(view.next ? [`next: ${view.next}`] : [])];
|
|
426
|
-
const shown = view.findings.slice(0, limit);
|
|
427
|
-
const design = view.designFindings;
|
|
428
|
-
return [
|
|
429
|
-
`findings: ${view.findings.length === 0 ? "none" : view.findings.length}${view.state === "stale" ? " (stale)" : ""}`,
|
|
430
|
-
...shown.map((finding) => {
|
|
431
|
-
const qualities = `${IMPACT_TEXT[finding.impact]}, ${finding.confidence} confidence, ${RECOVERY_TEXT[finding.recovery]}`;
|
|
432
|
-
const lead = findingLead(finding, finding.correction);
|
|
433
|
-
if (lead === null)
|
|
434
|
-
return `- ${finding.id} ${qualities}: ${finding.title}`;
|
|
435
|
-
return `- ${finding.id} ${lead.headline} (${lead.corrected ? "corrected in human review" : qualities})`;
|
|
436
|
-
}),
|
|
437
|
-
...(view.findings.length > limit
|
|
438
|
-
? [`- and ${plural(view.findings.length - limit, "more finding")}`]
|
|
439
|
-
: []),
|
|
440
|
-
...(design === null
|
|
441
|
-
? []
|
|
442
|
-
: [
|
|
443
|
-
`design findings: ${design.length === 0 ? "none" : design.length}`,
|
|
444
|
-
...design
|
|
445
|
-
.slice(0, limit)
|
|
446
|
-
.map((finding) => `- ${finding.id} ${finding.severity}: ${finding.headline}`),
|
|
447
|
-
...(design.length > limit
|
|
448
|
-
? [`- and ${plural(design.length - limit, "more design finding")}`]
|
|
449
|
-
: []),
|
|
450
|
-
]),
|
|
451
|
-
`all findings: ${fullCommand}`,
|
|
452
|
-
];
|
|
453
|
-
}
|
|
454
292
|
/**
|
|
455
293
|
* The end of a live run's human output, after its `analysis:` line: the findings of the analysis
|
|
456
294
|
* the run just completed. Prints nothing for --json, or when the run's analysis did not complete,
|
package/dist/cli/io.js
CHANGED
|
@@ -186,7 +186,12 @@ export function writeResult(command, io, result, formatHuman) {
|
|
|
186
186
|
}
|
|
187
187
|
else {
|
|
188
188
|
writeHuman(command, io, formatHuman(output));
|
|
189
|
-
|
|
189
|
+
// A run refused before it was created has no analysis to report, so its output ends with the
|
|
190
|
+
// refusal. Its JSON keeps the skip record.
|
|
191
|
+
if (output !== null &&
|
|
192
|
+
typeof output === "object" &&
|
|
193
|
+
"automaticAnalysis" in output &&
|
|
194
|
+
output.runId !== "not-created") {
|
|
190
195
|
const analysis = output.automaticAnalysis;
|
|
191
196
|
const cost = analysis?.result?.admission && refusedCost(analysis.result.admission);
|
|
192
197
|
const refusal = cost && costRefusal(cost, `--run ${analysis.result?.run ?? "latest"}`, cli);
|
package/dist/cli/update-check.js
CHANGED
|
@@ -61,7 +61,9 @@ function dayPassed(timestamp, now) {
|
|
|
61
61
|
* Only a person at a terminal is told, and only when nothing has switched the check off. A pipe,
|
|
62
62
|
* CI and a JSON document have a program reading them. A study participant runs the version the
|
|
63
63
|
* study chose, and a notice would send it to another. The telemetry switches turn this off too:
|
|
64
|
-
* someone who opted out of telemetry expects no unprompted requests.
|
|
64
|
+
* someone who opted out of telemetry expects no unprompted requests. NO_UPDATE_NOTIFIER is the
|
|
65
|
+
* switch the update-notifier package reads, and it is read the same way, so any value, even `0`,
|
|
66
|
+
* turns the check off for someone who set it for the CLIs built on that package.
|
|
65
67
|
*/
|
|
66
68
|
function checkAllowed(context) {
|
|
67
69
|
const env = context.env;
|
|
@@ -70,6 +72,7 @@ function checkAllowed(context) {
|
|
|
70
72
|
!context.ownCheckout &&
|
|
71
73
|
!envFlag(env.CI) &&
|
|
72
74
|
!envFlag(env.HUMANISH_NO_UPDATE_CHECK) &&
|
|
75
|
+
env.NO_UPDATE_NOTIFIER === undefined &&
|
|
73
76
|
!envFlag(env.HUMANISH_STUDY_PARTICIPANT) &&
|
|
74
77
|
!disabledByEnvironment(env));
|
|
75
78
|
}
|
|
@@ -54,12 +54,13 @@ export async function readCommsConnections(cwd) {
|
|
|
54
54
|
return empty();
|
|
55
55
|
if ((await lstat(file)).size > 65_536)
|
|
56
56
|
throw new Error("Connection configuration is too large.");
|
|
57
|
-
const
|
|
58
|
-
|
|
57
|
+
const read = await readContainedRegularFile(root, "comms.yaml", 65_536);
|
|
58
|
+
// Gone since the check above, or refused: either way it cannot be read safely.
|
|
59
|
+
if (read.status !== "read")
|
|
59
60
|
throw new Error("Cannot safely read connection configuration.");
|
|
60
61
|
// YAML diagnostics may echo the offending value; do not let them reach a status or frame.
|
|
61
62
|
try {
|
|
62
|
-
return parseConnections(parse(bytes.toString("utf8"), { maxAliasCount: 0 }));
|
|
63
|
+
return parseConnections(parse(read.bytes.toString("utf8"), { maxAliasCount: 0 }));
|
|
63
64
|
}
|
|
64
65
|
catch {
|
|
65
66
|
throw new Error("Invalid connection configuration.");
|