mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 02:00:52 +01:00
fix(plan): scope collapsed heading repair
This commit is contained in:
1 parent
635de31df4
commit
8f84a23aa5
6 files changed
+42
-5
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user