feat(FN-4629): complete Step 2 — show output for worktrunk settings

Fusion-Task-Id: FN-4629
Fusion-Task-Lineage: b8775bc8-4c04-4259-8cad-f5a3ba0c42e4
This commit is contained in:
Fusion (runfusion.ai)
2026-05-15 22:54:56 -07:00
committed by gsxdsm
parent 7995be5380
commit b65717f1bf
2 changed files with 48 additions and 15 deletions

View File

@@ -377,11 +377,11 @@ describe("settings commands", () => {
expect(errorSpy).toHaveBeenCalledWith('Error: Unknown setting "worktrunk.bogus"');
});
it("runSettingsShow includes Worktrunk integration section", async () => {
it("runSettingsShow includes Worktrunk Integration section and dotted values", async () => {
const getSettings = vi.fn().mockResolvedValue(makeSettings({
worktrunk: {
enabled: true,
binaryPath: "/usr/local/bin/worktrunk",
binaryPath: undefined,
onFailure: "fallback-native",
},
}));
@@ -396,10 +396,38 @@ describe("settings commands", () => {
await runSettingsShow("demo-project");
const output = logSpy.mock.calls.map((args) => args.join(" ")).join("\n");
expect(output).toContain("Worktrunk integration");
expect(output).toContain("Worktrunk Integration:");
expect(output).toContain("Worktrunk Enabled");
expect(output).toContain("Worktrunk Binary Path");
expect(output).toContain("true");
expect(output).toContain("Worktrunk On Failure");
expect(output).toContain('"fallback-native"');
expect(output).toContain("Worktrunk Binary Path");
expect(output).toContain("(not set)");
});
it("runSettingsShow omits Worktrunk Integration section when worktrunk is absent", async () => {
const getSettings = vi.fn().mockResolvedValue({
maxConcurrent: 2,
maxWorktrees: 4,
autoResolveConflicts: true,
smartConflictResolution: true,
requirePlanApproval: false,
ntfyEnabled: false,
taskPrefix: "FN",
worktreeNaming: "random",
});
vi.mocked(resolveProject).mockResolvedValue({
projectId: "proj-1",
projectName: "demo-project",
projectPath: "/projects/demo",
isRegistered: true,
store: { getSettings } as any,
});
await runSettingsShow("demo-project");
const output = logSpy.mock.calls.map((args) => args.join(" ")).join("\n");
expect(output).not.toContain("Worktrunk Integration:");
});
it("runSettingsShow includes Node Routing section", async () => {

View File

@@ -142,11 +142,14 @@ export function parseValue(key: ValidSettingKey, value: string): unknown {
/**
* Format a setting value for display
*/
function getDottedValue(obj: unknown, key: string): unknown {
return key.split(".").reduce<unknown>((acc, part) => {
if (!acc || typeof acc !== "object") return undefined;
return (acc as Record<string, unknown>)[part];
}, obj);
function readDottedSetting(settings: GlobalSettings | Settings, key: string): unknown {
const parts = key.split(".");
let cursor: unknown = settings;
for (const part of parts) {
if (cursor == null || typeof cursor !== "object") return undefined;
cursor = (cursor as Record<string, unknown>)[part];
}
return cursor;
}
function applyDottedSetting(key: string, value: unknown, current: Record<string, unknown>): Record<string, unknown> {
@@ -169,8 +172,10 @@ function applyDottedSetting(key: string, value: unknown, current: Record<string,
};
}
type CliSettingKey = string;
function formatSettingValue(
key: string,
key: CliSettingKey,
value: unknown,
_settings: GlobalSettings | Settings
): string {
@@ -191,7 +196,7 @@ function formatSettingValue(
}
if (typeof value === "string") {
const defaultValue = getDottedValue(DEFAULT_SETTINGS, key);
const defaultValue = readDottedSetting(DEFAULT_SETTINGS, key);
if (value === defaultValue) {
return `"${value}" (default)`;
}
@@ -204,7 +209,7 @@ function formatSettingValue(
/**
* Get display name for a setting (convert camelCase to readable)
*/
function getSettingLabel(key: string): string {
function getSettingLabel(key: CliSettingKey): string {
if (key === "ntfyEnabled") return "ntfy Enabled";
if (key === "ntfyTopic") return "ntfy Topic";
if (key === "worktrunk.enabled") return "Worktrunk Enabled";
@@ -249,7 +254,7 @@ export async function runSettingsShow(projectName?: string): Promise<void> {
keys: ["worktreeNaming", "worktreesDir", "recycleWorktrees"],
},
{
title: "Worktrunk integration",
title: "Worktrunk Integration",
keys: ["worktrunk.enabled", "worktrunk.binaryPath", "worktrunk.onFailure"],
},
{
@@ -271,14 +276,14 @@ export async function runSettingsShow(projectName?: string): Promise<void> {
];
for (const group of settingGroups) {
const hasValues = group.keys.some((key) => getDottedValue(settings, key) !== undefined);
const hasValues = group.keys.some((key) => readDottedSetting(settings, key) !== undefined);
if (!hasValues) continue;
console.log();
console.log(` ${group.title}:`);
for (const key of group.keys) {
const value = getDottedValue(settings, key);
const value = readDottedSetting(settings, key);
const label = getSettingLabel(key);
const formattedValue = formatSettingValue(key, value, settings);
console.log(` ${label.padEnd(25)} ${formattedValue}`);