Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/configure-herdr-refresh-intervals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@leesangb/wt": minor
---

Allow Herdr Git diff and pull request refresh intervals to be configured per repository.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ GitHub CLI's watch mode and update as soon as they finish. Focusing a Space
refreshes it immediately; stable states use a five-minute safety refresh, and
stale values expire during an outage.

These intervals can be customized with `herdr.refresh` settings. Values are in
seconds; focused, background, and pull request refreshes have safe minimums of
5, 30, and 60 seconds respectively. Settings are re-read while watchers run,
and focusing a Space always triggers an immediate refresh.

Herdr metadata tokens are display-only, so open the selected Space's pull
request with the `wt.herdr.open-pr` action. You can optionally bind it directly:

Expand Down Expand Up @@ -419,6 +424,9 @@ Edit `.wt/settings.json` in your repository:
- **scripts.post**: Array of commands to run after creating worktree (runs in new worktree directory)
- **scripts.postMode**: `async` (default) or `sync` for foreground execution
- **herdr.closeWorkspaceOnRemove**: Close the associated Herdr workspace after `wt rm` (default: `true`)
- **herdr.refresh.focusedSeconds**: Git diff refresh interval for a focused Herdr Space (default: `30`, minimum: `5`)
- **herdr.refresh.backgroundSeconds**: Git diff refresh interval for a background Herdr Space (default: `300`, minimum: `30`)
- **herdr.refresh.pullRequestSeconds**: Safety refresh interval for stable pull request and CI status (default: `300`, minimum: `60`)
- **issue.pattern**: Optional JavaScript regular expression for extracting issue keys from branch names
- **issue.url**: Optional issue tracker URL template. Use `$issue` where the extracted key should appear

Expand Down Expand Up @@ -449,7 +457,12 @@ Example `.wt/settings.local.json`:
"postMode": "sync"
},
"herdr": {
"closeWorkspaceOnRemove": false
"closeWorkspaceOnRemove": false,
"refresh": {
"focusedSeconds": 15,
"backgroundSeconds": 600,
"pullRequestSeconds": 600
}
}
}
```
Expand Down
85 changes: 85 additions & 0 deletions src/domain/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,35 @@ describe("mergeSettingsInputs", () => {
},
});
});

test("merges local Herdr refresh overrides without dropping shared intervals", () => {
expect(
mergeSettingsInputs(
{
herdr: {
refresh: {
focusedSeconds: 20,
backgroundSeconds: 240,
},
},
},
{
herdr: {
refresh: {
backgroundSeconds: 600,
},
},
}
)
).toEqual({
herdr: {
refresh: {
focusedSeconds: 20,
backgroundSeconds: 600,
},
},
});
});
});

describe("normalizeSettings", () => {
Expand Down Expand Up @@ -100,6 +129,11 @@ describe("normalizeSettings", () => {
},
herdr: {
closeWorkspaceOnRemove: true,
refresh: {
focusedSeconds: 30,
backgroundSeconds: 300,
pullRequestSeconds: 300,
},
},
});
});
Expand Down Expand Up @@ -127,6 +161,11 @@ describe("normalizeSettings", () => {
},
herdr: {
closeWorkspaceOnRemove: true,
refresh: {
focusedSeconds: 30,
backgroundSeconds: 300,
pullRequestSeconds: 300,
},
},
});
});
Expand Down Expand Up @@ -154,6 +193,47 @@ describe("normalizeSettings", () => {
}).herdr
).toEqual({
closeWorkspaceOnRemove: false,
refresh: {
focusedSeconds: 30,
backgroundSeconds: 300,
pullRequestSeconds: 300,
},
});
});

test("normalizes configurable Herdr refresh intervals in seconds", () => {
expect(
normalizeSettings({
herdr: {
refresh: {
focusedSeconds: 12,
backgroundSeconds: 90,
pullRequestSeconds: 120,
},
},
}).herdr.refresh
).toEqual({
focusedSeconds: 12,
backgroundSeconds: 90,
pullRequestSeconds: 120,
});
});

test("clamps Herdr refresh intervals to safe minimums", () => {
expect(
normalizeSettings({
herdr: {
refresh: {
focusedSeconds: 1,
backgroundSeconds: 2,
pullRequestSeconds: 3,
},
},
}).herdr.refresh
).toEqual({
focusedSeconds: 5,
backgroundSeconds: 30,
pullRequestSeconds: 60,
});
});

Expand All @@ -177,6 +257,11 @@ describe("normalizeSettings", () => {
},
herdr: {
closeWorkspaceOnRemove: true,
refresh: {
focusedSeconds: 30,
backgroundSeconds: 300,
pullRequestSeconds: 300,
},
},
});
});
Expand Down
54 changes: 53 additions & 1 deletion src/domain/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ export interface WtIssueSettings {
url: string;
}

export interface WtHerdrRefreshSettings {
focusedSeconds: number;
backgroundSeconds: number;
pullRequestSeconds: number;
}

export interface WtHerdrSettings {
closeWorkspaceOnRemove: boolean;
refresh: WtHerdrRefreshSettings;
}

export interface WtSettings {
Expand All @@ -31,13 +38,18 @@ export interface WtSettings {
}

export type WtCopySettingsInput = string[] | Partial<WtCopySettings>;
export type WtHerdrSettingsInput = Partial<
Omit<WtHerdrSettings, "refresh">
> & {
refresh?: Partial<WtHerdrRefreshSettings>;
};

export type WtSettingsInput = Partial<
Omit<WtSettings, "copy" | "scripts" | "herdr" | "issue">
> & {
copy?: WtCopySettingsInput;
scripts?: Partial<WtScriptsSettings>;
herdr?: Partial<WtHerdrSettings>;
herdr?: WtHerdrSettingsInput;
issue?: Partial<WtIssueSettings>;
};

Expand All @@ -56,6 +68,11 @@ export const DEFAULT_WT_SETTINGS: WtSettings = {
},
herdr: {
closeWorkspaceOnRemove: true,
refresh: {
focusedSeconds: 30,
backgroundSeconds: 300,
pullRequestSeconds: 300,
},
},
};

Expand Down Expand Up @@ -88,6 +105,16 @@ function normalizeIssueInput(
};
}

function normalizeRefreshSeconds(
value: number | undefined,
fallback: number,
minimum: number
): number {
return typeof value === "number" && Number.isFinite(value)
? Math.max(minimum, Math.floor(value))
: fallback;
}

export function mergeSettingsInputs(
base?: WtSettingsInput | null,
override?: WtSettingsInput | null
Expand Down Expand Up @@ -119,11 +146,19 @@ export function mergeSettingsInputs(
...override?.issue,
}
: undefined;
const mergedHerdrRefresh =
base?.herdr?.refresh || override?.herdr?.refresh
? {
...base?.herdr?.refresh,
...override?.herdr?.refresh,
}
: undefined;
const mergedHerdr =
base?.herdr || override?.herdr
? {
...base?.herdr,
...override?.herdr,
...(mergedHerdrRefresh ? { refresh: mergedHerdrRefresh } : {}),
}
: undefined;

Expand Down Expand Up @@ -161,6 +196,23 @@ export function normalizeSettings(
closeWorkspaceOnRemove:
input?.herdr?.closeWorkspaceOnRemove ??
DEFAULT_WT_SETTINGS.herdr.closeWorkspaceOnRemove,
refresh: {
focusedSeconds: normalizeRefreshSeconds(
input?.herdr?.refresh?.focusedSeconds,
DEFAULT_WT_SETTINGS.herdr.refresh.focusedSeconds,
5
),
backgroundSeconds: normalizeRefreshSeconds(
input?.herdr?.refresh?.backgroundSeconds,
DEFAULT_WT_SETTINGS.herdr.refresh.backgroundSeconds,
30
),
pullRequestSeconds: normalizeRefreshSeconds(
input?.herdr?.refresh?.pullRequestSeconds,
DEFAULT_WT_SETTINGS.herdr.refresh.pullRequestSeconds,
60
),
},
},
...(issueInput ? { issue: issueInput } : {}),
};
Expand Down
27 changes: 27 additions & 0 deletions src/herdr-plugin/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import {
parseWorktreeOpenedEvent,
parsePluginContext,
parseWtJsonOutput,
pullRequestRefreshIntervalMs,
pullRequestStatusToken,
refreshMetadataTtlMs,
streamAndCaptureOutput,
} from "./index.js";

Expand Down Expand Up @@ -227,6 +229,31 @@ describe("wt Herdr plugin", () => {
expect(gitDiffRefreshIntervalMs(false)).toBe(300_000);
});

test("uses configured intervals for focused and background Spaces", () => {
const refresh = {
focusedSeconds: 12,
backgroundSeconds: 90,
pullRequestSeconds: 120,
};

expect(gitDiffRefreshIntervalMs(true, refresh)).toBe(12_000);
expect(gitDiffRefreshIntervalMs(false, refresh)).toBe(90_000);
});

test("uses the configured pull request safety refresh interval", () => {
expect(
pullRequestRefreshIntervalMs({
focusedSeconds: 12,
backgroundSeconds: 90,
pullRequestSeconds: 120,
})
).toBe(120_000);
});

test("keeps metadata alive for two configured refresh periods", () => {
expect(refreshMetadataTtlMs(120_000)).toBe(240_000);
});

test("extracts the opened workspace and checkout from a Herdr event", () => {
expect(
parseWorktreeOpenedEvent(
Expand Down
Loading
Loading