Skip to content

Commit f766230

Browse files
committed
Keep heartbeat template doctor check lint-only
1 parent 8e49026 commit f766230

4 files changed

Lines changed: 1 addition & 258 deletions

File tree

src/commands/doctor-heartbeat-template-repair.test.ts

Lines changed: 0 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
analyzeHeartbeatTemplateForRepair,
77
collectHeartbeatTemplateHealthFindings,
88
maybeRepairHeartbeatTemplate,
9-
repairHeartbeatTemplateHealthFindings,
109
} from "./doctor-heartbeat-template-repair.js";
1110

1211
const mocks = vi.hoisted(() => ({
@@ -283,78 +282,4 @@ Add short tasks below the comments only when you want the agent to check somethi
283282
"Doctor changes",
284283
);
285284
});
286-
287-
it("previews pure dirty template repair as a file effect and diff", async () => {
288-
const { workspaceDir, heartbeatPath } = await makeWorkspaceWithHeartbeat(`\`\`\`markdown
289-
# Keep this file empty (or with only comments) to skip heartbeat API calls.
290-
291-
# Add tasks below when you want the agent to check something periodically.
292-
\`\`\`
293-
`);
294-
const cleanTemplate = "# clean heartbeat\n";
295-
const writeTextAtomic = vi.fn();
296-
297-
const result = await repairHeartbeatTemplateHealthFindings({
298-
cfg: { agents: { defaults: { workspace: workspaceDir } } },
299-
findings: [
300-
{
301-
checkId: "core/doctor/heartbeat-template",
302-
severity: "warning",
303-
message: "legacy template",
304-
path: heartbeatPath,
305-
requirement: "legacy-template",
306-
},
307-
],
308-
dryRun: true,
309-
diff: true,
310-
deps: {
311-
readCleanHeartbeatTemplate: async () => cleanTemplate,
312-
writeTextAtomic,
313-
},
314-
});
315-
316-
expect(writeTextAtomic).not.toHaveBeenCalled();
317-
expect(result.changes).toEqual([
318-
`Would replace ${heartbeatPath} with the clean heartbeat template.`,
319-
]);
320-
expect(result.effects).toEqual([
321-
{
322-
kind: "file",
323-
action: "would-replace-heartbeat-template",
324-
target: heartbeatPath,
325-
dryRunSafe: false,
326-
},
327-
]);
328-
expect(result.diffs).toEqual([
329-
expect.objectContaining({
330-
kind: "file",
331-
path: heartbeatPath,
332-
before: expect.stringContaining("```markdown"),
333-
after: cleanTemplate,
334-
}),
335-
]);
336-
await expect(fs.readFile(heartbeatPath, "utf-8")).resolves.toContain("```markdown");
337-
});
338-
339-
it("skips structured repair when only manual heartbeat findings are present", async () => {
340-
const result = await repairHeartbeatTemplateHealthFindings({
341-
cfg: {},
342-
findings: [
343-
{
344-
checkId: "core/doctor/heartbeat-template",
345-
severity: "warning",
346-
message: "custom content",
347-
path: "/tmp/HEARTBEAT.md",
348-
requirement: "legacy-template-with-custom-content",
349-
},
350-
],
351-
dryRun: true,
352-
});
353-
354-
expect(result).toEqual({
355-
status: "skipped",
356-
reason: "only manual heartbeat template findings were present",
357-
changes: [],
358-
});
359-
});
360285
});

src/commands/doctor-heartbeat-template-repair.ts

Lines changed: 1 addition & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,7 @@ import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent
66
import { resolveWorkspaceTemplateDir } from "../agents/workspace-templates.js";
77
import { DEFAULT_HEARTBEAT_FILENAME } from "../agents/workspace.js";
88
import type { OpenClawConfig } from "../config/types.openclaw.js";
9-
import type {
10-
HealthFinding,
11-
HealthRepairDiff,
12-
HealthRepairEffect,
13-
HealthRepairResult,
14-
} from "../flows/health-checks.js";
9+
import type { HealthFinding } from "../flows/health-checks.js";
1510
import { formatErrorMessage } from "../infra/errors.js";
1611
import { writeTextAtomic } from "../infra/json-files.js";
1712
import { shortenHomePath } from "../utils.js";
@@ -195,115 +190,6 @@ export async function collectHeartbeatTemplateHealthFindings(
195190
return [heartbeatTemplateAnalysisToHealthFinding(heartbeatPath, analysis)];
196191
}
197192

198-
function heartbeatTemplateReplacementEffect(
199-
heartbeatPath: string,
200-
dryRun: boolean,
201-
): HealthRepairEffect {
202-
return {
203-
kind: "file",
204-
action: dryRun ? "would-replace-heartbeat-template" : "replace-heartbeat-template",
205-
target: heartbeatPath,
206-
dryRunSafe: false,
207-
};
208-
}
209-
210-
function heartbeatTemplateReplacementDiff(
211-
heartbeatPath: string,
212-
before: string,
213-
after: string,
214-
): HealthRepairDiff {
215-
return {
216-
kind: "file",
217-
path: heartbeatPath,
218-
before,
219-
after,
220-
};
221-
}
222-
223-
/** Repairs or previews known legacy HEARTBEAT.md template rewrites for structured Doctor repair. */
224-
export async function repairHeartbeatTemplateHealthFindings(params: {
225-
cfg: OpenClawConfig;
226-
findings: readonly HealthFinding[];
227-
dryRun?: boolean;
228-
diff?: boolean;
229-
deps?: {
230-
readFile?: (filePath: string) => Promise<string>;
231-
writeTextAtomic?: typeof writeTextAtomic;
232-
readCleanHeartbeatTemplate?: () => Promise<string>;
233-
};
234-
}): Promise<HealthRepairResult> {
235-
const repairableFinding = params.findings.find(
236-
(finding) =>
237-
finding.checkId === HEARTBEAT_TEMPLATE_CHECK_ID &&
238-
finding.requirement === "legacy-template" &&
239-
typeof finding.path === "string",
240-
);
241-
if (repairableFinding === undefined) {
242-
return {
243-
status: "skipped",
244-
reason: "only manual heartbeat template findings were present",
245-
changes: [],
246-
};
247-
}
248-
249-
const heartbeatPath = repairableFinding.path;
250-
if (typeof heartbeatPath !== "string") {
251-
return {
252-
status: "skipped",
253-
reason: "heartbeat template finding did not include a file path",
254-
changes: [],
255-
};
256-
}
257-
const readFile = params.deps?.readFile ?? ((filePath: string) => fs.readFile(filePath, "utf-8"));
258-
const writeFile = params.deps?.writeTextAtomic ?? writeTextAtomic;
259-
const readTemplate = params.deps?.readCleanHeartbeatTemplate ?? readCleanHeartbeatTemplate;
260-
let content: string;
261-
let cleanTemplate: string;
262-
try {
263-
[content, cleanTemplate] = await Promise.all([readFile(heartbeatPath), readTemplate()]);
264-
} catch (error) {
265-
return {
266-
status: "failed",
267-
reason: `could not prepare heartbeat template repair: ${formatErrorMessage(error)}`,
268-
changes: [],
269-
};
270-
}
271-
272-
if (analyzeHeartbeatTemplateForRepair(content).status !== "dirty-template") {
273-
return {
274-
status: "skipped",
275-
reason: "heartbeat file is not a pure legacy template",
276-
changes: [],
277-
};
278-
}
279-
280-
if (params.dryRun !== true) {
281-
try {
282-
await writeFile(heartbeatPath, cleanTemplate, { mode: 0o600 });
283-
} catch (error) {
284-
return {
285-
status: "failed",
286-
reason: `could not repair heartbeat template: ${formatErrorMessage(error)}`,
287-
changes: [],
288-
};
289-
}
290-
}
291-
292-
const shortPath = shortenHomePath(heartbeatPath);
293-
return {
294-
changes: [
295-
params.dryRun === true
296-
? `Would replace ${shortPath} with the clean heartbeat template.`
297-
: `Replaced ${shortPath} with the clean heartbeat template.`,
298-
],
299-
diffs:
300-
params.diff === true
301-
? [heartbeatTemplateReplacementDiff(heartbeatPath, content, cleanTemplate)]
302-
: [],
303-
effects: [heartbeatTemplateReplacementEffect(heartbeatPath, params.dryRun === true)],
304-
};
305-
}
306-
307193
/** Replaces known dirty heartbeat templates with the clean runtime template when repair is enabled. */
308194
export async function maybeRepairHeartbeatTemplate(params: {
309195
cfg: OpenClawConfig;

src/flows/doctor-health-contributions.test.ts

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,6 @@ const mocks = vi.hoisted(() => ({
8686
collectDiskSpaceHealthFindings: vi.fn((): readonly HealthFinding[] => []),
8787
collectHeartbeatTemplateHealthFindings: vi.fn(async () => [] as unknown[]),
8888
maybeRepairHeartbeatTemplate: vi.fn().mockResolvedValue(undefined),
89-
repairHeartbeatTemplateHealthFindings: vi.fn(async () => ({
90-
changes: [] as string[],
91-
effects: [] as unknown[],
92-
})),
9389
collectDevicePairingHealthFindings: vi.fn(async () => []),
9490
scanConfiguredChannelPluginBlockers: vi.fn(
9591
(): Array<{ channelId: string; pluginId: string; reason: string }> => [],
@@ -312,7 +308,6 @@ vi.mock("../commands/doctor-disk-space.js", () => ({
312308
vi.mock("../commands/doctor-heartbeat-template-repair.js", () => ({
313309
collectHeartbeatTemplateHealthFindings: mocks.collectHeartbeatTemplateHealthFindings,
314310
maybeRepairHeartbeatTemplate: mocks.maybeRepairHeartbeatTemplate,
315-
repairHeartbeatTemplateHealthFindings: mocks.repairHeartbeatTemplateHealthFindings,
316311
}));
317312

318313
vi.mock("../commands/doctor-device-pairing.js", () => ({
@@ -514,8 +509,6 @@ describe("doctor health contributions", () => {
514509
mocks.collectHeartbeatTemplateHealthFindings.mockResolvedValue([]);
515510
mocks.maybeRepairHeartbeatTemplate.mockReset();
516511
mocks.maybeRepairHeartbeatTemplate.mockResolvedValue(undefined);
517-
mocks.repairHeartbeatTemplateHealthFindings.mockReset();
518-
mocks.repairHeartbeatTemplateHealthFindings.mockResolvedValue({ changes: [], effects: [] });
519512
mocks.collectDevicePairingHealthFindings.mockReset();
520513
mocks.collectDevicePairingHealthFindings.mockResolvedValue([]);
521514
mocks.scanConfiguredChannelPluginBlockers.mockReset();
@@ -1077,57 +1070,6 @@ describe("doctor health contributions", () => {
10771070
expect(mocks.collectHeartbeatTemplateHealthFindings).toHaveBeenCalledWith(ctx.cfg);
10781071
});
10791072

1080-
it("threads dry-run heartbeat template repairs through the structured check", async () => {
1081-
const contributionChecks = await resolveDoctorContributionHealthChecks();
1082-
const heartbeatTemplateCheck = contributionChecks.find(
1083-
(check) => check.id === "core/doctor/heartbeat-template",
1084-
);
1085-
expect(heartbeatTemplateCheck).toBeDefined();
1086-
const findings = [
1087-
{
1088-
checkId: "core/doctor/heartbeat-template",
1089-
severity: "warning" as const,
1090-
message: "HEARTBEAT.md contains an older heartbeat documentation template.",
1091-
path: "/tmp/openclaw-workspace/HEARTBEAT.md",
1092-
requirement: "legacy-template",
1093-
},
1094-
];
1095-
mocks.repairHeartbeatTemplateHealthFindings.mockResolvedValueOnce({
1096-
changes: ["Would replace heartbeat template."],
1097-
effects: [
1098-
{
1099-
kind: "file",
1100-
action: "would-replace-heartbeat-template",
1101-
target: "/tmp/openclaw-workspace/HEARTBEAT.md",
1102-
dryRunSafe: false,
1103-
},
1104-
],
1105-
});
1106-
1107-
const result = await heartbeatTemplateCheck!.repair?.(
1108-
{
1109-
cfg: { agents: { defaults: { workspace: "/tmp/openclaw-workspace" } } },
1110-
mode: "fix",
1111-
dryRun: true,
1112-
diff: true,
1113-
runtime: { log: vi.fn(), error: vi.fn(), exit: vi.fn() },
1114-
},
1115-
findings,
1116-
);
1117-
1118-
expect(mocks.repairHeartbeatTemplateHealthFindings).toHaveBeenCalledWith({
1119-
cfg: { agents: { defaults: { workspace: "/tmp/openclaw-workspace" } } },
1120-
findings,
1121-
dryRun: true,
1122-
diff: true,
1123-
});
1124-
expect(result?.effects).toContainEqual(
1125-
expect.objectContaining({
1126-
action: "would-replace-heartbeat-template",
1127-
}),
1128-
);
1129-
});
1130-
11311073
it("preserves allow-exec Gateway SecretRef resolution in auth health", async () => {
11321074
const contribution = requireDoctorContribution("doctor:gateway-auth");
11331075
const ctx = {

src/flows/doctor-health-contributions.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,16 +1848,6 @@ export function resolveDoctorHealthContributions(): DoctorHealthContribution[] {
18481848
await import("../commands/doctor-heartbeat-template-repair.js");
18491849
return await collectHeartbeatTemplateHealthFindings(ctx.cfg);
18501850
},
1851-
async repair(ctx, findings) {
1852-
const { repairHeartbeatTemplateHealthFindings } =
1853-
await import("../commands/doctor-heartbeat-template-repair.js");
1854-
return await repairHeartbeatTemplateHealthFindings({
1855-
cfg: ctx.cfg,
1856-
findings,
1857-
dryRun: ctx.dryRun,
1858-
diff: ctx.diff,
1859-
});
1860-
},
18611851
},
18621852
run: runHeartbeatTemplateRepairHealth,
18631853
}),

0 commit comments

Comments
 (0)