refactor: narrow sf slash surface

This commit is contained in:
Mikael Hugo 2026-05-14 20:04:53 +02:00
parent 5ce9df2e37
commit 587b5fa31c
5 changed files with 117 additions and 106 deletions

View file

@ -176,6 +176,22 @@ function parseTopLevelCatalogCommands() {
);
}
function parsePublicDirectCommands() {
const source = read(join(sfRoot, "commands", "catalog.js"));
const start = source.indexOf("export const PUBLIC_DIRECT_COMMANDS");
const end = source.indexOf("export const PUBLIC_TOP_LEVEL_SUBCOMMANDS");
if (start === -1 || end === -1 || end <= start) {
throw new Error(
"Could not locate PUBLIC_DIRECT_COMMANDS in commands/catalog.js",
);
}
return new Set(
[...source.slice(start, end).matchAll(/"([^"]+)"/g)].map(
(match) => match[1],
),
);
}
function parseHandledTopLevelCommands() {
const handlerFiles = [
"core.js",
@ -268,10 +284,13 @@ function main() {
const manifest = parseManifest();
const registeredTools = parseRegisteredTools();
const catalogCommands = parseTopLevelCatalogCommands();
const publicDirectCommands = parsePublicDirectCommands();
const directCommandNames = uniqueSorted(
BASE_DIRECT_COMMAND_NAMES.concat(
catalogCommands.filter(
(command) => !BASE_RUNTIME_COMMAND_NAMES.has(command),
(command) =>
publicDirectCommands.has(command) &&
!BASE_RUNTIME_COMMAND_NAMES.has(command),
),
parseDirectRegisteredCommands().filter(
(command) => !BASE_RUNTIME_COMMAND_NAMES.has(command),

View file

@ -19,55 +19,31 @@ const TOP_LEVEL_SUBCOMMANDS = [
{ cmd: "quick", desc: "Execute a quick task without full planning overhead" },
{ cmd: "discuss", desc: "Discuss architecture and decisions" },
{ cmd: "capture", desc: "Fire-and-forget thought capture" },
{ cmd: "changelog", desc: "Show categorized release notes" },
{ cmd: "triage", desc: "Manually trigger triage of pending captures" },
{ cmd: "dispatch", desc: "Dispatch a specific phase directly" },
{ cmd: "history", desc: "View execution history" },
{ cmd: "undo", desc: "Revert last completed unit" },
{ cmd: "skip", desc: "Prevent a unit from autonomous dispatch" },
{ cmd: "export", desc: "Export milestone or slice results" },
{ cmd: "cleanup", desc: "Remove merged branches or snapshots" },
{ cmd: "mode", desc: "Switch workflow mode (solo/team)" },
{ cmd: "park", desc: "Park a milestone" },
{ cmd: "unpark", desc: "Reactivate a parked milestone" },
{ cmd: "prefs", desc: "Manage preferences" },
{ cmd: "config", desc: "Set API keys for external tools" },
{ cmd: "keys", desc: "API key manager" },
{ cmd: "hooks", desc: "Show configured hooks" },
{ cmd: "run-hook", desc: "Manually trigger a specific hook" },
{ cmd: "skill-health", desc: "Skill lifecycle dashboard" },
{ cmd: "model", desc: "Switch the active session model or open a picker" },
{ cmd: "doctor", desc: "Runtime health checks with auto-fix" },
{ cmd: "uok", desc: "UOK runtime health, ledger status, and gate metrics" },
{ cmd: "repair", desc: "Repair runtime state and generated artifacts" },
{ cmd: "logs", desc: "Browse activity logs, debug logs, and metrics" },
{ cmd: "forensics", desc: "Examine execution logs" },
{ cmd: "init", desc: "Project init wizard" },
{ cmd: "setup", desc: "Global setup status and configuration" },
{ cmd: "migrate", desc: "Migrate a v1 .planning directory to .sf format" },
{ cmd: "remote", desc: "Configure remote question delivery" },
{ cmd: "steer", desc: "Hard-steer plan documents during execution" },
{ cmd: "inspect", desc: "Show SQLite DB diagnostics" },
{ cmd: "knowledge", desc: "Add persistent project knowledge" },
{
cmd: "new-milestone",
desc: "Create a milestone from a specification document",
},
{ cmd: "parallel", desc: "Parallel milestone orchestration" },
{ cmd: "park", desc: "Park a milestone" },
{ cmd: "unpark", desc: "Reactivate a parked milestone" },
{ cmd: "update", desc: "Update SF to the latest version" },
{ cmd: "start", desc: "Start a workflow template" },
{ cmd: "templates", desc: "List available workflow templates" },
{ cmd: "extensions", desc: "Manage extensions" },
{
cmd: "codebase",
desc: "Generate, refresh, and inspect the codebase map cache",
},
{
cmd: "solver-eval",
desc: "Compare raw agent loops against SF autonomous solver control",
},
{
cmd: "scaffold",
desc: "Inspect or refresh ADR-021 versioned scaffold docs",
},
{ cmd: "workflow", desc: "Inspect or continue workflow state" },
{ cmd: "ship", desc: "Prepare changes for review and delivery" },
{ cmd: "schedule", desc: "Manage time-bound reminders" },
{ cmd: "backlog", desc: "Inspect priority-ordered work" },
];
function filterStartsWith(partial, options, prefix = "") {
const normalizedPrefix = prefix.length > 0 ? `${prefix} ` : "";

View file

@ -11,7 +11,7 @@ import { resolveProjectRoot } from "../worktree.js";
* Comprehensive description of all available SF commands for help text.
*/
export const SF_COMMAND_DESCRIPTION =
"SF — Singularity Forge: /help|start|templates|next|autonomous|pause|status|widget|visualize|queue|quick|discuss|capture|triage|todo|dispatch|history|undo|undo-task|reset-slice|rate|skip|cleanup|mode|control|permission-profile|model-mode|show-config|prefs|config|keys|hooks|run-hook|skill-health|doctor|uok|logs|forensics|migrate|remote|steer|knowledge|harness|solver-eval|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp|rethink|codebase|notifications|ship|do|session-report|backlog|pr-branch|add-tests|scan|scaffold|extract-learnings|eval-review|plan|agent";
"SF — Singularity Forge workflow runtime: /next for assisted build, /discuss for plan/discovery, /autonomous for the full research-plan-execute loop.";
export const BASE_RUNTIME_COMMANDS = new Set([
"settings",
@ -117,7 +117,7 @@ export const TOP_LEVEL_SUBCOMMANDS = [
{ cmd: "model", desc: "Switch the active session model or open a picker" },
{
cmd: "mode",
desc: "Switch mode: ask · plan · build · yolo (full autonomy) — or Shift+Tab to cycle",
desc: "Internal mode compatibility; use /discuss, /next, or /autonomous for product workflows",
},
{
cmd: "control",
@ -317,8 +317,67 @@ 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.
//
// Hidden by category:
// - /triage, /agent, /parallel, /cmux, /sidekicks — internal 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
// through /ship, /next, or autonomous workflow
export const PUBLIC_DIRECT_COMMANDS = new Set([
"autonomous",
"backlog",
"capture",
"config",
"discuss",
"doctor",
"forensics",
"help",
"history",
"init",
"keys",
"knowledge",
"logs",
"model",
"next",
"park",
"pause",
"prefs",
"queue",
"quick",
"remote",
"repair",
"schedule",
"ship",
"skip",
"setup",
"start",
"status",
"stop",
"templates",
"triage",
"undo",
"unpark",
"update",
"visualize",
"workflow",
]);
export const PUBLIC_TOP_LEVEL_SUBCOMMANDS = TOP_LEVEL_SUBCOMMANDS.filter(
(command) => PUBLIC_DIRECT_COMMANDS.has(command.cmd),
);
export const DIRECT_SF_COMMANDS = TOP_LEVEL_SUBCOMMANDS.filter(
(command) => !BASE_RUNTIME_COMMANDS.has(command.cmd),
(command) =>
PUBLIC_DIRECT_COMMANDS.has(command.cmd) &&
!BASE_RUNTIME_COMMANDS.has(command.cmd),
);
export const DIRECT_SF_COMMAND_NAMES = DIRECT_SF_COMMANDS.map(
@ -653,7 +712,7 @@ export function getSfArgumentCompletions(prefix) {
parts.push("");
}
if (parts.length <= 1) {
return filterOptions(parts[0] ?? "", TOP_LEVEL_SUBCOMMANDS);
return filterOptions(parts[0] ?? "", PUBLIC_TOP_LEVEL_SUBCOMMANDS);
}
const [command, subcommand = "", third = ""] = parts;
if (command === "cmux") {

View file

@ -56,130 +56,65 @@
"chapter_close"
],
"commands": [
"add-tests",
"ask",
"audit",
"autonomous",
"backlog",
"capture",
"chronicle",
"cleanup",
"cmux",
"codebase",
"color",
"color-char",
"color-config",
"color-next",
"color-set",
"config",
"configure-agent",
"control",
"cost",
"create-extension",
"create-slash-command",
"debug",
"delegate",
"diff",
"discuss",
"dispatch",
"do",
"doctor",
"emoji",
"emoji-config",
"emoji-history",
"emoji-set",
"escalate",
"eval-review",
"experimental",
"extensions",
"extract-learnings",
"fast",
"find",
"forensics",
"guard-status",
"guard-toggle",
"harness",
"help",
"history",
"hooks",
"implement",
"init",
"inspect",
"instructions",
"keep-alive",
"keys",
"kill",
"knowledge",
"logs",
"mcp",
"migrate",
"mode",
"model-mode",
"new-milestone",
"next",
"notifications",
"notify-beep",
"notify-focus",
"notify-save-global",
"notify-say",
"notify-status",
"notify-threshold",
"parallel",
"park",
"pause",
"permission",
"permission-mode",
"permission-profile",
"plan",
"pr-branch",
"prefs",
"queue",
"quick",
"rate",
"recover",
"remote",
"rename",
"repair",
"research",
"reset-slice",
"rethink",
"rewind",
"run-hook",
"scaffold",
"scan",
"schedule",
"search",
"session-report",
"setup",
"ship",
"show-config",
"sidekicks",
"skill-health",
"skills",
"skip",
"solver-eval",
"start",
"status",
"statusline",
"steer",
"streamer-mode",
"subagent",
"tasks",
"templates",
"theme",
"todo",
"trajectory",
"triage",
"undo",
"undo-task",
"unpark",
"uok",
"update",
"usage",
"visualize",
"widget",
"workflow",
"worktree",
"wt"
],
"hooks": [
@ -201,6 +136,11 @@
"turn_start",
"turn_end"
],
"shortcuts": ["Ctrl+Alt+G", "Ctrl+Alt+H", "Ctrl+Alt+M", "Ctrl+Shift+H"]
"shortcuts": [
"Ctrl+Alt+G",
"Ctrl+Alt+H",
"Ctrl+Alt+M",
"Ctrl+Shift+H"
]
}
}

View file

@ -5,6 +5,7 @@ import { test } from "vitest";
import guardrails from "../../guardrails/index.js";
import {
DIRECT_SF_COMMAND_NAMES,
getSfArgumentCompletions,
getSfTopLevelCommandCompletions,
} from "../commands/catalog.js";
import { registerSFCommands } from "../commands/index.js";
@ -21,15 +22,28 @@ test("direct SF command surface registers workflow verbs without legacy sf names
const names = registered.map((entry) => entry.name).sort();
assert.ok(names.includes("autonomous"));
assert.ok(names.includes("plan"));
assert.ok(names.includes("next"));
assert.ok(names.includes("discuss"));
assert.ok(names.includes("schedule"));
assert.ok(names.includes("doctor"));
assert.ok(names.includes("status"));
assert.ok(names.includes("ship"));
assert.equal(names.includes("plan"), false);
assert.equal(names.includes("model"), false);
assert.equal(names.includes("permission-profile"), false);
assert.ok(!names.includes("sf"));
assert.ok(!names.includes("stop"));
assert.deepEqual(names, [...DIRECT_SF_COMMAND_NAMES].sort());
});
test("top_level_completions_keep_platform_owned_product_paths_visible", () => {
const labels = getSfArgumentCompletions("").map((entry) => entry.label);
assert.ok(labels.includes("model"));
assert.equal(labels.includes("mode"), false);
assert.equal(labels.includes("permission-profile"), false);
});
test("direct command completions strip the already typed command name", () => {
assert.deepEqual(getSfTopLevelCommandCompletions("autonomous", "--"), [
{
@ -45,7 +59,7 @@ test("direct command completions strip the already typed command name", () => {
]);
});
test("extension_manifest_uses_permission_profile_command_name", () => {
test("extension_manifest_hides_advanced_control_axes_from_public_sf_surface", () => {
const manifest = JSON.parse(
readFileSync(
join(
@ -56,7 +70,10 @@ test("extension_manifest_uses_permission_profile_command_name", () => {
),
);
const commands = manifest.provides?.commands ?? [];
assert.equal(commands.includes("permission-profile"), true);
assert.equal(commands.includes("permission-profile"), false);
assert.equal(commands.includes("mode"), false);
assert.equal(commands.includes("control"), false);
assert.equal(commands.includes("model-mode"), false);
assert.equal(commands.includes("trust"), false);
});