refactor: tier sf slash commands
This commit is contained in:
parent
587b5fa31c
commit
ab1a1edcf9
8 changed files with 252 additions and 154 deletions
|
|
@ -1161,6 +1161,7 @@ export type MessageRenderer<T = unknown> = (
|
|||
export interface RegisteredCommand {
|
||||
name: string;
|
||||
description?: string;
|
||||
menuTier?: "primary" | "secondary" | "internal";
|
||||
getArgumentCompletions?: (
|
||||
argumentPrefix: string,
|
||||
) => AutocompleteItem[] | null;
|
||||
|
|
|
|||
|
|
@ -118,6 +118,8 @@ import { UserMessageComponent } from "./components/user-message.js";
|
|||
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
||||
import { handleAgentEvent } from "./controllers/chat-controller.js";
|
||||
import { createExtensionUIContext as buildExtensionUIContext } from "./controllers/extension-ui-controller.js";
|
||||
import { setupEditorSubmitHandler as setupEditorSubmitHandlerController } from "./controllers/input-controller.js";
|
||||
import { updateAvailableProviderCount as updateAvailableProviderCountController } from "./controllers/model-controller.js";
|
||||
import {
|
||||
buildScopeGroups,
|
||||
formatDiagnostics,
|
||||
|
|
@ -125,8 +127,6 @@ import {
|
|||
formatScopeGroups,
|
||||
getShortPath,
|
||||
} from "./resource-display.js";
|
||||
import { setupEditorSubmitHandler as setupEditorSubmitHandlerController } from "./controllers/input-controller.js";
|
||||
import { updateAvailableProviderCount as updateAvailableProviderCountController } from "./controllers/model-controller.js";
|
||||
import {
|
||||
getAppKeyDisplay,
|
||||
type SlashCommandContext,
|
||||
|
|
@ -603,6 +603,7 @@ export class InteractiveMode {
|
|||
).map((cmd) => ({
|
||||
name: cmd.name,
|
||||
description: cmd.description ?? "(extension command)",
|
||||
menuTier: cmd.menuTier,
|
||||
getArgumentCompletions: cmd.getArgumentCompletions,
|
||||
}));
|
||||
|
||||
|
|
@ -1110,10 +1111,7 @@ export class InteractiveMode {
|
|||
);
|
||||
|
||||
if (collisionDiags.length > 0) {
|
||||
const collisionLines = formatDiagnostics(
|
||||
collisionDiags,
|
||||
metadata,
|
||||
);
|
||||
const collisionLines = formatDiagnostics(collisionDiags, metadata);
|
||||
this.chatContainer.addChild(
|
||||
new Text(
|
||||
`${theme.fg("warning", "[Skill conflicts]")}\n${collisionLines}`,
|
||||
|
|
@ -1139,10 +1137,7 @@ export class InteractiveMode {
|
|||
|
||||
const promptDiagnostics = promptsResult.diagnostics;
|
||||
if (promptDiagnostics.length > 0) {
|
||||
const warningLines = formatDiagnostics(
|
||||
promptDiagnostics,
|
||||
metadata,
|
||||
);
|
||||
const warningLines = formatDiagnostics(promptDiagnostics, metadata);
|
||||
this.chatContainer.addChild(
|
||||
new Text(
|
||||
`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`,
|
||||
|
|
@ -1175,10 +1170,7 @@ export class InteractiveMode {
|
|||
extensionDiagnostics.push(...shortcutDiagnostics);
|
||||
|
||||
if (extensionDiagnostics.length > 0) {
|
||||
const warningLines = formatDiagnostics(
|
||||
extensionDiagnostics,
|
||||
metadata,
|
||||
);
|
||||
const warningLines = formatDiagnostics(extensionDiagnostics, metadata);
|
||||
this.chatContainer.addChild(
|
||||
new Text(
|
||||
`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,31 @@ describe("CombinedAutocompleteProvider — slash commands", () => {
|
|||
assert.equal(result!.prefix, "/");
|
||||
});
|
||||
|
||||
it("returns only primary commands for bare / when tiers are configured", () => {
|
||||
const provider = makeProvider([
|
||||
{ name: "help", description: "Help", menuTier: "primary" },
|
||||
{ name: "next", description: "Step", menuTier: "primary" },
|
||||
{ name: "queue", description: "Queue", menuTier: "secondary" },
|
||||
]);
|
||||
const result = provider.getSuggestions(["/"], 0, 1);
|
||||
assert.ok(result);
|
||||
assert.deepEqual(result!.items.map((item) => item.value).sort(), [
|
||||
"help",
|
||||
"next",
|
||||
]);
|
||||
});
|
||||
|
||||
it("searches secondary commands after a prefix is typed", () => {
|
||||
const provider = makeProvider([
|
||||
{ name: "quick", description: "Quick", menuTier: "primary" },
|
||||
{ name: "queue", description: "Queue", menuTier: "secondary" },
|
||||
]);
|
||||
const result = provider.getSuggestions(["/q"], 0, 2);
|
||||
assert.ok(result);
|
||||
assert.ok(result!.items.some((item) => item.value === "quick"));
|
||||
assert.ok(result!.items.some((item) => item.value === "queue"));
|
||||
});
|
||||
|
||||
it("filters commands by typed prefix", () => {
|
||||
const provider = makeProvider(sampleCommands);
|
||||
const result = provider.getSuggestions(["/se"], 0, 3);
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ export interface AutocompleteItem {
|
|||
export interface SlashCommand {
|
||||
name: string;
|
||||
description?: string;
|
||||
menuTier?: "primary" | "secondary" | "internal";
|
||||
// Function to get argument completions for this command
|
||||
// Returns null if no argument completion is available
|
||||
getArgumentCompletions?(argumentPrefix: string): AutocompleteItem[] | null;
|
||||
|
|
@ -207,22 +208,30 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|||
name: "name" in cmd ? cmd.name : cmd.value,
|
||||
label: "name" in cmd ? cmd.name : cmd.label,
|
||||
description: cmd.description,
|
||||
menuTier: "menuTier" in cmd ? cmd.menuTier : undefined,
|
||||
}));
|
||||
|
||||
const filtered = fuzzyFilter(
|
||||
commandItems,
|
||||
prefix,
|
||||
(item) => item.name,
|
||||
).map((item) => ({
|
||||
let filtered = fuzzyFilter(commandItems, prefix, (item) => item.name);
|
||||
|
||||
if (prefix.length === 0) {
|
||||
const primary = filtered.filter(
|
||||
(item) => item.menuTier === "primary",
|
||||
);
|
||||
if (primary.length > 0) {
|
||||
filtered = primary;
|
||||
}
|
||||
}
|
||||
|
||||
const items = filtered.map((item) => ({
|
||||
value: item.name,
|
||||
label: item.label,
|
||||
...(item.description && { description: item.description }),
|
||||
}));
|
||||
|
||||
if (filtered.length === 0) return null;
|
||||
if (items.length === 0) return null;
|
||||
|
||||
return {
|
||||
items: filtered,
|
||||
items,
|
||||
prefix: `/${prefix}`,
|
||||
};
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -318,15 +318,14 @@ export const TOP_LEVEL_SUBCOMMANDS = [
|
|||
];
|
||||
|
||||
// Lean product surface (UOK control-plane convo 2026-05-14): operators
|
||||
// pick modes, not personas, and SF runs implementation machinery (triage,
|
||||
// agent picks, mode/work-mode shifts, orchestration internals) on its own.
|
||||
// Commands not in this set stay callable for scripting/debug but don't
|
||||
// show up in the slash catalog or help.
|
||||
// pick workflows, not personas, and SF runs implementation machinery (agent
|
||||
// picks, mode/work-mode shifts, orchestration internals) on its own. Commands
|
||||
// not in this set stay callable for scripting/debug but don't show up in the
|
||||
// slash catalog or help.
|
||||
//
|
||||
// Hidden by category:
|
||||
// - /triage, /agent, /parallel, /cmux, /sidekicks — internal machinery
|
||||
// - /agent, /parallel, /cmux, /sidekicks — internal orchestration machinery
|
||||
// - /mode, /control, /permission-profile, /model-mode — Shift+Tab + advanced axes
|
||||
// - /repair — auto-detected work-mode shift, fold into /autonomous
|
||||
// - /hooks, /run-hook, /skill-health, /inspect, /recover — diagnostics
|
||||
// - /mcp, /extensions, /configure-agent, /experimental — platform plumbing
|
||||
// - /delegate, /pr-branch, /add-tests — lower-level conveniences reached
|
||||
|
|
@ -370,6 +369,28 @@ export const PUBLIC_DIRECT_COMMANDS = new Set([
|
|||
"workflow",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Primary command tier — the 5 things an operator should memorize. Four
|
||||
* product modes plus /help. Everything else is reachable via autocomplete
|
||||
* (PUBLIC_DIRECT_COMMANDS) or operator-driven discovery (`/help all`).
|
||||
*
|
||||
* Rationale (2026-05-14 product-surface convo): a 34-command menu defeats
|
||||
* the product-mode framing. Operators interact with modes (`/next`,
|
||||
* `/discuss`, `/autonomous`) plus the non-workflow lane (`/quick`); SF
|
||||
* surfaces relevant secondary commands inline when context warrants.
|
||||
*/
|
||||
export const PRIMARY_COMMANDS = new Set([
|
||||
"next",
|
||||
"autonomous",
|
||||
"discuss",
|
||||
"quick",
|
||||
"help",
|
||||
]);
|
||||
|
||||
export const PRIMARY_TOP_LEVEL_SUBCOMMANDS = TOP_LEVEL_SUBCOMMANDS.filter(
|
||||
(command) => PRIMARY_COMMANDS.has(command.cmd),
|
||||
);
|
||||
|
||||
export const PUBLIC_TOP_LEVEL_SUBCOMMANDS = TOP_LEVEL_SUBCOMMANDS.filter(
|
||||
(command) => PUBLIC_DIRECT_COMMANDS.has(command.cmd),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -25,138 +25,156 @@ import {
|
|||
formatProgressLine,
|
||||
} from "../../progress-score.js";
|
||||
import { setSessionModelOverride } from "../../session-model-override.js";
|
||||
import { sfHome } from "../../sf-home.js";
|
||||
import { formattedShortcutPair } from "../../shortcut-defs.js";
|
||||
import { deriveState } from "../../state.js";
|
||||
import { writeUokDiagnostics } from "../../uok/diagnostic-synthesis.js";
|
||||
import { PRIMARY_COMMANDS, PUBLIC_TOP_LEVEL_SUBCOMMANDS } from "../catalog.js";
|
||||
import { projectRoot } from "../context.js";
|
||||
import { sfHome } from "../../sf-home.js";
|
||||
|
||||
const HELP_CATEGORY_ORDER = [
|
||||
{
|
||||
title: "PRIMARY",
|
||||
commands: ["next", "autonomous", "discuss", "quick", "help"],
|
||||
},
|
||||
{
|
||||
title: "WORKFLOW",
|
||||
commands: [
|
||||
"start",
|
||||
"templates",
|
||||
"workflow",
|
||||
"ship",
|
||||
"backlog",
|
||||
"schedule",
|
||||
"triage",
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "STATUS",
|
||||
commands: ["status", "queue", "visualize", "history", "logs", "forensics"],
|
||||
},
|
||||
{
|
||||
title: "CONTROL",
|
||||
commands: ["pause", "stop", "skip", "undo", "park", "unpark", "capture"],
|
||||
},
|
||||
{
|
||||
title: "SETUP",
|
||||
commands: [
|
||||
"init",
|
||||
"setup",
|
||||
"doctor",
|
||||
"repair",
|
||||
"remote",
|
||||
"knowledge",
|
||||
"config",
|
||||
"keys",
|
||||
"model",
|
||||
"prefs",
|
||||
"update",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function commandByName() {
|
||||
return new Map(
|
||||
PUBLIC_TOP_LEVEL_SUBCOMMANDS.map((command) => [command.cmd, command]),
|
||||
);
|
||||
}
|
||||
|
||||
function commandLine(command) {
|
||||
return ` /${command.cmd.padEnd(12)} ${command.desc ?? ""}`.trimEnd();
|
||||
}
|
||||
|
||||
function groupedPublicHelpLines() {
|
||||
const byName = commandByName();
|
||||
const emitted = new Set();
|
||||
const lines = ["SF — Singularity Forge workflow runtime\n"];
|
||||
for (const category of HELP_CATEGORY_ORDER) {
|
||||
const categoryLines = category.commands
|
||||
.map((name) => byName.get(name))
|
||||
.filter(Boolean)
|
||||
.map((command) => {
|
||||
emitted.add(command.cmd);
|
||||
return commandLine(command);
|
||||
});
|
||||
if (categoryLines.length === 0) continue;
|
||||
lines.push(category.title, ...categoryLines, "");
|
||||
}
|
||||
const other = PUBLIC_TOP_LEVEL_SUBCOMMANDS.filter(
|
||||
(command) => !emitted.has(command.cmd),
|
||||
);
|
||||
if (other.length > 0) {
|
||||
lines.push("OTHER", ...other.map(commandLine), "");
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function keywordHelpLines(keyword) {
|
||||
const needle = keyword.toLowerCase();
|
||||
const tokens = needle
|
||||
.split(/[^a-z0-9-]+/i)
|
||||
.map((token) => token.trim())
|
||||
.filter(
|
||||
(token) =>
|
||||
token.length > 2 &&
|
||||
!new Set([
|
||||
"the",
|
||||
"and",
|
||||
"for",
|
||||
"with",
|
||||
"want",
|
||||
"show",
|
||||
"see",
|
||||
"my",
|
||||
"how",
|
||||
"what",
|
||||
"where",
|
||||
]).has(token),
|
||||
);
|
||||
const matches = PUBLIC_TOP_LEVEL_SUBCOMMANDS.filter(
|
||||
(command) =>
|
||||
command.cmd.toLowerCase().includes(needle) ||
|
||||
(command.desc ?? "").toLowerCase().includes(needle) ||
|
||||
tokens.some(
|
||||
(token) =>
|
||||
command.cmd.toLowerCase().includes(token) ||
|
||||
(command.desc ?? "").toLowerCase().includes(token),
|
||||
),
|
||||
);
|
||||
if (matches.length === 0) {
|
||||
return [
|
||||
`No public SF command matched "${keyword}".`,
|
||||
"Try /help all, or describe the work in /discuss.",
|
||||
];
|
||||
}
|
||||
return [`SF command matches for "${keyword}"\n`, ...matches.map(commandLine)];
|
||||
}
|
||||
|
||||
export function showHelp(ctx, args = "") {
|
||||
const summaryLines = [
|
||||
"SF — Singularity Forge\n",
|
||||
"QUICK START",
|
||||
" /start <tpl> Start a workflow template",
|
||||
" /next Run one assisted unit",
|
||||
" /autonomous Run all queued product units continuously",
|
||||
" /pause Pause autonomous mode",
|
||||
" /autonomous stop Stop autonomous mode gracefully",
|
||||
"",
|
||||
"VISIBILITY",
|
||||
` /status Dashboard (${formattedShortcutPair("dashboard")})`,
|
||||
` /parallel watch Parallel monitor (${formattedShortcutPair("parallel")})`,
|
||||
` /notifications Notification history (${formattedShortcutPair("notifications")})`,
|
||||
" /tasks Background work surface — units, workers, budget",
|
||||
" /visualize Interactive 10-tab TUI",
|
||||
" /queue Show queued/dispatched units",
|
||||
" /research Force research stage",
|
||||
" /plan Force planning stage",
|
||||
" /implement Force implementation stage",
|
||||
"",
|
||||
"COURSE CORRECTION",
|
||||
" /steer <desc> Apply user override to active work",
|
||||
" /steer mode <m> [scope] Change work mode (now|after-current-unit|next-milestone)",
|
||||
" /steer permission-profile <p> [scope] Change permission profile",
|
||||
" /steer model-mode <m> Change model mode for next unit",
|
||||
" /capture <text> Quick-capture a thought to CAPTURES.md",
|
||||
" /triage Classify and route pending captures",
|
||||
" /undo Revert last completed unit [--force]",
|
||||
" /rethink Conversational project reorganization",
|
||||
"",
|
||||
"SETUP",
|
||||
" /init Project init wizard",
|
||||
" /setup Global setup status [llm|search|remote|keys|prefs]",
|
||||
" /reload Snapshot and reload agent with fresh extension code",
|
||||
" /model Switch active session model",
|
||||
" /prefs Manage preferences",
|
||||
" /doctor Diagnose and repair .sf/ state",
|
||||
" /repair Switch to repair work mode and run diagnostics",
|
||||
" /tasks Background work surface",
|
||||
" /skills List discovered skills [reload|--eval <name>|--auto-create]",
|
||||
" /cost Show cost summary [--session|--all|--prometheus]",
|
||||
"",
|
||||
"Use /help all for the complete command reference.",
|
||||
];
|
||||
const allLines = [
|
||||
"SF — Singularity Forge\n",
|
||||
"WORKFLOW",
|
||||
" /start <tpl> Start a workflow template (bugfix, spike, feature, hotfix, etc.)",
|
||||
" /templates List available workflow templates [info <name>]",
|
||||
" /next Run one assisted unit",
|
||||
" /next Assisted mode: execute next task, then pause [--dry-run] [--verbose]",
|
||||
" /autonomous Run all queued product units continuously [--verbose]",
|
||||
" /autonomous stop Stop autonomous mode gracefully",
|
||||
" /pause Pause autonomous mode (preserves state, /autonomous to resume)",
|
||||
" /discuss Start guided milestone/slice discussion",
|
||||
" /new-milestone Create milestone from headless context (used by sf headless)",
|
||||
"",
|
||||
"VISIBILITY",
|
||||
` /status Show progress dashboard (${formattedShortcutPair("dashboard")})`,
|
||||
` /parallel watch Open parallel worker monitor (${formattedShortcutPair("parallel")})`,
|
||||
" /visualize Interactive 10-tab TUI (progress, timeline, deps, metrics, health, agent, changes, knowledge, captures, export)",
|
||||
" /queue Show queued/dispatched units and execution order",
|
||||
" /tasks Background work surface — units, workers, budget, checkpoints",
|
||||
" /research Force research stage for current unit",
|
||||
" /plan Force planning stage for current unit",
|
||||
" /implement Force implementation stage for current unit",
|
||||
" /history View execution history [--cost] [--phase] [--model] [N]",
|
||||
" /trajectory View execution trajectory — step-by-step trace with costs and errors",
|
||||
" /changelog Show categorized release notes [version]",
|
||||
` /notifications View persistent notification history [clear|tail|filter] (${formattedShortcutPair("notifications")})`,
|
||||
"",
|
||||
"COURSE CORRECTION",
|
||||
" /steer <desc> Apply user override to active work",
|
||||
" /capture <text> Quick-capture a thought to CAPTURES.md",
|
||||
" /triage Classify and route pending captures",
|
||||
" /skip <unit> Prevent a unit from autonomous mode dispatch",
|
||||
" /undo Revert last completed unit [--force]",
|
||||
" /rethink Conversational project reorganization — reorder, park, discard, add milestones",
|
||||
" /park [id] Park a milestone — skip without deleting [reason]",
|
||||
" /unpark [id] Reactivate a parked milestone",
|
||||
"",
|
||||
"PROJECT KNOWLEDGE",
|
||||
" /knowledge <type> <text> Add rule, pattern, or lesson to KNOWLEDGE.md",
|
||||
" /codebase [generate|update|stats|indexer] Manage CODEBASE.md and Sift code search",
|
||||
"",
|
||||
"SCHEDULE",
|
||||
" /schedule add --in <dur> <title> Schedule a follow-up item",
|
||||
" /schedule list Show pending scheduled items",
|
||||
" /schedule done <id> Mark an item complete",
|
||||
"",
|
||||
"SETUP & CONFIGURATION",
|
||||
" /init Project init wizard — detect, configure, bootstrap .sf/",
|
||||
" /setup Global setup status [llm|search|remote|keys|prefs]",
|
||||
" /model Switch active session model [provider/model|model-id]",
|
||||
" /mode Switch work mode (chat/plan/build/review/repair/research)",
|
||||
" /control Switch run control (manual/assisted/autonomous)",
|
||||
" /permission-profile Switch permission profile (restricted/normal/trusted/unrestricted)",
|
||||
" /model-mode Switch model mode (fast/smart/deep)",
|
||||
" /prefs Manage preferences [global|project|status|wizard|setup|import-claude]",
|
||||
" /cmux Manage cmux integration [status|on|off|notifications|sidebar|splits|browser]",
|
||||
" /config Set API keys for external tools",
|
||||
" /keys API key manager [list|add|remove|test|rotate|doctor]",
|
||||
" /show-config Show effective configuration (models, routing, toggles)",
|
||||
" /hooks Show post-unit hook configuration",
|
||||
" /extensions Manage extensions [list|enable|disable|info]",
|
||||
" /fast Toggle OpenAI service tier [on|off|flex|status]",
|
||||
" /mcp External MCP server status [status|check <server>|reload]",
|
||||
"",
|
||||
"MAINTENANCE",
|
||||
" /doctor Diagnose and repair .sf/ state [audit|fix|heal] [scope]",
|
||||
" /repair Switch to repair work mode and run diagnostics [--autonomous]",
|
||||
" /tasks Background work surface [--refresh|--failed|--cancelled|--all]",
|
||||
" /skills List discovered skills from .agents/skills/",
|
||||
" /skills reload Reload skills from disk — picks up new/updated skill files",
|
||||
" /skills --eval <name> Run eval cases for a skill",
|
||||
" /reload Snapshot & reload agent, resume same session",
|
||||
" /export Export milestone/slice results [--json|--markdown|--html] [--all]",
|
||||
" /cleanup Remove merged branches or snapshots [branches|snapshots]",
|
||||
" /worktree Manage worktrees from the TUI [list|merge|clean|remove]",
|
||||
" /migrate Migrate .planning/ (v1) to .sf/ (v2) format",
|
||||
" /remote Configure remote question delivery [slack|discord|status|disconnect]",
|
||||
" /inspect Show SQLite DB diagnostics (schema, row counts, recent entries)",
|
||||
" /update Update SF to the latest version via npm",
|
||||
];
|
||||
const showAll = args.trim().toLowerCase() === "all";
|
||||
ctx.ui.notify((showAll ? allLines : summaryLines).join("\n"), "info");
|
||||
const request = args.trim();
|
||||
if (request.length > 0 && request.toLowerCase() !== "all") {
|
||||
ctx.ui.notify(keywordHelpLines(request).join("\n"), "info");
|
||||
return;
|
||||
}
|
||||
if (request.toLowerCase() === "all") {
|
||||
ctx.ui.notify(groupedPublicHelpLines().join("\n"), "info");
|
||||
return;
|
||||
}
|
||||
const primary = PUBLIC_TOP_LEVEL_SUBCOMMANDS.filter((command) =>
|
||||
PRIMARY_COMMANDS.has(command.cmd),
|
||||
);
|
||||
ctx.ui.notify(
|
||||
[
|
||||
"SF — Singularity Forge workflow runtime\n",
|
||||
"PRIMARY",
|
||||
...primary.map(commandLine),
|
||||
"",
|
||||
`Shift+Tab cycles runtime mode. ${formattedShortcutPair("dashboard")} opens /status.`,
|
||||
"Type /<letters> to search secondary commands.",
|
||||
"Use /help all for the grouped public command reference.",
|
||||
].join("\n"),
|
||||
"info",
|
||||
);
|
||||
}
|
||||
export async function handleStatus(ctx) {
|
||||
const basePath = projectRoot();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { importExtensionModule } from "@singularity-forge/coding-agent";
|
|||
import {
|
||||
DIRECT_SF_COMMANDS,
|
||||
getSfTopLevelCommandCompletions,
|
||||
PRIMARY_COMMANDS,
|
||||
SF_COMMAND_DESCRIPTION,
|
||||
} from "./catalog.js";
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ export function registerSFCommands(pi) {
|
|||
for (const command of DIRECT_SF_COMMANDS) {
|
||||
pi.registerCommand(command.cmd, {
|
||||
description: command.desc || SF_COMMAND_DESCRIPTION,
|
||||
menuTier: PRIMARY_COMMANDS.has(command.cmd) ? "primary" : "secondary",
|
||||
getArgumentCompletions: (prefix) =>
|
||||
getSfTopLevelCommandCompletions(command.cmd, prefix),
|
||||
handler: async (args, ctx) => {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import {
|
|||
DIRECT_SF_COMMAND_NAMES,
|
||||
getSfArgumentCompletions,
|
||||
getSfTopLevelCommandCompletions,
|
||||
PRIMARY_COMMANDS,
|
||||
} from "../commands/catalog.js";
|
||||
import { showHelp } from "../commands/handlers/core.js";
|
||||
import { registerSFCommands } from "../commands/index.js";
|
||||
|
||||
test("direct SF command surface registers workflow verbs without legacy sf namespace", () => {
|
||||
|
|
@ -44,6 +46,34 @@ test("top_level_completions_keep_platform_owned_product_paths_visible", () => {
|
|||
assert.equal(labels.includes("permission-profile"), false);
|
||||
});
|
||||
|
||||
test("primary_command_tier_is_the_five_command_memory_model", () => {
|
||||
assert.deepEqual([...PRIMARY_COMMANDS].sort(), [
|
||||
"autonomous",
|
||||
"discuss",
|
||||
"help",
|
||||
"next",
|
||||
"quick",
|
||||
]);
|
||||
});
|
||||
|
||||
test("help_keyword_routes_natural_language_to_public_commands", () => {
|
||||
const messages = [];
|
||||
showHelp(
|
||||
{
|
||||
ui: {
|
||||
notify(message) {
|
||||
messages.push(message);
|
||||
},
|
||||
},
|
||||
},
|
||||
"I want to see my queue",
|
||||
);
|
||||
|
||||
assert.equal(messages.length, 1);
|
||||
assert.match(messages[0], /\/queue\b/);
|
||||
assert.doesNotMatch(messages[0], /\/parallel\b/);
|
||||
});
|
||||
|
||||
test("direct command completions strip the already typed command name", () => {
|
||||
assert.deepEqual(getSfTopLevelCommandCompletions("autonomous", "--"), [
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue