feat(coding-agent,tui): support argument-hint frontmatter in prompt templates (#2780)

* feat(coding-agent,tui): support argument-hint frontmatter in prompt templates

Parse argument-hint from prompt template frontmatter and display it
in the autocomplete dropdown description, matching Claude Code's
convention for custom commands.

Frontmatter format:
  ---
  description: Code review
  argument-hint: "[file | #PR | PR-URL]"
  ---

The hint renders in the description column of the autocomplete list:
  review   [file | #PR | PR-URL] — Code review

Closes #2761

* docs(coding-agent,tui): add argument-hint documentation, tests, and built-in hints

- Document argument-hint frontmatter in prompt-templates.md with <required>/[optional] convention
- Add argument-hint to built-in prompts: pr, is, wr
- Expand tests: required/optional hints, missing hints, empty hints, special characters
- Add changelog entries for coding-agent and tui
This commit is contained in:
Andrés Vinueza
2026-04-16 16:02:35 -05:00
committed by GitHub
parent f84c4c89f5
commit aa25726ebf
10 changed files with 170 additions and 7 deletions

View File

@@ -1,5 +1,6 @@
---
description: Analyze GitHub issues (bugs or feature requests)
argument-hint: "<issue>"
---
Analyze GitHub issue(s): $ARGUMENTS

View File

@@ -1,5 +1,6 @@
---
description: Review PRs from URLs with structured issue and code analysis
argument-hint: "<PR-URL>"
---
You are given one or more GitHub PR URLs: $@

View File

@@ -1,5 +1,6 @@
---
description: Finish the current task end-to-end with changelog, commit, and push
argument-hint: "[instructions]"
---
Wrap it.

View File

@@ -270,6 +270,8 @@ await runtime.fork("entry-id");
- Added label timestamps to the session tree with a `Shift+T` toggle in `/tree`, smart date formatting, and timestamp preservation through branching ([#2691](https://github.com/badlogic/pi-mono/pull/2691) by [@w-winter](https://github.com/w-winter))
- Added `argument-hint` frontmatter field for prompt templates, displayed before the description in the autocomplete dropdown ([#2761](https://github.com/badlogic/pi-mono/issues/2761))
### Fixed
- Fixed startup resource loading to reuse the initial `ResourceLoader` for the first runtime, so extensions are not loaded twice before session startup and `session_start` handlers still fire for singleton-style extensions ([#2766](https://github.com/badlogic/pi-mono/issues/2766))

View File

@@ -30,6 +30,27 @@ Review the staged changes (`git diff --cached`). Focus on:
- The filename becomes the command name. `review.md` becomes `/review`.
- `description` is optional. If missing, the first non-empty line is used.
- `argument-hint` is optional. When set, the hint is displayed before the description in the autocomplete dropdown.
### Argument Hints
Use `argument-hint` in frontmatter to show expected arguments in autocomplete. Use `<angle brackets>` for required arguments and `[square brackets]` for optional ones:
```markdown
---
description: Review PRs from URLs with structured issue and code analysis
argument-hint: "<PR-URL>"
---
```
This renders in the autocomplete dropdown as:
```
→ pr <PR-URL> — Review PRs from URLs with structured issue and code analysis
is <issue> — Analyze GitHub issues (bugs or feature requests)
wr [instructions] — Finish the current task end-to-end
cl — Audit changelog entries before release
```
## Usage

View File

@@ -11,6 +11,7 @@ import { createSyntheticSourceInfo, type SourceInfo } from "./source-info.js";
export interface PromptTemplate {
name: string;
description: string;
argumentHint?: string;
content: string;
sourceInfo: SourceInfo;
filePath: string; // Absolute path to the template file
@@ -121,6 +122,7 @@ function loadTemplateFromFile(filePath: string, sourceInfo: SourceInfo): PromptT
return {
name,
description,
...(frontmatter["argument-hint"] && { argumentHint: frontmatter["argument-hint"] }),
content: body,
sourceInfo,
filePath,

View File

@@ -432,6 +432,7 @@ export class InteractiveMode {
const templateCommands: SlashCommand[] = this.session.promptTemplates.map((cmd) => ({
name: cmd.name,
description: this.prefixAutocompleteDescription(cmd.description, cmd.sourceInfo),
...(cmd.argumentHint && { argumentHint: cmd.argumentHint }),
}));
// Convert extension commands to SlashCommand format

View File

@@ -8,8 +8,11 @@
* - Edge cases and integration between parsing and substitution
*/
import { describe, expect, test } from "vitest";
import { parseCommandArgs, substituteArgs } from "../src/core/prompt-templates.js";
import { mkdirSync, rmSync, writeFileSync } from "fs";
import { tmpdir } from "os";
import { join } from "path";
import { afterAll, describe, expect, test } from "vitest";
import { loadPromptTemplates, parseCommandArgs, substituteArgs } from "../src/core/prompt-templates.js";
// ============================================================================
// substituteArgs
@@ -379,3 +382,123 @@ describe("parseCommandArgs + substituteArgs integration", () => {
expect(substituteArgs(template1, args)).toBe(substituteArgs(template2, args));
});
});
// ============================================================================
// loadPromptTemplates - argument-hint frontmatter
// ============================================================================
describe("loadPromptTemplates - argument-hint", () => {
const testDir = join(tmpdir(), `pi-test-prompts-${Date.now()}`);
function writeTemplate(name: string, content: string) {
mkdirSync(testDir, { recursive: true });
writeFileSync(join(testDir, `${name}.md`), content);
}
test("should parse required argument-hint from frontmatter", () => {
writeTemplate(
"pr",
`---
description: Review PRs from URLs with structured issue and code analysis
argument-hint: "<PR-URL>"
---
You are given one or more GitHub PR URLs: $@`,
);
const templates = loadPromptTemplates({
promptPaths: [testDir],
includeDefaults: false,
});
const pr = templates.find((t) => t.name === "pr");
expect(pr).toBeDefined();
expect(pr!.argumentHint).toBe("<PR-URL>");
expect(pr!.description).toBe("Review PRs from URLs with structured issue and code analysis");
});
test("should parse optional argument-hint from frontmatter", () => {
writeTemplate(
"wr",
`---
description: Finish the current task end-to-end with changelog, commit, and push
argument-hint: "[instructions]"
---
Wrap it. Additional instructions: $ARGUMENTS`,
);
const templates = loadPromptTemplates({
promptPaths: [testDir],
includeDefaults: false,
});
const wr = templates.find((t) => t.name === "wr");
expect(wr).toBeDefined();
expect(wr!.argumentHint).toBe("[instructions]");
expect(wr!.description).toBe("Finish the current task end-to-end with changelog, commit, and push");
});
test("should leave argumentHint undefined when not specified", () => {
writeTemplate(
"cl",
`---
description: Audit changelog entries before release
---
Audit changelog entries for all commits since the last release.`,
);
const templates = loadPromptTemplates({
promptPaths: [testDir],
includeDefaults: false,
});
const cl = templates.find((t) => t.name === "cl");
expect(cl).toBeDefined();
expect(cl!.argumentHint).toBeUndefined();
});
test("should ignore empty argument-hint", () => {
writeTemplate(
"empty-hint",
`---
description: A command with empty hint
argument-hint: ""
---
Do something`,
);
const templates = loadPromptTemplates({
promptPaths: [testDir],
includeDefaults: false,
});
const tmpl = templates.find((t) => t.name === "empty-hint");
expect(tmpl).toBeDefined();
expect(tmpl!.argumentHint).toBeUndefined();
});
test("should preserve argument-hint with special characters", () => {
writeTemplate(
"is",
`---
description: Analyze GitHub issues (bugs or feature requests)
argument-hint: "<issue>"
---
Analyze GitHub issue(s): $ARGUMENTS`,
);
const templates = loadPromptTemplates({
promptPaths: [testDir],
includeDefaults: false,
});
const is = templates.find((t) => t.name === "is");
expect(is).toBeDefined();
expect(is!.argumentHint).toBe("<issue>");
});
afterAll(() => {
try {
rmSync(testDir, { recursive: true, force: true });
} catch {}
});
});

View File

@@ -62,6 +62,10 @@
- Fixed slash-command argument autocomplete to await async `getArgumentCompletions()` results and ignore invalid return values, preventing crashes when extension commands provide asynchronous completions ([#2719](https://github.com/badlogic/pi-mono/issues/2719))
- Fixed non-capturing overlay padding from inflating scrollback and corrupting the viewport on terminal widen ([#2758](https://github.com/badlogic/pi-mono/pull/2758) by [@dotBeeps](https://github.com/dotBeeps))
### Added
- Added `argumentHint` to `SlashCommand` interface, displayed before the description in the autocomplete dropdown ([#2761](https://github.com/badlogic/pi-mono/issues/2761))
## [0.64.0] - 2026-03-29
### Fixed

View File

@@ -223,6 +223,7 @@ type Awaitable<T> = T | Promise<T>;
export interface SlashCommand {
name: string;
description?: string;
argumentHint?: string;
// Function to get argument completions for this command
// Returns null if no argument completion is available
getArgumentCompletions?(argumentPrefix: string): Awaitable<AutocompleteItem[] | null>;
@@ -303,11 +304,17 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
if (spaceIndex === -1) {
const prefix = textBeforeCursor.slice(1);
const commandItems = this.commands.map((cmd) => ({
name: "name" in cmd ? cmd.name : cmd.value,
label: "name" in cmd ? cmd.name : cmd.label,
description: cmd.description,
}));
const commandItems = this.commands.map((cmd) => {
const name = "name" in cmd ? cmd.name : cmd.value;
const hint = "argumentHint" in cmd && cmd.argumentHint ? cmd.argumentHint : undefined;
const desc = cmd.description ?? "";
const fullDesc = hint ? (desc ? `${hint}${desc}` : hint) : desc;
return {
name,
label: name,
description: fullDesc || undefined,
};
});
const filtered = fuzzyFilter(commandItems, prefix, (item) => item.name).map((item) => ({
value: item.name,