mirror of
https://github.com/sdwolf4103/opencode-working-memory.git
synced 2026-07-17 12:56:42 +02:00
fix: use plain text labels instead of Markdown headers
- Changed '## Memory Candidates' to 'Memory candidates:' in compaction context - Changed '## Pending Todos' to 'Pending todos:' in todo rendering - Updated extractCandidateBlock() to parse plain text format (primary) - Removed stripXmlTags() function (no longer needed) - All 42 tests pass Root cause: Markdown headings (##) render as purple in OpenCode UI, same issue as XML tags and HTML comments. Plain text labels avoid all special markup rendering.
This commit is contained in:
+20
-16
@@ -133,9 +133,8 @@ import { parseWorkspaceMemoryCandidates } from "../src/extractors.ts";
|
||||
|
||||
test("parseWorkspaceMemoryCandidates rejects short text", () => {
|
||||
const summary = `
|
||||
<workspace_memory_candidates>
|
||||
## Memory Candidates
|
||||
- [decision] short text
|
||||
</workspace_memory_candidates>
|
||||
`;
|
||||
const items = parseWorkspaceMemoryCandidates(summary);
|
||||
assert.equal(items.length, 0);
|
||||
@@ -143,9 +142,8 @@ test("parseWorkspaceMemoryCandidates rejects short text", () => {
|
||||
|
||||
test("parseWorkspaceMemoryCandidates rejects git commit hash", () => {
|
||||
const summary = `
|
||||
<workspace_memory_candidates>
|
||||
## Memory Candidates
|
||||
- [project] abc123def456 is the commit hash
|
||||
</workspace_memory_candidates>
|
||||
`;
|
||||
const items = parseWorkspaceMemoryCandidates(summary);
|
||||
assert.equal(items.length, 0);
|
||||
@@ -153,9 +151,8 @@ test("parseWorkspaceMemoryCandidates rejects git commit hash", () => {
|
||||
|
||||
test("parseWorkspaceMemoryCandidates rejects raw error", () => {
|
||||
const summary = `
|
||||
<workspace_memory_candidates>
|
||||
## Memory Candidates
|
||||
- [feedback] TypeError: Cannot read property 'x' of undefined
|
||||
</workspace_memory_candidates>
|
||||
`;
|
||||
const items = parseWorkspaceMemoryCandidates(summary);
|
||||
assert.equal(items.length, 0);
|
||||
@@ -163,9 +160,8 @@ test("parseWorkspaceMemoryCandidates rejects raw error", () => {
|
||||
|
||||
test("parseWorkspaceMemoryCandidates rejects stack trace", () => {
|
||||
const summary = `
|
||||
<workspace_memory_candidates>
|
||||
## Memory Candidates
|
||||
- [reference] at foo (bar.ts:10:5)
|
||||
</workspace_memory_candidates>
|
||||
`;
|
||||
const items = parseWorkspaceMemoryCandidates(summary);
|
||||
assert.equal(items.length, 0);
|
||||
@@ -173,9 +169,8 @@ test("parseWorkspaceMemoryCandidates rejects stack trace", () => {
|
||||
|
||||
test("parseWorkspaceMemoryCandidates rejects commit prefix", () => {
|
||||
const summary = `
|
||||
<workspace_memory_candidates>
|
||||
## Memory Candidates
|
||||
- [project] fix: add new feature
|
||||
</workspace_memory_candidates>
|
||||
`;
|
||||
const items = parseWorkspaceMemoryCandidates(summary);
|
||||
assert.equal(items.length, 0);
|
||||
@@ -183,9 +178,8 @@ test("parseWorkspaceMemoryCandidates rejects commit prefix", () => {
|
||||
|
||||
test("parseWorkspaceMemoryCandidates rejects path-heavy facts", () => {
|
||||
const summary = `
|
||||
<workspace_memory_candidates>
|
||||
## Memory Candidates
|
||||
- [project] files at /src/a.ts /src/b.ts /src/c.ts are important
|
||||
</workspace_memory_candidates>
|
||||
`;
|
||||
const items = parseWorkspaceMemoryCandidates(summary);
|
||||
assert.equal(items.length, 0);
|
||||
@@ -193,9 +187,8 @@ test("parseWorkspaceMemoryCandidates rejects path-heavy facts", () => {
|
||||
|
||||
test("parseWorkspaceMemoryCandidates accepts valid decision", () => {
|
||||
const summary = `
|
||||
<workspace_memory_candidates>
|
||||
## Memory Candidates
|
||||
- [decision] Use pnpm instead of npm for package management
|
||||
</workspace_memory_candidates>
|
||||
`;
|
||||
const items = parseWorkspaceMemoryCandidates(summary);
|
||||
assert.equal(items.length, 1);
|
||||
@@ -205,11 +198,22 @@ test("parseWorkspaceMemoryCandidates accepts valid decision", () => {
|
||||
|
||||
test("parseWorkspaceMemoryCandidates accepts valid project info", () => {
|
||||
const summary = `
|
||||
<workspace_memory_candidates>
|
||||
## Memory Candidates
|
||||
- [project] This project uses TypeScript for all source files
|
||||
</workspace_memory_candidates>
|
||||
`;
|
||||
const items = parseWorkspaceMemoryCandidates(summary);
|
||||
assert.equal(items.length, 1);
|
||||
assert.equal(items[0].type, "project");
|
||||
});
|
||||
|
||||
test("parseWorkspaceMemoryCandidates accepts plain text label format (no Markdown)", () => {
|
||||
const summary = `
|
||||
Memory candidates:
|
||||
- [decision] Use plain text labels to avoid purple Markdown headers
|
||||
- [project] This repo uses pnpm for package management
|
||||
`;
|
||||
const items = parseWorkspaceMemoryCandidates(summary);
|
||||
assert.equal(items.length, 2);
|
||||
assert.equal(items[0].type, "decision");
|
||||
assert.equal(items[1].type, "project");
|
||||
});
|
||||
+15
-29
@@ -235,13 +235,13 @@ test("experimental.session.compacting: context contains no XML tags for compacti
|
||||
assert.equal(contextText.includes("<pending_todos>"), false,
|
||||
"Context should not contain <pending_todos> tag");
|
||||
|
||||
// Verify: context should contain the private context header
|
||||
assert.equal(contextText.includes("[PRIVATE COMPACTION CONTEXT - DO NOT OUTPUT]"), true,
|
||||
"Context should contain private context header");
|
||||
// Verify: context should NOT contain square bracket markers
|
||||
assert.equal(contextText.includes("[PRIVATE COMPACTION CONTEXT"), false,
|
||||
"Context should not contain square bracket markers");
|
||||
|
||||
// Verify: context should contain Markdown headers instead
|
||||
assert.equal(contextText.includes("## Workspace Memory") || contextText.includes("## Hot Session State") || contextText.includes("## Pending Todos"), true,
|
||||
"Context should use Markdown headers instead of XML tags");
|
||||
// Verify: context should contain plain text headers
|
||||
assert.equal(contextText.includes("Workspace memory") || contextText.includes("Hot session state") || contextText.includes("Pending todos"), true,
|
||||
"Context should use plain text headers instead of XML tags");
|
||||
|
||||
} finally {
|
||||
await rm(tmpDir, { recursive: true, force: true });
|
||||
@@ -264,48 +264,34 @@ test("compactionContextHeader does not require XML output", async () => {
|
||||
|
||||
const header = output.context[0] || "";
|
||||
|
||||
// Should instruct HTML comment format
|
||||
assert.equal(header.includes("<!-- workspace_memory_candidates"), true,
|
||||
"Header should instruct HTML comment format");
|
||||
|
||||
// Should NOT instruct legacy XML format as primary
|
||||
// Note: We check that the instruction for HTML comments comes before any XML mention
|
||||
const htmlCommentIndex = header.indexOf("<!-- workspace_memory_candidates");
|
||||
const xmlTagIndex = header.indexOf("<workspace_memory_candidates>\n");
|
||||
if (xmlTagIndex !== -1) {
|
||||
assert.equal(htmlCommentIndex < xmlTagIndex, true,
|
||||
"HTML comment format should be the primary instruction, not XML");
|
||||
}
|
||||
|
||||
// Should explicitly forbid XML
|
||||
assert.equal(header.includes("DO NOT use XML tags"), true,
|
||||
"Header should forbid XML tags");
|
||||
// Should instruct plain text label format (not Markdown headers)
|
||||
assert.equal(header.includes("Memory candidates:"), true,
|
||||
"Header should use plain text 'Memory candidates:' label");
|
||||
|
||||
} finally {
|
||||
await rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("parseWorkspaceMemoryCandidates accepts HTML comment format", async () => {
|
||||
test("parseWorkspaceMemoryCandidates accepts Markdown section format", async () => {
|
||||
const summary = `
|
||||
## Summary
|
||||
Progress made on testing.
|
||||
|
||||
<!-- workspace_memory_candidates
|
||||
- [decision] Use HTML comments for candidates
|
||||
## Memory Candidates
|
||||
- [decision] Use Markdown sections for candidates
|
||||
- [project] This repo uses Markdown for docs
|
||||
-->
|
||||
|
||||
Next steps: continue development.
|
||||
`;
|
||||
|
||||
const candidates = parseWorkspaceMemoryCandidates(summary);
|
||||
assert.equal(candidates.length, 2, "Should parse HTML comment format");
|
||||
assert.equal(candidates.length, 2, "Should parse Markdown section format");
|
||||
assert.equal(candidates[0].type, "decision");
|
||||
assert.equal(candidates[1].type, "project");
|
||||
});
|
||||
|
||||
test("parseWorkspaceMemoryCandidates accepts Markdown section format", async () => {
|
||||
test("parseWorkspaceMemoryCandidates accepts legacy Workspace Memory Candidates section", async () => {
|
||||
const summary = `
|
||||
## Summary
|
||||
Progress made on testing.
|
||||
@@ -318,7 +304,7 @@ Continue development.
|
||||
`;
|
||||
|
||||
const candidates = parseWorkspaceMemoryCandidates(summary);
|
||||
assert.equal(candidates.length, 1, "Should parse Markdown section format");
|
||||
assert.equal(candidates.length, 1, "Should parse legacy section format");
|
||||
assert.equal(candidates[0].type, "reference");
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ function entry(id: string, text: string, type: LongTermMemoryEntry["type"] = "de
|
||||
// Task 2: renderWorkspaceMemory tests
|
||||
// ============================================
|
||||
|
||||
test("renderWorkspaceMemory never truncates closing XML tag", () => {
|
||||
test("renderWorkspaceMemory respects budget and fits entries", () => {
|
||||
const entries = Array.from({ length: 28 }, (_, i) =>
|
||||
entry(`mem_${i}`, `Long durable memory entry ${i} `.repeat(20))
|
||||
);
|
||||
@@ -36,8 +36,8 @@ test("renderWorkspaceMemory never truncates closing XML tag", () => {
|
||||
|
||||
const rendered = renderWorkspaceMemory(store);
|
||||
|
||||
assert.ok(rendered.endsWith("</workspace_memory>"),
|
||||
`Rendered memory must end with closing tag. Got: ...${rendered.slice(-50)}`);
|
||||
assert.ok(!rendered.includes("<workspace_memory>"),
|
||||
"Should not contain XML tags");
|
||||
assert.ok(rendered.length <= 700,
|
||||
`Rendered memory must not exceed maxChars. Got: ${rendered.length}`);
|
||||
});
|
||||
@@ -56,7 +56,7 @@ test("renderWorkspaceMemory returns empty string when maxChars too small", () =>
|
||||
"When maxChars too small for even minimal envelope, return empty string");
|
||||
});
|
||||
|
||||
test("renderWorkspaceMemory respects budget and fits entries", () => {
|
||||
test("renderWorkspaceMemory respects small budget", () => {
|
||||
// Create entries that would overflow a small budget
|
||||
const entries = [
|
||||
entry("a", "First memory entry that is reasonably long"),
|
||||
@@ -74,8 +74,8 @@ test("renderWorkspaceMemory respects budget and fits entries", () => {
|
||||
|
||||
const rendered = renderWorkspaceMemory(store);
|
||||
|
||||
assert.ok(rendered.endsWith("</workspace_memory>"),
|
||||
"Must end with closing tag even when truncating entries");
|
||||
assert.ok(!rendered.includes("<workspace_memory>"),
|
||||
"Should not contain XML tags");
|
||||
assert.ok(rendered.length <= 200,
|
||||
`Must respect maxChars limit. Got: ${rendered.length}`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user