fix(plan): scope collapsed heading repair

This commit is contained in:
scotttong committed 2026-08-07 18:03:32 -07:00
1 parent 635de31df4
commit 8f84a23aa5
6 files changed
+42 -5

No files matched your search

+1
View File
@@ -2353,6 +2353,7 @@ export {
type EnvironmentCustomImageTerminalSessionToken,
} from "./validators/environment-custom-images.js";
export * from "./validators/skill-policy.js";
export { normalizeMarkdownBody } from "./validators/text.js";
export {
FEATURE_TIERS,
INSTANCE_FEATURE_CATALOG,
+1
View File
@@ -914,4 +914,5 @@ export {
type RevokeToolTrustRule,
} from "./tool-access.js";
export * from "./skill-policy.js";
export { normalizeMarkdownBody } from "./text.js";
export * from "./app-definition.js";
+7 -2
View File
@@ -57,6 +57,11 @@ describe("normalizeMidLineHeadings", () => {
expect(normalizeMidLineHeadings(body)).toBe(body);
});
it("does not close a long fence with a shorter run", () => {
const body = "````markdown\n```\ncode ## comment\n````";
expect(normalizeMidLineHeadings(body)).toBe(body);
});
it("is a no-op when there is no `#` at all", () => {
const body = "Just some prose with no markers.";
expect(normalizeMidLineHeadings(body)).toBe(body);
@@ -71,8 +76,8 @@ describe("normalizeMarkdownBody / multilineTextSchema", () => {
);
});
it("multilineTextSchema self-heals a collapsed plan body on write", () => {
it("multilineTextSchema leaves unrelated mid-line markers unchanged", () => {
const parsed = multilineTextSchema.parse("plan pack)## The goal, restated");
expect(parsed).toBe("plan pack)\n\n## The goal, restated");
expect(parsed).toBe("plan pack)## The goal, restated");
});
});
+5 -2
View File
@@ -43,6 +43,7 @@ export function normalizeMidLineHeadings(value: string): string {
const lines = value.split("\n");
let inFence = false;
let fenceChar = "";
let fenceLength = 0;
const out: string[] = [];
for (const line of lines) {
const fenceMatch = FENCE_LINE.exec(line);
@@ -51,9 +52,11 @@ export function normalizeMidLineHeadings(value: string): string {
if (!inFence) {
inFence = true;
fenceChar = marker[0]!;
} else if (marker[0] === fenceChar) {
fenceLength = marker.length;
} else if (marker[0] === fenceChar && marker.length >= fenceLength) {
inFence = false;
fenceChar = "";
fenceLength = 0;
}
out.push(line);
continue;
@@ -67,4 +70,4 @@ export function normalizeMarkdownBody(value: string): string {
return normalizeMidLineHeadings(normalizeEscapedLineBreaks(value));
}
export const multilineTextSchema = z.string().transform(normalizeMarkdownBody);
export const multilineTextSchema = z.string().transform(normalizeEscapedLineBreaks);
@@ -281,6 +281,32 @@ describe("document annotation routes", () => {
expect(mockHeartbeatService.wakeup).not.toHaveBeenCalled();
});
it("heals collapsed headings only for the plan document", async () => {
mockIssueService.getById.mockResolvedValue({
id: issueId,
companyId,
title: "Document API",
status: "in_progress",
assigneeAgentId: null,
});
await request(await createApp())
.put(`/api/issues/${issueId}/documents/plan`)
.send({ format: "markdown", body: "Intro)## Goal" })
.expect(200);
expect(mockDocumentService.upsertIssueDocument).toHaveBeenLastCalledWith(
expect.objectContaining({ body: "Intro)\n\n## Goal" }),
);
await request(await createApp())
.put(`/api/issues/${issueId}/documents/notes`)
.send({ format: "markdown", body: "Intro)## Legitimate inline text" })
.expect(200);
expect(mockDocumentService.upsertIssueDocument).toHaveBeenLastCalledWith(
expect.objectContaining({ body: "Intro)## Legitimate inline text" }),
);
});
it("queues one bounded recovery when a board document revision expires the final review interactions", async () => {
const assigneeAgentId = "99999999-9999-4999-8999-999999999999";
mockIssueService.getById.mockResolvedValue({
+2 -1
View File
@@ -68,6 +68,7 @@ import {
getClosedIsolatedExecutionWorkspaceMessage,
isClosedIsolatedExecutionWorkspace,
isUuidLike,
normalizeMarkdownBody,
normalizeIssueIdentifier as normalizeIssueReferenceIdentifier,
type CompactIssue,
type CompanySearchExtractQuery,
@@ -6676,7 +6677,7 @@ export function issueRoutes(
key: keyParsed.data,
title: req.body.title ?? null,
format: req.body.format,
body: req.body.body,
body: keyParsed.data === "plan" ? normalizeMarkdownBody(req.body.body) : req.body.body,
changeSummary: req.body.changeSummary ?? null,
baseRevisionId: req.body.baseRevisionId ?? null,
createdByAgentId: actor.agentId ?? null,