fix(cli): correct completion options and setup guidance (#7851)

* fix(cli): align MCP doctor completion with its options

* fix(cli): explain zsh completion initialization

* fix(cli): include launcher timing in doctor completion

* style(cli): format doctor completion schema
This commit is contained in:
morluto authored and GitHub committed 2026-10-29 01:28:04 +08:00
1 parent f9e7571a3d
commit 60ab4da329
6 files changed
+166 -44

No files matched your search

+9
View File
@@ -165,6 +165,15 @@ into the current shell:
eval "$(rea completions bash)"
```
For zsh, initialize completion before loading the hook. If your shell already
runs `compinit`, place the hook after that existing initialization:
```zsh
autoload -Uz compinit
compinit
eval "$(rea completions zsh)"
```
The Bash hook supports macOS system Bash 3.2. It uses optional completion
features when the shell provides them and falls back to portable registration.
Completion includes standard global options and the active command's options;
+17 -12
View File
@@ -68,7 +68,7 @@ import { registerCutterCommands } from "./cli/cutterCommands.js";
import { excludeDeclaredSensitiveArguments } from "./cli/explicitSensitiveArguments.js";
import { rejectSurplusCliArguments } from "./cli/strictPositionals.js";
import { presentCliArgumentErrors } from "./cli/argumentErrors.js";
import { createCliCompletions } from "./cli/completions.js";
import { completionHelpNote, createCliCompletions } from "./cli/completions.js";
import { registerGuidedWorkflowCommands } from "./cli/guidedWorkflowCommands.js";
export { closeCliJavaScriptResources } from "./cli/javascriptApplicationAnalysis.js";
@@ -205,16 +205,21 @@ export const createCli = (
cli as unknown as Parameters<typeof createCliCompletions>[0],
environment,
);
cli.serve = (arguments_ = process.argv.slice(2), options) =>
complete(arguments_, options ?? {})
? Promise.resolve()
: serveStrict(
normalizeCliArguments(arguments_),
options ?? {},
(tokens, selected) =>
serveDeclared(tokens, selected, (declared, declaredOptions) =>
serve(adaptCliOutputArgumentsForIncur(declared), declaredOptions),
),
);
cli.serve = (arguments_ = process.argv.slice(2), options) => {
if (complete(arguments_, options ?? {})) return Promise.resolve();
const normalized = normalizeCliArguments(arguments_);
const result = serveStrict(normalized, options ?? {}, (tokens, selected) =>
serveDeclared(tokens, selected, (declared, declaredOptions) =>
serve(adaptCliOutputArgumentsForIncur(declared), declaredOptions),
),
);
const note = completionHelpNote(normalized);
if (note === undefined) return result;
return result.then(() => {
const write =
options?.stdout ?? ((text: string) => process.stdout.write(text));
write(`\n${note}`);
});
};
return cli;
};
+47 -16
View File
@@ -9,19 +9,53 @@ import {
normalizeCliArguments,
splitIncurBuiltinArguments,
} from "../cliBuiltinArguments.js";
import {
CLI_DOCUMENT_OUTPUT_FORMATS,
CLI_OUTPUT_FORMATS,
parseCliOutputArguments,
} from "../cliOutput.js";
import { CLI_OUTPUT_FORMATS, parseCliOutputArguments } from "../cliOutput.js";
import { bashCompletionScript } from "./bashCompletionRegistration.js";
import { MCP_DOCTOR_ALIAS, MCP_DOCTOR_OPTIONS } from "../mcpDoctorOptions.js";
type Commands = Parameters<typeof Completions.complete>[0];
type CompletionCli = Parameters<typeof Cli.toCommands.get>[0];
type ServeOptions = NonNullable<Parameters<CompletionCli["serve"]>[1]>;
const ROOT_FLAGS = new Set(["--mcp", "--update", "--version"]);
const standardGlobals = (root: boolean, doctor: boolean): z.ZodObject =>
/** Supplement Incur's built-in setup instructions without replacing its help. */
export const completionHelpNote = (
arguments_: readonly string[],
): string | undefined => {
const split = splitIncurBuiltinArguments(arguments_);
const words =
split.command[0] === "rea" ? split.command.slice(1) : split.command;
if (words[0] !== "completions") return undefined;
const rest = words.slice(1);
if (
rest.length > 1 ||
(rest.length === 1 &&
!CLI_COMPLETION_SHELLS.includes(
rest[0] as (typeof CLI_COMPLETION_SHELLS)[number],
))
)
return undefined;
const values = new Set(split.outputValues.map(({ index }) => index));
const flags = arguments_.filter((_, index) => !values.has(index));
if (
flags.some((word) =>
[
"--schema",
"--llms",
"--llms-full",
"--mcp",
"--update",
"--version",
].includes(word),
)
)
return undefined;
if (rest.length > 0 && !flags.includes("--help") && !flags.includes("-h"))
return undefined;
return "Zsh: initialize completion with `autoload -Uz compinit; compinit` before evaluating the zsh hook. If your shell already runs compinit, place the hook after it.\n";
};
const standardGlobals = (root: boolean): z.ZodObject =>
z.object(
Object.fromEntries([
...[...VALUED_OUTPUT_FLAGS].map(
@@ -29,11 +63,7 @@ const standardGlobals = (root: boolean, doctor: boolean): z.ZodObject =>
[
flag.slice(2),
flag === "--format"
? z
.enum(
doctor ? CLI_DOCUMENT_OUTPUT_FORMATS : CLI_OUTPUT_FORMATS,
)
.optional()
? z.enum(CLI_OUTPUT_FORMATS).optional()
: z.string().optional(),
] as const,
),
@@ -113,17 +143,18 @@ export const createCliCompletions = (
normalizeCliArguments(words.slice(1, index)),
).command;
const input = [cli.name, ...before, words[index] ?? ""];
const doctor = before[0] === "mcp" && before[1] === "doctor";
const candidates = Completions.complete(
completionCommands(cli),
undefined,
input,
input.length - 1,
{
schema: standardGlobals(
before.length === 0,
before[0] === "mcp" && before[1] === "doctor",
),
alias: { help: "h" },
schema: doctor
? // The launcher consumes this global before doctor parses options.
MCP_DOCTOR_OPTIONS.extend({ timings: z.boolean().optional() })
: standardGlobals(before.length === 0),
alias: doctor ? MCP_DOCTOR_ALIAS : { help: "h" },
},
);
const result = Completions.format(shell, candidates);
+2 -16
View File
@@ -1,6 +1,6 @@
import { Client, SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { Help, z } from "incur";
import { Help } from "incur";
import { performance } from "node:perf_hooks";
import { resolve } from "node:path";
import { StringDecoder } from "node:string_decoder";
@@ -30,25 +30,11 @@ import {
MCP_STARTUP_POLICY,
mcpDoctorDeadlineRecovery,
} from "./mcpStartupPolicy.js";
import { MCP_DOCTOR_ALIAS, MCP_DOCTOR_OPTIONS } from "./mcpDoctorOptions.js";
const OUTPUT_FORMATS = CLI_DOCUMENT_OUTPUT_FORMATS;
type OutputFormat = (typeof OUTPUT_FORMATS)[number];
/**
* Canonical `rea mcp doctor` options. This schema is the single source of
* truth: the argument parser accepts exactly these options (plus the `-h`
* alias below) and the help renderer lists them. Keep them in sync by
* changing this schema, not either consumer.
*/
const MCP_DOCTOR_OPTIONS = z.object({
format: z.enum(OUTPUT_FORMATS).optional().describe("Output format"),
json: z.boolean().optional().describe("Emit the report as JSON"),
fullOutput: z.boolean().optional().describe("Show the full output envelope"),
help: z.boolean().optional().describe("Show help"),
});
const MCP_DOCTOR_ALIAS = { help: "h" } as const;
const MCP_DOCTOR_COMMAND = "rea mcp doctor";
const MCP_DOCTOR_DESCRIPTION = "Validate MCP adapter startup and tool listing";
const MCP_DOCTOR_SCOPE =
+16
View File
@@ -0,0 +1,16 @@
import { z } from "incur";
import { CLI_DOCUMENT_OUTPUT_FORMATS } from "./cliOutput.js";
/** The options accepted by the production MCP doctor entry. */
export const MCP_DOCTOR_OPTIONS = z.object({
format: z
.enum(CLI_DOCUMENT_OUTPUT_FORMATS)
.optional()
.describe("Output format"),
json: z.boolean().optional().describe("Emit the report as JSON"),
fullOutput: z.boolean().optional().describe("Show the full output envelope"),
help: z.boolean().optional().describe("Show help"),
});
export const MCP_DOCTOR_ALIAS = { help: "h" } as const;
+75
View File
@@ -79,6 +79,15 @@ it("uses canonical global flag metadata and retains command options and enum hin
"md",
"jsonl",
]);
expect(complete(["rea", "mcp", "doctor", "--"])).toEqual([
"--format",
"--json",
"--full-output",
"--help",
"--timings",
]);
expect(complete(["rea", "mcp", "doctor", "--t"])).toEqual(["--timings"]);
expect(complete(["rea", "mcp", "doctor", "-h"])).toEqual(["-h"]);
expect(complete(["rea", "--filter-output", "mcp", ""])).toEqual([
"instructions",
"mcp",
@@ -116,6 +125,65 @@ it("shares Bash registration across ordinary and protocol routes while retaining
expect(ordinary(["completions", "bash", "--schema"], {})).toBe(false);
});
workspaceCliTest(
"explains zsh completion initialization in public help",
async ({ processes, workspace }) => {
const environment = {
...process.env,
...workspace.environment,
REA_LOG_LEVEL: "silent",
REA_TEST_NODE: process.execPath,
REA_TEST_DISPATCHER: resolve("scripts/rea.mjs"),
};
for (const arguments_ of [
["completions", "--help"],
["--json", "completions", "--help"],
["completions"],
]) {
const help = await processes.run(
process.execPath,
[resolve("scripts/rea.mjs"), ...arguments_],
{ env: environment },
);
expect(help.exitCode, help.stderr).toBe(0);
expect(help.stdout).toContain(
"initialize completion with `autoload -Uz compinit; compinit` before evaluating the zsh hook",
);
}
},
);
workspaceCliTest.skipIf(process.platform !== "darwin")(
"registers zsh completion after compinit",
async ({ processes, workspace }) => {
const environment = {
...process.env,
...workspace.environment,
REA_LOG_LEVEL: "silent",
REA_TEST_NODE: process.execPath,
REA_TEST_DISPATCHER: resolve("scripts/rea.mjs"),
};
const result = await processes.run(
"zsh",
[
"-f",
"-c",
`
rea() { "$REA_TEST_NODE" "$REA_TEST_DISPATCHER" "$@"; }
autoload -Uz compinit
compinit -i -D
eval "$(rea completions zsh)" || exit
print -r -- "association=\${+_comps[rea]}"
`,
],
{ env: environment },
);
expect(result.exitCode, result.stderr).toBe(0);
expect(result.stderr).toBe("");
expect(result.stdout).toContain("association=1\n");
},
);
workspaceCliTest.skipIf(process.platform === "win32")(
"runs generated Bash completion with valid MCP scope and incomplete global values through the public launcher",
async ({ processes, workspace }) => {
@@ -136,6 +204,10 @@ COMP_WORDS=(rea instructions --format "")
COMP_CWORD=3
_incur_complete_rea || exit
printf 'format=%s\\n' "\${COMPREPLY[*]}"
COMP_WORDS=(rea mcp doctor --)
COMP_CWORD=3
_incur_complete_rea || exit
printf 'doctor=%s\\n' "\${COMPREPLY[*]}"
`,
],
{
@@ -152,5 +224,8 @@ printf 'format=%s\\n' "\${COMPREPLY[*]}"
expect(result.stderr).toBe("");
expect(result.stdout).toContain("mcp=add doctor\n");
expect(result.stdout).toContain("format=toon json yaml md jsonl csv tsv\n");
expect(result.stdout).toContain(
"doctor=--format --json --full-output --help --timings\n",
);
},
);