From 86a56e6d686f43a98a8b0a882654d9510db5c7c0 Mon Sep 17 00:00:00 2001 From: Rudy Celekli Date: Fri, 23 Oct 2026 22:23:19 -0400 Subject: [PATCH] fix(orch): read gates and standing orders containing Unicode line separators --- .../poteto-mode/scripts/orch/orch.test.ts | 30 +++++++++++++++++++ .../skills/poteto-mode/scripts/orch/store.ts | 6 ++-- .../poteto-mode/scripts/orch/orch.test.ts | 30 +++++++++++++++++++ .../skills/poteto-mode/scripts/orch/store.ts | 6 ++-- 4 files changed, 66 insertions(+), 6 deletions(-) diff --git a/harness/pstack/claude-code/skills/poteto-mode/scripts/orch/orch.test.ts b/harness/pstack/claude-code/skills/poteto-mode/scripts/orch/orch.test.ts index be8edd505..a805007f4 100644 --- a/harness/pstack/claude-code/skills/poteto-mode/scripts/orch/orch.test.ts +++ b/harness/pstack/claude-code/skills/poteto-mode/scripts/orch/orch.test.ts @@ -492,6 +492,36 @@ describe("Store", () => { ]); }); + it("reads back gates, standing orders, and status containing Unicode line separators", async () => { + const { store } = await initializedStore(); + await store.gates.park({ + id: "release", + question: "Ship now?\u2028Copied from the brief", + options: "ship,wait", + defaultAnswer: "wait", + }); + await store.standing.add({ line: "Never force push.\u2029Ask first." }); + + expect(await store.gates.list()).toEqual([ + { + kind: "open", + id: "release", + question: "Ship now?\u2028Copied from the brief", + options: "ship,wait", + defaultAnswer: "wait", + }, + ]); + expect(await store.standing.show()).toEqual([ + { number: 1, line: "Never force push.\u2029Ask first." }, + ]); + await store.units.add({ id: "u1", track: "build" }); + await store.units.set({ id: "u1", state: "blocked\u2028on review" }); + expect((await store.status.render()).summary.openGateIds).toEqual([ + "release", + ]); + expect((await store.status.render()).changed).toBe("no derived changes"); + }); + for (const forge of ["gh", "origin"] as const) { it(`resolves the ordered frontier from the ${forge} base chain and validates an optional pin`, async () => { const { directory, store } = await initializedStore(); diff --git a/harness/pstack/claude-code/skills/poteto-mode/scripts/orch/store.ts b/harness/pstack/claude-code/skills/poteto-mode/scripts/orch/store.ts index a7cef5af8..ac05a253b 100644 --- a/harness/pstack/claude-code/skills/poteto-mode/scripts/orch/store.ts +++ b/harness/pstack/claude-code/skills/poteto-mode/scripts/orch/store.ts @@ -628,7 +628,7 @@ async function readGates(store: string): Promise { const id = lines.shift() ?? ""; const fields = new Map(); for (const value of lines) { - const match = /^- ([^:]+): (.*)$/.exec(value); + const match = /^- ([^:]+): (.*)$/s.exec(value); if (match === null) { throw new UserError(`gates.md has a malformed gate ${id}`); } @@ -740,7 +740,7 @@ async function readStanding( } const result: StandingLine[] = []; for (const value of raw.split("\n").filter((item) => item.length > 0)) { - const match = /^([1-9]\d*)\. (.+)$/.exec(value); + const match = /^([1-9]\d*)\. (.+)$/s.exec(value); const number = Number(match?.[1] ?? 0); if (match === null || number !== result.length + 1) { throw new UserError("preferences.md has malformed numbering"); @@ -798,7 +798,7 @@ function countRecord(value: unknown): Record | null { } function previousSummary(raw: string): StatusSummary | null { - const match = //.exec(raw); + const match = //s.exec(raw); if (match === null) { return null; } diff --git a/pstack/skills/poteto-mode/scripts/orch/orch.test.ts b/pstack/skills/poteto-mode/scripts/orch/orch.test.ts index be8edd505..a805007f4 100644 --- a/pstack/skills/poteto-mode/scripts/orch/orch.test.ts +++ b/pstack/skills/poteto-mode/scripts/orch/orch.test.ts @@ -492,6 +492,36 @@ describe("Store", () => { ]); }); + it("reads back gates, standing orders, and status containing Unicode line separators", async () => { + const { store } = await initializedStore(); + await store.gates.park({ + id: "release", + question: "Ship now?\u2028Copied from the brief", + options: "ship,wait", + defaultAnswer: "wait", + }); + await store.standing.add({ line: "Never force push.\u2029Ask first." }); + + expect(await store.gates.list()).toEqual([ + { + kind: "open", + id: "release", + question: "Ship now?\u2028Copied from the brief", + options: "ship,wait", + defaultAnswer: "wait", + }, + ]); + expect(await store.standing.show()).toEqual([ + { number: 1, line: "Never force push.\u2029Ask first." }, + ]); + await store.units.add({ id: "u1", track: "build" }); + await store.units.set({ id: "u1", state: "blocked\u2028on review" }); + expect((await store.status.render()).summary.openGateIds).toEqual([ + "release", + ]); + expect((await store.status.render()).changed).toBe("no derived changes"); + }); + for (const forge of ["gh", "origin"] as const) { it(`resolves the ordered frontier from the ${forge} base chain and validates an optional pin`, async () => { const { directory, store } = await initializedStore(); diff --git a/pstack/skills/poteto-mode/scripts/orch/store.ts b/pstack/skills/poteto-mode/scripts/orch/store.ts index a7cef5af8..ac05a253b 100644 --- a/pstack/skills/poteto-mode/scripts/orch/store.ts +++ b/pstack/skills/poteto-mode/scripts/orch/store.ts @@ -628,7 +628,7 @@ async function readGates(store: string): Promise { const id = lines.shift() ?? ""; const fields = new Map(); for (const value of lines) { - const match = /^- ([^:]+): (.*)$/.exec(value); + const match = /^- ([^:]+): (.*)$/s.exec(value); if (match === null) { throw new UserError(`gates.md has a malformed gate ${id}`); } @@ -740,7 +740,7 @@ async function readStanding( } const result: StandingLine[] = []; for (const value of raw.split("\n").filter((item) => item.length > 0)) { - const match = /^([1-9]\d*)\. (.+)$/.exec(value); + const match = /^([1-9]\d*)\. (.+)$/s.exec(value); const number = Number(match?.[1] ?? 0); if (match === null || number !== result.length + 1) { throw new UserError("preferences.md has malformed numbering"); @@ -798,7 +798,7 @@ function countRecord(value: unknown): Record | null { } function previousSummary(raw: string): StatusSummary | null { - const match = //.exec(raw); + const match = //s.exec(raw); if (match === null) { return null; }