@counterfact-labs/cli 0.2.7 → 0.2.9
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/bundle/client-release.json +1 -1
- package/dist/adapters/analysis-results.js +87 -0
- package/dist/adapters/combined-results.js +27 -0
- package/dist/adapters/entry-files.js +5 -2
- package/dist/adapters/entry-packing.js +161 -0
- package/dist/adapters/entry-selection.js +13 -1
- package/dist/adapters/files.js +110 -5
- package/dist/adapters/git.js +10 -1
- package/dist/adapters/relay-plan.js +5 -7
- package/dist/adapters/release.js +4 -1
- package/dist/adapters/results.js +9 -2
- package/dist/adapters/selection.js +19 -9
- package/dist/adapters/settings.js +37 -6
- package/dist/adapters/source-relay-plan.js +7 -0
- package/dist/analysis-core/analysis/code-agents/skipped-files.js +1 -1
- package/dist/analysis-core/analysis/file-analysis-plan.js +1 -0
- package/dist/analysis-core/analysis/file-dependencies.js +1 -0
- package/dist/analysis-core/analysis/finding-activity-switch.js +1 -0
- package/dist/analysis-core/analysis/finding-identity.js +1 -0
- package/dist/analysis-core/analysis/finding-quality/policy.js +1 -0
- package/dist/analysis-core/analysis/folder-analysis-plan.js +1 -0
- package/dist/analysis-core/analysis/passes/full-file-passes.js +1 -0
- package/dist/analysis-core/analysis/project-architecture.js +1 -0
- package/dist/analysis-core/analysis/schemas.js +1 -1
- package/dist/analysis-core/chunks/2AASSZRO.js +35 -0
- package/dist/analysis-core/chunks/2YREYGOG.js +1 -0
- package/dist/analysis-core/chunks/33HRXJ5Q.js +1 -0
- package/dist/analysis-core/chunks/33IGHOYT.js +1 -0
- package/dist/analysis-core/chunks/4PT5C4IT.js +7 -0
- package/dist/analysis-core/chunks/4RXRAB5S.js +1 -0
- package/dist/analysis-core/chunks/4UDQBDDE.js +1 -0
- package/dist/analysis-core/chunks/{6RWEWBHK.js → 5B5UCWD3.js} +1 -1
- package/dist/analysis-core/chunks/6YCABS5T.js +1 -0
- package/dist/analysis-core/chunks/CQQ4TTXE.js +1 -0
- package/dist/analysis-core/chunks/CTLHKFPH.js +19 -0
- package/dist/analysis-core/chunks/F54DOWKN.js +6 -0
- package/dist/analysis-core/chunks/FRXOQ2M6.js +3 -0
- package/dist/analysis-core/chunks/HCPO6QPU.js +1 -0
- package/dist/analysis-core/chunks/HHFDNY6V.js +14 -0
- package/dist/analysis-core/chunks/{FBTOOUPL.js → HVQJKPQ4.js} +1 -1
- package/dist/analysis-core/chunks/INVTU73D.js +1 -0
- package/dist/analysis-core/chunks/JEWBPIQI.js +1 -0
- package/dist/analysis-core/chunks/JGK37AWN.js +1 -0
- package/dist/analysis-core/chunks/LFYTD7JF.js +1 -0
- package/dist/analysis-core/chunks/LMUXOKIM.js +1 -0
- package/dist/analysis-core/chunks/LOBIDUCA.js +1 -0
- package/dist/analysis-core/chunks/M5YWJY7P.js +1 -0
- package/dist/analysis-core/chunks/MGVIKA74.js +1 -0
- package/dist/analysis-core/chunks/MKYLK6DB.js +1 -0
- package/dist/analysis-core/chunks/MZOKZ3N6.js +1 -0
- package/dist/analysis-core/chunks/NXHVBJL4.js +1 -0
- package/dist/analysis-core/chunks/OKBK3JL3.js +1 -0
- package/dist/analysis-core/chunks/PBHHWFR6.js +1 -0
- package/dist/analysis-core/chunks/Q5WR6B2H.js +2 -0
- package/dist/analysis-core/chunks/QVXARKP7.js +1 -0
- package/dist/analysis-core/chunks/RQTNTIR4.js +1 -0
- package/dist/analysis-core/chunks/{RNTMH4MU.js → S27Y2TP5.js} +1 -1
- package/dist/analysis-core/chunks/SJESFYBA.js +10 -0
- package/dist/analysis-core/chunks/SP6XFHNH.js +1 -0
- package/dist/analysis-core/chunks/VDUFPTDD.js +1 -0
- package/dist/analysis-core/chunks/W24SFYZM.js +67 -0
- package/dist/analysis-core/chunks/YTHF2E2S.js +1 -0
- package/dist/analysis-core/chunks/YWL52FVW.js +2 -0
- package/dist/analysis-core/contracts/analysis-selection.js +1 -1
- package/dist/analysis-core/contracts/analysis-size.js +1 -0
- package/dist/analysis-core/contracts/execution-request.js +1 -0
- package/dist/analysis-core/contracts/intake-finding.js +1 -0
- package/dist/analysis-core/contracts/model-call-identity.js +1 -0
- package/dist/analysis-core/contracts/relay-analysis.js +1 -1
- package/dist/analysis-core/contracts/requests.js +1 -1
- package/dist/analysis-core/conventions/analysis-fit.js +1 -1
- package/dist/analysis-core/conventions/file-admission.js +1 -1
- package/dist/analysis-core/conventions/read-limits.js +1 -0
- package/dist/analysis-core/import/calls/modules.js +1 -1
- package/dist/analysis-core/import/entry-selection.js +1 -0
- package/dist/analysis-core/import/outside-loader-sources.js +1 -1
- package/dist/analysis-core/index.js +3 -3
- package/dist/analysis-core/requests/answer-formats.js +1 -1
- package/dist/analysis-core/requests/local-plan.js +1 -1
- package/dist/analysis-core/requests/planned-limits.js +1 -0
- package/dist/analysis-core/requests/registry.js +1 -1
- package/dist/analysis-core/requests/template-version.js +1 -1
- package/dist/analysis-core/requests/tools.js +1 -1
- package/dist/analysis-core/settings.js +1 -1
- package/dist/analysis-core/stages/engine-entry.js +1 -1
- package/dist/bundle/seal/engine.js +1 -1
- package/dist/cli/analysis-selection.js +47 -5
- package/dist/cli/api.js +1 -1
- package/dist/cli/commands/analyze.js +58 -15
- package/dist/cli/commands/skipped.js +65 -0
- package/dist/cli/context.js +2 -1
- package/dist/cli/finding-display.js +102 -7
- package/dist/cli/finding-settings.js +3 -1
- package/dist/cli/intake-payload.js +26 -2
- package/dist/cli/main.js +21 -7
- package/dist/cli/mcp-failure.js +6 -5
- package/dist/cli/mcp.js +134 -28
- package/dist/cli/relay-check.js +107 -23
- package/dist/cli/relay-failure.js +4 -4
- package/dist/cli/relay-preparation.js +50 -12
- package/dist/cli/result-check.js +31 -12
- package/dist/cli/saved-results.js +10 -0
- package/dist/cli/scope-choices.js +3 -3
- package/dist/cli/sign-in.js +2 -2
- package/dist/cli/skipped-files.js +22 -0
- package/dist/cli/summary.js +15 -1
- package/dist/contract/agent-choice.js +25 -2
- package/dist/contract/cli-api-messages.js +3 -0
- package/dist/contract/cli-refusals.js +31 -5
- package/dist/contract/cli-version.js +1 -1
- package/dist/contract/console-url.js +3 -0
- package/dist/contract/console.js +17 -38
- package/dist/contract/engine.js +1 -1
- package/dist/contract/messages.js +4 -3
- package/dist/contract/project-architecture.js +1 -0
- package/dist/contract/scope-choice.js +31 -11
- package/dist/contract/selection-offer.js +5 -8
- package/dist/gatekeeper/cli-api.js +70 -4
- package/dist/gatekeeper/console.js +19 -3
- package/dist/gatekeeper/model.js +5 -23
- package/dist/gatekeeper/trust/policy.js +2 -1
- package/package.json +4 -2
- package/dist/analysis-core/analysis/source-notes/types.js +0 -1
- package/dist/analysis-core/chunks/2PSNOSJ2.js +0 -1
- package/dist/analysis-core/chunks/2PZF54KH.js +0 -10
- package/dist/analysis-core/chunks/6B2HILSY.js +0 -1
- package/dist/analysis-core/chunks/6U2IR4XB.js +0 -1
- package/dist/analysis-core/chunks/7NEWZ74F.js +0 -1
- package/dist/analysis-core/chunks/CGCJN73D.js +0 -14
- package/dist/analysis-core/chunks/CWYVUGG4.js +0 -1
- package/dist/analysis-core/chunks/FSLWD2PE.js +0 -1
- package/dist/analysis-core/chunks/GOQVKWP3.js +0 -32
- package/dist/analysis-core/chunks/IHAVDX4L.js +0 -66
- package/dist/analysis-core/chunks/IVULXWQN.js +0 -21
- package/dist/analysis-core/chunks/K2GNDX3W.js +0 -1
- package/dist/analysis-core/chunks/K5FUABWW.js +0 -1
- package/dist/analysis-core/chunks/KGQYR62R.js +0 -7
- package/dist/analysis-core/chunks/KWECQJ6X.js +0 -1
- package/dist/analysis-core/chunks/PY5M22CL.js +0 -1
- package/dist/analysis-core/chunks/PYKAERZZ.js +0 -1
- package/dist/analysis-core/chunks/SKVEBOGA.js +0 -1
- package/dist/analysis-core/chunks/SLJ6CW7W.js +0 -1
- package/dist/analysis-core/chunks/UEORNVQB.js +0 -1
- package/dist/analysis-core/chunks/WYLIV2P2.js +0 -1
- package/dist/analysis-core/data/finding-types.js +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { formatSavedResults } from "../saved-results.js";
|
|
2
|
+
import { skipped } from "./skipped.js";
|
|
1
3
|
import { loadDevelopmentRelay } from "../development-relay.js";
|
|
2
4
|
import { loadInstalledRelease } from "../../adapters/release.js";
|
|
3
5
|
import { cliRefusals, START_FAILURE_MESSAGE } from "../../contract/cli-refusals.js";
|
|
@@ -10,11 +12,13 @@ import { GITIGNORE_OUTPUT_NOTICE } from "../../adapters/gitignore-output.js";
|
|
|
10
12
|
import { readFile } from "node:fs/promises";
|
|
11
13
|
import { resolve } from "node:path";
|
|
12
14
|
import { loadAgentProposal } from "../../contract/agent-proposal.js";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
+
import { fileScopeOf } from "../../analysis-core/analysis/file-analysis-plan.js";
|
|
16
|
+
import { folderPartsEnabled } from "../../analysis-core/settings.js";
|
|
17
|
+
import { captureArchitectureSettings, scopeChoicesEnabled } from "../../adapters/settings.js";
|
|
15
18
|
import { resolveRunSelection } from "../analysis-selection.js";
|
|
16
19
|
import { renderScopeChoices } from "../scope-choices.js";
|
|
17
20
|
import { progressLine } from "../../contract/scope-choice.js";
|
|
21
|
+
import { consoleCapabilities } from "../../gatekeeper/console.js";
|
|
18
22
|
// Only a real pause says "paused"; an unreviewed release or an upload-only server cannot start.
|
|
19
23
|
const unavailable = START_FAILURE_MESSAGE;
|
|
20
24
|
function requireLocal(value) {
|
|
@@ -34,36 +38,70 @@ function requireLocal(value) {
|
|
|
34
38
|
// A local-capable client never falls back to sending source files to the hosted upload API.
|
|
35
39
|
if (value.mode !== "local")
|
|
36
40
|
throw new ApiError(503, "unavailable", unavailable);
|
|
41
|
+
return consoleCapabilities(value);
|
|
37
42
|
}
|
|
38
|
-
export function parseAnalyzeOptions(args, choices = false) {
|
|
43
|
+
export function parseAnalyzeOptions(args, choices = false, multiple = false) {
|
|
39
44
|
let path, proposalFile, scopeFile;
|
|
45
|
+
let skipped;
|
|
46
|
+
let scope;
|
|
47
|
+
const paths = [];
|
|
40
48
|
for (let index = 0; index < args.length; index++) {
|
|
41
49
|
const arg = args[index];
|
|
42
|
-
if (
|
|
50
|
+
if (["--skipped", "--skipped-plan", "--show-skipped"].includes(arg) && skipped === undefined) {
|
|
51
|
+
skipped = arg === "--skipped" ? "analyze" : arg === "--skipped-plan" ? "plan" : "files";
|
|
52
|
+
}
|
|
53
|
+
else if (arg === "--proposal" && proposalFile === undefined) {
|
|
43
54
|
const value = args[++index];
|
|
44
55
|
if (!value || value.startsWith("-"))
|
|
45
56
|
throw new Error();
|
|
46
57
|
proposalFile = value;
|
|
47
58
|
}
|
|
48
|
-
else if (choices && arg === "--scope" && scopeFile === undefined) {
|
|
59
|
+
else if ((choices || args[index + 1]?.startsWith("[")) && arg === "--scope" && scope === undefined && scopeFile === undefined) {
|
|
49
60
|
const value = args[++index];
|
|
50
61
|
if (!value || value.startsWith("-"))
|
|
51
62
|
throw new Error();
|
|
52
|
-
|
|
63
|
+
if (value.startsWith("["))
|
|
64
|
+
scope = JSON.parse(value);
|
|
65
|
+
else
|
|
66
|
+
scopeFile = value;
|
|
67
|
+
}
|
|
68
|
+
else if (!arg.startsWith("-") && (path === undefined || multiple)) {
|
|
69
|
+
path ??= arg;
|
|
70
|
+
paths.push(arg);
|
|
53
71
|
}
|
|
54
|
-
else if (!arg.startsWith("-") && path === undefined)
|
|
55
|
-
path = arg;
|
|
56
72
|
else
|
|
57
73
|
throw new Error();
|
|
58
74
|
}
|
|
59
|
-
|
|
75
|
+
if (skipped && (paths.length > 1 || proposalFile || scope !== undefined || scopeFile !== undefined))
|
|
76
|
+
throw new Error();
|
|
77
|
+
return { path: path ?? ".", options: { ...(paths.length > 1 ? { paths } : {}), ...(scope === undefined ? {} : { scope }), ...(proposalFile === undefined ? {} : { proposalFile }), ...(scopeFile === undefined ? {} : { scopeFile }), ...(skipped ? { skipped } : {}) } };
|
|
60
78
|
}
|
|
61
79
|
export async function analyze(path, options, ctx) {
|
|
80
|
+
if (Array.isArray(options.scope) && (options.proposal !== undefined || options.proposalFile !== undefined)) {
|
|
81
|
+
throw new ApiError(400, "invalid_body", cliRefusals.invalid_body);
|
|
82
|
+
}
|
|
83
|
+
if (options.skipped)
|
|
84
|
+
return skipped(path, options.skipped, ctx, analyze, options.surface);
|
|
62
85
|
const startup = ctx.startup;
|
|
63
|
-
|
|
86
|
+
let settings = startup.settings;
|
|
87
|
+
if (Array.isArray(options.scope)) {
|
|
88
|
+
try {
|
|
89
|
+
if (!folderPartsEnabled(settings))
|
|
90
|
+
throw new Error();
|
|
91
|
+
const scope = fileScopeOf({ ...settings, CF_PART_FILES: JSON.stringify(options.scope) });
|
|
92
|
+
if (!scope?.length)
|
|
93
|
+
throw new Error();
|
|
94
|
+
settings = Object.freeze({ ...settings, CF_PART_FILES: JSON.stringify(scope) });
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
throw new ApiError(400, "invalid_body", cliRefusals.invalid_body);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const scope = Array.isArray(options.scope) ? undefined : options.scope;
|
|
64
101
|
const proposal = await loadAgentProposal(async () => options.proposalFile === undefined ? options.proposal
|
|
65
102
|
: JSON.parse(await readFile(resolve(ctx.cwd ?? process.cwd(), options.proposalFile), "utf8")), startup.settings);
|
|
66
|
-
|
|
103
|
+
const mode = await (ctx.consoleMode?.() ?? ctx.api.request("checks/mode"));
|
|
104
|
+
const capabilities = requireLocal(mode);
|
|
67
105
|
const development = startup.transport.CF_RELAY_DEV === "1" ? await loadDevelopmentRelay(startup.transport) : undefined;
|
|
68
106
|
const release = development?.release ?? ctx.relayRuntime?.release ?? await loadInstalledRelease();
|
|
69
107
|
if (release.projectContext !== "checks")
|
|
@@ -71,7 +109,7 @@ export async function analyze(path, options, ctx) {
|
|
|
71
109
|
const token = await ctx.token?.();
|
|
72
110
|
if (!token)
|
|
73
111
|
throw new ApiError(401, "unauthorized", cliApiMessages.unauthorized);
|
|
74
|
-
const selection = await resolveRunSelection(path, ctx.cwd ?? process.cwd(),
|
|
112
|
+
const selection = await resolveRunSelection(path, ctx.cwd ?? process.cwd(), settings, { ...options, scope });
|
|
75
113
|
const folder = selection?.path ?? resolveAgentFolder(path, ctx.cwd);
|
|
76
114
|
const config = Object.freeze({ ...startup.transport, CF_RELAY_URL: startup.transport.CF_RELAY_URL ?? release.relayUrl,
|
|
77
115
|
CF_LOCAL_RELAY: "1", CF_CONSOLE_URL: ctx.api.baseUrl, CF_CONSOLE_TOKEN: token });
|
|
@@ -87,10 +125,15 @@ export async function analyze(path, options, ctx) {
|
|
|
87
125
|
let choice;
|
|
88
126
|
try {
|
|
89
127
|
const code = await (ctx.relayRuntime?.run ?? runRelayCheck)(folder, {
|
|
90
|
-
|
|
128
|
+
analyzedFolder: options.analyzedFolder ?? path,
|
|
129
|
+
settings: captureArchitectureSettings(settings, config), config, release, skippedFilesSupported: capabilities.skippedFiles,
|
|
130
|
+
consoleSupportsActivity: capabilities.findingActivity, consoleSupportsArchitecture: capabilities.architecture,
|
|
131
|
+
cwd: ctx.cwd, connect: development?.connect ?? ctx.relayRuntime?.connect,
|
|
91
132
|
...(ctx.relayRuntime?.selectionProvider ? { selectionProvider: ctx.relayRuntime.selectionProvider } : {}),
|
|
92
133
|
...(proposal ? { proposal } : {}),
|
|
93
134
|
...(selection ? { selection } : {}), ...(scope ? { scope } : {}),
|
|
135
|
+
...(options.addToAnalysisId ? { addToAnalysisId: options.addToAnalysisId, expectedProjectId: options.expectedProjectId } : {}),
|
|
136
|
+
...(options.retryUpload ? { retryUpload: true } : {}),
|
|
94
137
|
signal: ctx.signal ? AbortSignal.any([controller.signal, ctx.signal]) : controller.signal,
|
|
95
138
|
stdout() { }, stderr() { },
|
|
96
139
|
onStart: () => { ctx.onCheckStart?.(); ctx.write(`\r\u001b[2K${progressLine(selection, "terminal")}`); progressShown = true; },
|
|
@@ -117,8 +160,8 @@ export async function analyze(path, options, ctx) {
|
|
|
117
160
|
onSaved: result => {
|
|
118
161
|
clearProgress();
|
|
119
162
|
ctx.onCheckComplete?.(result);
|
|
120
|
-
|
|
121
|
-
|
|
163
|
+
if (!options.deferResults)
|
|
164
|
+
ctx.write(formatSavedResults(result, settings, ctx.findingNumbers, !options.addToAnalysisId, { agentReadableResults: ctx.findingSettings.agentReadableResults, surface: options.surface, analyzedFolder: options.analyzedFolder ?? path }));
|
|
122
165
|
},
|
|
123
166
|
});
|
|
124
167
|
if (choice && !ctx.onScopeChoice && selection)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { followupSettings, skippedFilesEnabled } from "../../analysis-core/settings.js";
|
|
2
|
+
import { loadAnalysisResults } from "../../adapters/analysis-results.js";
|
|
3
|
+
import { readLocalFolder } from "../../adapters/files.js";
|
|
4
|
+
import { loadCompletion } from "../../adapters/completion.js";
|
|
5
|
+
import { sizeSkippedFiles } from "../../analysis-core/analysis/code-agents/skipped-files.js";
|
|
6
|
+
import { followupPlanText } from "../skipped-files.js";
|
|
7
|
+
import { resolveAgentFolder } from "../agent-folder.js";
|
|
8
|
+
import { formatSavedResults } from "../saved-results.js";
|
|
9
|
+
export async function skipped(path, action, ctx, run, surface) {
|
|
10
|
+
if (!skippedFilesEnabled(ctx.startup.settings))
|
|
11
|
+
throw new Error("Skipped-file analyses are not enabled.");
|
|
12
|
+
const folder = resolveAgentFolder(path, ctx.cwd);
|
|
13
|
+
let latest;
|
|
14
|
+
let mode;
|
|
15
|
+
const deferred = { ...ctx, consoleMode: ctx.consoleMode ?? (() => mode ??= ctx.api.request("checks/mode")),
|
|
16
|
+
onCheckComplete: (result) => { latest = result; } };
|
|
17
|
+
const publish = () => {
|
|
18
|
+
if (!latest)
|
|
19
|
+
return;
|
|
20
|
+
ctx.onCheckComplete?.(latest);
|
|
21
|
+
ctx.write(formatSavedResults(latest, ctx.startup.settings, ctx.findingNumbers, false, { agentReadableResults: ctx.findingSettings.agentReadableResults, surface }));
|
|
22
|
+
};
|
|
23
|
+
// Finish an already completed upload before planning; its reading must not be purchased again.
|
|
24
|
+
const pending = await loadCompletion(folder);
|
|
25
|
+
const unsettled = pending?.uploaded ? await loadAnalysisResults(folder).then(saved => saved.context.pending?.checkId === pending.payload.checkId).catch(() => false) : false;
|
|
26
|
+
if (pending && !pending.finished && (!pending.uploaded || unsettled)) {
|
|
27
|
+
const recovered = await run(folder, { retryUpload: true, deferResults: true, surface }, deferred);
|
|
28
|
+
if (recovered !== 0) {
|
|
29
|
+
if (action === "analyze")
|
|
30
|
+
publish();
|
|
31
|
+
return recovered;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let saved;
|
|
35
|
+
try {
|
|
36
|
+
saved = await loadAnalysisResults(folder);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new Error("The saved analysis is missing or unreadable. Run an analysis of this folder first.");
|
|
40
|
+
}
|
|
41
|
+
const paths = sizeSkippedFiles(saved.report.refusedFiles).map(file => file.path);
|
|
42
|
+
if (action === "files") {
|
|
43
|
+
ctx.write(paths.length ? `${paths.join("\n")}\n` : "No skipped files remain to analyze.\n");
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
const settings = followupSettings(ctx.startup.settings, saved.context.settings);
|
|
47
|
+
const source = await readLocalFolder(settings, folder, undefined, { planSkipped: paths });
|
|
48
|
+
const plan = source.filePlan;
|
|
49
|
+
ctx.write(followupPlanText(plan));
|
|
50
|
+
if (action === "plan")
|
|
51
|
+
return 0;
|
|
52
|
+
// Groups share the same local result files; save one completely before starting the next.
|
|
53
|
+
for (const group of plan.groups) {
|
|
54
|
+
ctx.signal?.throwIfAborted();
|
|
55
|
+
const scoped = { ...deferred,
|
|
56
|
+
startup: { ...ctx.startup, settings: Object.freeze({ ...settings, CF_PART_FILES: JSON.stringify(group.files) }) } };
|
|
57
|
+
const code = await run(folder, { deferResults: true, surface, addToAnalysisId: saved.context.analysisId, expectedProjectId: saved.context.projectId }, scoped);
|
|
58
|
+
if (code !== 0) {
|
|
59
|
+
publish();
|
|
60
|
+
return code;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
publish();
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
package/dist/cli/context.js
CHANGED
|
@@ -11,11 +11,12 @@ export function commandContext(options = {}) {
|
|
|
11
11
|
const startup = options.startup ?? readCliSettings(options.env, options.emailSignIn);
|
|
12
12
|
const homeDir = options.homeDir ?? homedir();
|
|
13
13
|
const baseUrl = (options.baseUrl ?? startup.baseUrl ?? DEFAULT_URL).replace(/\/+$/, "");
|
|
14
|
-
const api = new ApiClient(baseUrl, () => readCredentials(homeDir, api.baseUrl), undefined, options.signal);
|
|
14
|
+
const api = new ApiClient(baseUrl, () => readCredentials(homeDir, api.baseUrl), undefined, options.signal, startup.transport.CF_CLI_TRANSPORT_ERRORS !== "0");
|
|
15
15
|
const write = options.write ?? (text => { process.stdout.write(text); });
|
|
16
16
|
const findingSettings = options.findingSettings ?? readFindingSettings(options.env);
|
|
17
17
|
return {
|
|
18
18
|
findingNumbers: findingSettings.numbersEnabled,
|
|
19
|
+
findingSettings,
|
|
19
20
|
signInRetry: (options.env ?? process.env).CF_SIGN_IN_RETRY !== "0",
|
|
20
21
|
startup, signal: options.signal, relayRuntime: options.relayRuntime, cwd: options.cwd ?? process.cwd(),
|
|
21
22
|
token: () => readCredentials(homeDir, api.baseUrl), onCheckComplete: options.onCheckComplete, onCheckStart: options.onCheckStart, onAgentChoice: options.onAgentChoice,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { stripVTControlCharacters } from "node:util";
|
|
2
|
+
import { groupByActivity, groupByAudience } from "../analysis-core/analysis/schemas.js";
|
|
2
3
|
export const TEAM_WORKFLOW_HEADING = "Team workflow";
|
|
4
|
+
export const notActiveCountLine = (count) => `${count} more ${count === 1 ? "finding is" : "findings are"} about code that doesn't run today (not active)`;
|
|
5
|
+
export const activityAdvice = (activity) => activity.state === "dead" ? "Remove it (cleanup)."
|
|
6
|
+
: `Fix this before turning on ${activity.gate}, or remove it if you don't plan to use it.`;
|
|
3
7
|
export const formatFindingNumber = (seq) => `F-${seq}`;
|
|
4
8
|
/** Intake returns one id per payload finding in order; the optional number mapping is joined by id. */
|
|
5
9
|
export function savedFindings(result, report) {
|
|
@@ -7,28 +11,119 @@ export function savedFindings(result, report) {
|
|
|
7
11
|
return result.findingIds.map((id, index) => {
|
|
8
12
|
const numbered = numbers.get(id), local = report[index];
|
|
9
13
|
return { id, ...(numbered ? { seq: numbered.seq, number: formatFindingNumber(numbered.seq) } : {}),
|
|
10
|
-
title: local?.title ?? "", ...(local?.audience === undefined ? {} : { audience: local.audience })
|
|
14
|
+
title: local?.title ?? "", ...(local?.audience === undefined ? {} : { audience: local.audience }),
|
|
15
|
+
...(local?.activity === undefined ? {} : { activity: local.activity }),
|
|
16
|
+
...(local?.activity === undefined || local.fixes === undefined ? {} : { fixes: local.fixes.map(({ title, approach }) => ({ title: title ?? "", approach: approach ?? "" })) }),
|
|
17
|
+
...(local?.severity === undefined ? {} : { severity: local.severity }),
|
|
18
|
+
...(local?.site === undefined ? {} : { site: local.site }),
|
|
19
|
+
...(local?.fixes === undefined ? {} : { shortFix: shortFix(local.fixes[0]) }) };
|
|
11
20
|
});
|
|
12
21
|
}
|
|
22
|
+
function findingLabel(value, enabled, index, labels) {
|
|
23
|
+
return labels === "position" ? `${index + 1}.` : enabled && value.seq !== undefined ? formatFindingNumber(value.seq) : value.id ?? `${index + 1}.`;
|
|
24
|
+
}
|
|
13
25
|
function findingLine(value, enabled, index, labels, audienceEnabled) {
|
|
14
26
|
// The off switch preserves the original Markdown summary, including whitespace.
|
|
15
27
|
if (labels === "position" && !audienceEnabled)
|
|
16
28
|
return `${index + 1}. ${value.title}`;
|
|
17
29
|
const title = value.title.replace(/[\r\n\u2028\u2029]+/g, " ");
|
|
18
|
-
const label =
|
|
30
|
+
const label = findingLabel(value, enabled, index, labels);
|
|
19
31
|
return `${label}${title ? ` ${title}` : ""}`;
|
|
20
32
|
}
|
|
21
|
-
export function findingDisplayRows(findings, { numbersEnabled = true, audienceEnabled, labels }) {
|
|
22
|
-
const
|
|
33
|
+
export function findingDisplayRows(findings, { numbersEnabled = true, audienceEnabled, notActiveEnabled = false, labels }) {
|
|
34
|
+
const { active } = groupByActivity(findings, notActiveEnabled);
|
|
35
|
+
const groups = audienceEnabled ? groupByAudience(active) : { users: active, team: [] };
|
|
23
36
|
return [...groups.users, ...groups.team].map((finding, index) => ({ finding,
|
|
24
37
|
label: findingLine(finding, numbersEnabled, index, labels, audienceEnabled), startsTeamGroup: groups.team.length > 0 && index === groups.users.length }));
|
|
25
38
|
}
|
|
26
|
-
|
|
39
|
+
function legacyFindingList(result, options) {
|
|
40
|
+
const groups = groupByActivity(result.findings ?? [], options.notActiveEnabled === true);
|
|
41
|
+
const collapsed = groups.notActive.length ? notActiveCountLine(groups.notActive.length) + "\n" : "";
|
|
27
42
|
if (options.numbersEnabled !== false && result.findings?.some(value => value.seq !== undefined)) {
|
|
28
|
-
return "Findings:\n" + findingDisplayRows(result.findings, { ...options, labels: "reference" }).map(row => `${row.startsTeamGroup ? `${TEAM_WORKFLOW_HEADING}:\n` : ""}${row.label}\n`).join("");
|
|
43
|
+
return "Findings:\n" + findingDisplayRows(result.findings, { ...options, labels: "reference" }).map(row => `${row.startsTeamGroup ? `${TEAM_WORKFLOW_HEADING}:\n` : ""}${row.label}\n`).join("") + collapsed;
|
|
29
44
|
}
|
|
45
|
+
if (collapsed)
|
|
46
|
+
return (groups.active.length ? `Finding ids: ${groups.active.map(f => f.id).join(", ")}\n` : "") + collapsed;
|
|
30
47
|
return result.findingIds?.length ? `Finding ids: ${result.findingIds.join(", ")}\n` : "";
|
|
31
48
|
}
|
|
49
|
+
export function formatFindingActivity(activity) {
|
|
50
|
+
return `${activity.state === "dead" ? "Dead" : "Switched off"}: ${activity.reason}\n${activityAdvice(activity)}\n`;
|
|
51
|
+
}
|
|
52
|
+
// This is a presentation budget, separate from the size of the saved report.
|
|
53
|
+
export const AGENT_FINDING_LIST_BUDGET = 12_000;
|
|
54
|
+
function shortFix(fix) {
|
|
55
|
+
return [fix?.summary, fix?.title, fix?.approach].find(text => text?.trim()) ?? "";
|
|
56
|
+
}
|
|
57
|
+
function compactText(value, limit) {
|
|
58
|
+
const text = stripVTControlCharacters(value).replace(/[\p{Cc}\s]+/gu, " ").replace(/\s*\u2014\s*/g, ", ").trim();
|
|
59
|
+
if (text.length <= limit)
|
|
60
|
+
return text;
|
|
61
|
+
// Avoid ending on half of a supplementary Unicode character.
|
|
62
|
+
return Array.from(text.slice(0, limit - 3)).filter(char => !/^[\ud800-\udbff]$/.test(char)).join("") + "...";
|
|
63
|
+
}
|
|
64
|
+
function sentence(text) { return /[.!?]$/.test(text) ? text : `${text}.`; }
|
|
65
|
+
function compactFinding(value) {
|
|
66
|
+
const compact = legacyFinding(value);
|
|
67
|
+
// Full fixes remain in saved detail; compact lists use only the existing short fix.
|
|
68
|
+
delete compact.fixes;
|
|
69
|
+
return { ...compact, title: compactText(value.title, 160) || "Untitled finding",
|
|
70
|
+
severity: compactText(value.severity ?? "", 24) || "severity unavailable",
|
|
71
|
+
...(value.site ? { site: { path: compactText(value.site.path, 240), line: value.site.line } } : {}),
|
|
72
|
+
shortFix: compactText(value.shortFix ?? "", 240) || "No suggested fix available." };
|
|
73
|
+
}
|
|
74
|
+
function legacyFinding(value) {
|
|
75
|
+
const original = { ...value };
|
|
76
|
+
delete original.severity;
|
|
77
|
+
delete original.site;
|
|
78
|
+
delete original.shortFix;
|
|
79
|
+
return original;
|
|
80
|
+
}
|
|
81
|
+
export function findingDetailReference(value, projectId) {
|
|
82
|
+
return { id: value.seq !== undefined ? formatFindingNumber(value.seq) : value.id, ...(projectId ? { projectId } : {}) };
|
|
83
|
+
}
|
|
84
|
+
export function formatFindingContinuation(next) {
|
|
85
|
+
return `Next finding: get_finding(${JSON.stringify(next)})\n`;
|
|
86
|
+
}
|
|
87
|
+
export function presentSavedFindings(result, options) {
|
|
88
|
+
const rows = findingDisplayRows(result.findings ?? [], { ...options, labels: "reference" });
|
|
89
|
+
const orderedReferences = rows.flatMap(({ finding }) => finding.id ? [{ id: finding.id, seq: finding.seq }] : []);
|
|
90
|
+
if (options.agentReadableResults === false || options.numbersEnabled === false || !rows.length) {
|
|
91
|
+
return { text: legacyFindingList(result, options), findings: rows.map(row => legacyFinding(row.finding)),
|
|
92
|
+
findingIds: result.findingIds ?? [], nextFinding: undefined, orderedReferences };
|
|
93
|
+
}
|
|
94
|
+
const { notActive } = groupByActivity(result.findings ?? [], options.notActiveEnabled === true);
|
|
95
|
+
const collapsed = notActive.length ? notActiveCountLine(notActive.length) + "\n" : "";
|
|
96
|
+
let text = "Findings:\n";
|
|
97
|
+
const findings = [];
|
|
98
|
+
let nextFinding;
|
|
99
|
+
for (const [index, row] of rows.entries()) {
|
|
100
|
+
const value = compactFinding(row.finding);
|
|
101
|
+
const label = findingLabel(value, true, index, "reference");
|
|
102
|
+
const site = value.site ? `${value.site.path}:${value.site.line ?? "?"}` : "File unavailable";
|
|
103
|
+
const line = `${row.startsTeamGroup ? `${TEAM_WORKFLOW_HEADING}:\n` : ""}${label} (${value.severity}) ${sentence(value.title)} ${site}. Fix: ${value.shortFix}\n`;
|
|
104
|
+
const next = rows[index + 1]?.finding;
|
|
105
|
+
const footer = next ? overflowLine(rows.length - index - 1, next, options) : "";
|
|
106
|
+
const candidate = { text: text + line + footer + collapsed, findings: [...findings, value],
|
|
107
|
+
findingIds: [...findings, value].flatMap(finding => finding.id ? [finding.id] : []),
|
|
108
|
+
...(next ? { nextFinding: findingDetailReference(next, options.projectId) } : {}) };
|
|
109
|
+
if (JSON.stringify(candidate).length > AGENT_FINDING_LIST_BUDGET) {
|
|
110
|
+
nextFinding = findingDetailReference(row.finding, options.projectId);
|
|
111
|
+
text += overflowLine(rows.length - index, row.finding, options);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
text += line;
|
|
115
|
+
findings.push(value);
|
|
116
|
+
}
|
|
117
|
+
return { text: text + collapsed, findings, findingIds: findings.flatMap(value => value.id ? [value.id] : []), nextFinding, orderedReferences };
|
|
118
|
+
}
|
|
119
|
+
function overflowLine(count, next, options) {
|
|
120
|
+
const guidance = options.surface === "mcp" ? `Next: get_finding(${JSON.stringify(findingDetailReference(next, options.projectId))}); follow nextFinding for the rest.`
|
|
121
|
+
: `Run counterfact-labs finding ${next.id} for the next one, or open Full results below.`;
|
|
122
|
+
return `${count} more ${count === 1 ? "finding" : "findings"}. ${guidance}\n`;
|
|
123
|
+
}
|
|
124
|
+
export function formatSavedFindings(result, options) {
|
|
125
|
+
return presentSavedFindings(result, options).text;
|
|
126
|
+
}
|
|
32
127
|
export function formatWaitingFixes(items, enabled, needsProject) {
|
|
33
128
|
if (!items.length)
|
|
34
129
|
return "No waiting fixes.";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { findingAudienceEnabled } from "../analysis-core/analysis/finding-audience-switch.js";
|
|
2
|
+
import { notActiveEnabled } from "../analysis-core/analysis/finding-activity-switch.js";
|
|
2
3
|
export function readFindingSettings(env = process.env, bundle) {
|
|
3
4
|
return { numbersEnabled: env.CF_FINDING_NUMBERS !== "0", fixesEnabled: env.CF_WAITING_FIXES !== "0",
|
|
4
|
-
audienceEnabled: findingAudienceEnabled(env, bundle)
|
|
5
|
+
audienceEnabled: findingAudienceEnabled(env, bundle), notActiveEnabled: notActiveEnabled(env),
|
|
6
|
+
agentReadableResults: env.CF_AGENT_READABLE_RESULTS !== "0" };
|
|
5
7
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { skippedFilesEnabled } from "../analysis-core/settings.js";
|
|
2
|
+
import { fingerprintOf } from "../analysis-core/analysis/finding-identity.js";
|
|
1
3
|
import { resultSafetyCheckEnabled } from "../analysis-core/settings.js";
|
|
2
4
|
import { localFindingsPayloadSchema } from "../contract/console.js";
|
|
3
5
|
import { projectPublicFinding } from "../contract/public-report.js";
|
|
4
6
|
import { checkIntakeFindings } from "./result-check.js";
|
|
5
7
|
import { RESULTS_TOO_LARGE_MESSAGE } from "../contract/cli-refusals.js";
|
|
8
|
+
import { projectArchitecture } from "../contract/project-architecture.js";
|
|
6
9
|
export function buildIntakePayload(identity, agent, report, options = {}) {
|
|
7
10
|
const repo = (key) => key === null || key === report.repoKey ? agent.repository : key;
|
|
8
11
|
let findings = report.findings.map(finding => projectPublicFinding(finding, repo));
|
|
@@ -19,8 +22,29 @@ export function buildIntakePayload(identity, agent, report, options = {}) {
|
|
|
19
22
|
resultCheck = counts;
|
|
20
23
|
findingIndexes = acceptedIndexes;
|
|
21
24
|
}
|
|
22
|
-
const
|
|
25
|
+
const metadata = !skippedFilesEnabled(options.settings ?? {}) ? {} : {
|
|
26
|
+
...(options.coverage ? { coverage: options.coverage } : {}),
|
|
27
|
+
...(options.addToAnalysisId ? { addToAnalysisId: options.addToAnalysisId } : {}),
|
|
28
|
+
...(options.findingAssessments ? { findingAssessments: findingIndexes.flatMap((index, at) => {
|
|
29
|
+
const assessment = options.findingAssessments.find(row => row.fingerprint === fingerprintOf(report.findings[index]));
|
|
30
|
+
return assessment ? [{ ...assessment, fingerprint: fingerprintOf(findings[at]) }] : [];
|
|
31
|
+
}) } : {}),
|
|
32
|
+
};
|
|
33
|
+
const findingActivities = findings.map(finding => finding.activity);
|
|
34
|
+
// Older consoles use a strict schema. Local grouping survives without sending an unsupported field.
|
|
35
|
+
if (options.consoleSupportsActivity !== true)
|
|
36
|
+
findings = findings.map(finding => {
|
|
37
|
+
const legacy = { ...finding };
|
|
38
|
+
delete legacy.activity;
|
|
39
|
+
return legacy;
|
|
40
|
+
});
|
|
41
|
+
const architecture = options.consoleSupportsArchitecture === true && options.settings?.CF_LOCAL_ARCHITECTURE === "1"
|
|
42
|
+
? projectArchitecture(report.graph) : undefined;
|
|
43
|
+
let result = localFindingsPayloadSchema.safeParse({ ...identity, agent, findings, ...metadata, ...(architecture ? { architecture } : {}) });
|
|
44
|
+
// Optional map metadata must not crowd otherwise valid findings out of the total upload allowance.
|
|
45
|
+
if (!result.success && architecture)
|
|
46
|
+
result = localFindingsPayloadSchema.safeParse({ ...identity, agent, findings, ...metadata });
|
|
23
47
|
if (!result.success)
|
|
24
48
|
throw new Error(RESULTS_TOO_LARGE_MESSAGE);
|
|
25
|
-
return { payload: result.data, resultCheck, findingIndexes };
|
|
49
|
+
return { payload: result.data, resultCheck, findingIndexes, findingActivities };
|
|
26
50
|
}
|
package/dist/cli/main.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
3
|
import { realpath } from "node:fs/promises";
|
|
4
4
|
import { errorCode, errorText } from "./api.js";
|
|
5
|
-
import { readEmailSignIn, readSelectionSettings, scopeChoicesEnabled } from "../adapters/settings.js";
|
|
5
|
+
import { fileScopeEnabled, readEmailSignIn, readSelectionSettings, scopeChoicesEnabled, multiEntryEnabled } from "../adapters/settings.js";
|
|
6
6
|
import { commandContext } from "./context.js";
|
|
7
7
|
import { analyze, parseAnalyzeOptions } from "./commands/analyze.js";
|
|
8
8
|
import { finding } from "./commands/finding.js";
|
|
@@ -10,23 +10,35 @@ import { balance } from "./commands/balance.js";
|
|
|
10
10
|
import { login, parseLoginOptions } from "./commands/login.js";
|
|
11
11
|
import { logout } from "./commands/logout.js";
|
|
12
12
|
import { serveMcp } from "./mcp.js";
|
|
13
|
+
import { folderPartsEnabled, skippedFilesEnabled } from "../analysis-core/settings.js";
|
|
13
14
|
import { loadAnalysisScope } from "./scope-choices.js";
|
|
14
15
|
import { FILE_SCOPE_PUBLIC_USAGE } from "../contract/scope-choice.js";
|
|
15
|
-
const usage = "Usage: counterfact-labs analyze [path] [--proposal <json-file>]\n counterfact-labs finding <id>\n counterfact-labs balance | login | logout | mcp\n";
|
|
16
16
|
export async function run(args, options = {}) {
|
|
17
|
+
const skippedUsage = skippedFilesEnabled(options.startup?.settings ?? options.env ?? process.env)
|
|
18
|
+
? " counterfact-labs analyze [path] [--show-skipped | --skipped-plan | --skipped]\n" : "";
|
|
19
|
+
const usage = `Usage: counterfact-labs analyze [path] [--proposal <json-file>]\n${skippedUsage} counterfact-labs finding <id>\n counterfact-labs balance | login | logout | mcp\n`;
|
|
17
20
|
const write = options.write ?? (text => { process.stdout.write(text); });
|
|
18
21
|
const emailSignIn = options.emailSignIn ?? readEmailSignIn(options.env);
|
|
22
|
+
let fileMode = false;
|
|
19
23
|
try {
|
|
20
24
|
const [command, ...rest] = args;
|
|
21
25
|
const env = options.startup?.settings ?? options.env ?? process.env;
|
|
22
26
|
// Help follows the CLI defaults without validating unrelated commands' analysis switches.
|
|
23
27
|
const selectionSettings = readSelectionSettings(env, { validate: command === "analyze" || command === "mcp" });
|
|
28
|
+
fileMode = fileScopeEnabled(selectionSettings);
|
|
24
29
|
const commandOptions = { ...options, emailSignIn, env: { ...(options.env ?? process.env), ...selectionSettings,
|
|
25
30
|
...(command === "analyze" || command === "mcp" ? {} : {
|
|
26
|
-
CF_FILE_SCOPE: undefined, CF_SCOPE_CHOICES: undefined, CF_LOCAL_IMPORT_RESOLUTION: undefined,
|
|
31
|
+
CF_FILE_SCOPE: undefined, CF_SCOPE_CHOICES: undefined, CF_LOCAL_IMPORT_RESOLUTION: undefined, CF_MULTI_ENTRY: undefined,
|
|
27
32
|
}) } };
|
|
28
33
|
const choices = scopeChoicesEnabled(selectionSettings);
|
|
29
|
-
const
|
|
34
|
+
const scopeUsage = choices ? FILE_SCOPE_PUBLIC_USAGE : usage;
|
|
35
|
+
// Insert the skipped-files usage line after the first usage line.
|
|
36
|
+
const firstLineEnd = scopeUsage.indexOf("\n") + 1;
|
|
37
|
+
let commandUsage = choices && firstLineEnd > 0
|
|
38
|
+
? `${scopeUsage.slice(0, firstLineEnd)}${skippedUsage}${scopeUsage.slice(firstLineEnd)}`
|
|
39
|
+
: scopeUsage;
|
|
40
|
+
if (folderPartsEnabled(env))
|
|
41
|
+
commandUsage = commandUsage.replace(choices ? "[--scope <json-file>]" : "[--proposal <json-file>]", choices ? "[--scope <json-file | json-list>]" : "[--proposal <json-file>] [--scope <json-list>]");
|
|
30
42
|
if (["--help", "-h", "help"].includes(command) || !command) {
|
|
31
43
|
write(commandUsage);
|
|
32
44
|
return 0;
|
|
@@ -34,13 +46,15 @@ export async function run(args, options = {}) {
|
|
|
34
46
|
if (command === "analyze") {
|
|
35
47
|
let parsed;
|
|
36
48
|
try {
|
|
37
|
-
parsed = parseAnalyzeOptions(rest, choices);
|
|
49
|
+
parsed = parseAnalyzeOptions(rest, choices, multiEntryEnabled(selectionSettings));
|
|
38
50
|
}
|
|
39
51
|
catch {
|
|
40
52
|
throw new Error(commandUsage.trimEnd());
|
|
41
53
|
}
|
|
42
54
|
const ctx = commandContext(commandOptions);
|
|
43
|
-
|
|
55
|
+
if (Array.isArray(parsed.options.scope) && !folderPartsEnabled(ctx.startup.settings))
|
|
56
|
+
throw new Error(commandUsage.trimEnd());
|
|
57
|
+
const scope = Array.isArray(parsed.options.scope) ? parsed.options.scope : await loadAnalysisScope(parsed.options, ctx.cwd ?? process.cwd(), ctx.startup.settings);
|
|
44
58
|
return await analyze(parsed.path, { ...parsed.options, scope }, ctx);
|
|
45
59
|
}
|
|
46
60
|
if (command === "finding" && rest.length === 1 && !rest[0].startsWith("-"))
|
|
@@ -70,7 +84,7 @@ export async function run(args, options = {}) {
|
|
|
70
84
|
throw new Error(commandUsage.trimEnd());
|
|
71
85
|
}
|
|
72
86
|
catch (error) {
|
|
73
|
-
write(errorText(error) + "\n");
|
|
87
|
+
write(errorText(error, fileMode) + "\n");
|
|
74
88
|
return errorCode(error);
|
|
75
89
|
}
|
|
76
90
|
}
|
package/dist/cli/mcp-failure.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { CliRefusalError, failureReason, START_FAILURE_MESSAGE } from "../contract/cli-refusals.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const message =
|
|
6
|
-
:
|
|
2
|
+
import { cliApiMessages } from "../contract/cli-api-messages.js";
|
|
3
|
+
import { errorText, SavedSignInError } from "./api.js";
|
|
4
|
+
export function mcpFailure(error, enabled, fileMode = true) {
|
|
5
|
+
const message = error instanceof SavedSignInError ? cliApiMessages.unreadable_sign_in
|
|
6
|
+
: !enabled && !fileMode && error instanceof CliRefusalError && error.code === "outside_project"
|
|
7
|
+
? START_FAILURE_MESSAGE : errorText(error, fileMode);
|
|
7
8
|
return { message, ...(enabled ? { reason: failureReason(error) } : {}) };
|
|
8
9
|
}
|