feat(FN-4394): complete Step 1 — add heartbeat scope discipline types and defaults

Fusion-Task-Id: FN-4394
Fusion-Task-Lineage: 1e7ad660-2cd2-40ba-b7b8-7ef552c6fa13
This commit is contained in:
Fusion
2026-05-14 02:33:04 -07:00
committed by gsxdsm
parent 706dab2062
commit 0e14950aec
5 changed files with 55 additions and 1 deletions

View File

@@ -89,6 +89,12 @@ describe("settings key parity", () => {
expect(isGlobalSettingsKey("autoClaimCandidatesInPrompt")).toBe(false);
});
it("keeps heartbeatScopeDiscipline project-scoped with strict default", () => {
expect(DEFAULT_PROJECT_SETTINGS.heartbeatScopeDiscipline).toBe("strict");
expect(isProjectSettingsKey("heartbeatScopeDiscipline")).toBe(true);
expect(isGlobalSettingsKey("heartbeatScopeDiscipline")).toBe(false);
});
it("documents autoClaimCandidatesInPrompt expected integer range", () => {
const inRange = [0, 1, 5, 10];
const outOfRange = [-1, 11, 100];

View File

@@ -3,6 +3,7 @@ import {
validateDirectMergeCommitStrategy,
validateGithubAuthMode,
validateGithubRepoSlug,
validateHeartbeatScopeDisciplineMode,
validateUnavailableNodePolicy,
} from "../settings-validation.js";
@@ -47,6 +48,20 @@ describe("settings-validation", () => {
});
});
describe("validateHeartbeatScopeDisciplineMode", () => {
it("accepts supported modes", () => {
expect(validateHeartbeatScopeDisciplineMode("strict")).toBe("strict");
expect(validateHeartbeatScopeDisciplineMode("lite")).toBe("lite");
expect(validateHeartbeatScopeDisciplineMode("off")).toBe("off");
});
it("returns undefined for invalid values", () => {
expect(validateHeartbeatScopeDisciplineMode("minimal")).toBeUndefined();
expect(validateHeartbeatScopeDisciplineMode(123)).toBeUndefined();
expect(validateHeartbeatScopeDisciplineMode(undefined)).toBeUndefined();
});
});
describe("validateGithubRepoSlug", () => {
it("accepts valid owner/repo slugs", () => {
expect(validateGithubRepoSlug("owner/repo")).toBe("owner/repo");

View File

@@ -172,6 +172,7 @@ export const DEFAULT_PROJECT_SETTINGS = {
pollIntervalMs: 15000,
heartbeatMultiplier: 1,
autoClaimCandidatesInPrompt: 5,
heartbeatScopeDiscipline: "strict",
groupOverlappingFiles: true,
overlapIgnorePaths: [],
autoMerge: true,

View File

@@ -1,9 +1,19 @@
import type { DirectMergeCommitStrategy, GithubAuthMode, UnavailableNodePolicy } from "./types.js";
import type {
DirectMergeCommitStrategy,
GithubAuthMode,
HeartbeatScopeDisciplineMode,
UnavailableNodePolicy,
} from "./types.js";
const UNAVAILABLE_NODE_POLICIES: readonly UnavailableNodePolicy[] = ["block", "fallback-local"] as const;
const DIRECT_MERGE_COMMIT_STRATEGIES: readonly DirectMergeCommitStrategy[] = ["auto", "always-squash", "always-rebase"] as const;
const GITHUB_AUTH_MODES: readonly GithubAuthMode[] = ["gh-cli", "token"] as const;
const GITHUB_REPO_SLUG_PATTERN = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
const HEARTBEAT_SCOPE_DISCIPLINE_MODES: readonly HeartbeatScopeDisciplineMode[] = [
"strict",
"lite",
"off",
] as const;
/**
* Validates a project unavailable-node routing policy value.
@@ -60,3 +70,16 @@ export function validateGithubRepoSlug(value: unknown): string | undefined {
}
return GITHUB_REPO_SLUG_PATTERN.test(trimmed) ? trimmed : undefined;
}
/** Returns a validated heartbeat scope-discipline mode for project/agent settings, otherwise undefined. */
export function validateHeartbeatScopeDisciplineMode(value: unknown): HeartbeatScopeDisciplineMode | undefined {
if (value === undefined) {
return undefined;
}
if (typeof value !== "string") {
return undefined;
}
return (HEARTBEAT_SCOPE_DISCIPLINE_MODES as readonly string[]).includes(value)
? (value as HeartbeatScopeDisciplineMode)
: undefined;
}

View File

@@ -1665,6 +1665,7 @@ export interface ResolvedEvalSettings {
}
export type AgentMemoryInclusionMode = "full" | "index" | "off";
export type HeartbeatScopeDisciplineMode = "strict" | "lite" | "off";
export interface GlobalSettings {
/** Theme mode preference: dark, light, or system (follows OS). Default: "dark". */
@@ -2095,6 +2096,12 @@ export interface ProjectSettings {
heartbeatMultiplier?: number;
/** Number of auto-claim candidates rendered in no-task heartbeat prompts. Range: 0-10. Default: 5. */
autoClaimCandidatesInPrompt?: number;
/** Heartbeat scope-discipline procedure mode.
* - "strict": coordination-focused scope discipline (default)
* - "lite": pre-FN-3884 behavior
* - "off": minimal procedure with no scope-classification step
*/
heartbeatScopeDiscipline?: HeartbeatScopeDisciplineMode;
groupOverlappingFiles: boolean;
/** File/directory paths to ignore when evaluating overlap serialization.
* Entries are project-relative paths (for example: `docs/README.md`, `docs/`, `generated/*`).
@@ -4689,6 +4696,8 @@ export interface AgentHeartbeatConfig {
budgetConfig?: AgentBudgetConfig;
/** Per-agent override for memory prompt inclusion mode. */
agentMemoryInclusionMode?: AgentMemoryInclusionMode;
/** Per-agent override for heartbeat scope-discipline procedure mode. */
heartbeatScopeDiscipline?: HeartbeatScopeDisciplineMode;
/** Last resolved memory inclusion mode recorded by engine for transition logging. */
lastAgentMemoryInclusionMode?: AgentMemoryInclusionMode;
/**