From fd45832d6d42159adc5bc97243f505903221ce87 Mon Sep 17 00:00:00 2001 From: e3322-pcsk9 <107932936+e3322-pcsk9@users.noreply.github.com> Date: Sun, 31 May 2026 19:52:07 +0800 Subject: [PATCH 1/2] test(renderer): add regression tests for literal {{ in non-template content Files containing literal {{ (e.g., test fixtures with "garbage{{") crash shardmind update with RENDER_TEMPLATE_ERROR when the differ renders them through Nunjucks. These tests capture the expected behavior: non-template content should pass through unchanged. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- tests/unit/renderer.test.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/unit/renderer.test.ts b/tests/unit/renderer.test.ts index 8f721f5..d80c48c 100644 --- a/tests/unit/renderer.test.ts +++ b/tests/unit/renderer.test.ts @@ -378,4 +378,29 @@ describe('renderString', () => { const ctx = makeContext({ values: { name: 'alice' } }); expect(renderString('Hi {{ name | shout }}!', ctx, 'x.md', env)).toBe('Hi ALICE!'); }); + + it('passes through content with literal {{ that is not a valid Nunjucks expression', () => { + // Regression test: .test.ts files and other non-template content may + // contain literal {{ (e.g., "garbage{{" in a test fixture). When the + // differ renders old/new templates for three-way merge, files without + // any actual Nunjucks syntax should be returned as-is rather than + // crashing with RENDER_TEMPLATE_ERROR. + const ctx = makeContext({ values: {} }); + const source = 'const input = "garbage{{";\nassert.equal(runScript(input), expected);\n'; + expect(renderString(source, ctx, 'stop-checklist.test.ts')).toBe(source); + }); + + it('passes through content with unclosed {{ in a string literal', () => { + const ctx = makeContext({ values: {} }); + const source = 'test("handles malformed input", () => {\n const result = parse("data{{");\n expect(result).toBeNull();\n});\n'; + expect(renderString(source, ctx, 'test.ts')).toBe(source); + }); + + it('still substitutes valid Nunjucks variables alongside literal {{', () => { + // A file that uses {{ user_name }} (valid) should still get substituted. + // Only broken Nunjucks that can't be parsed should fall back. + const ctx = makeContext({ values: { user_name: 'Alice' } }); + const source = 'Hello {{ user_name }}!\n'; + expect(renderString(source, ctx, 'note.md')).toBe('Hello Alice!\n'); + }); }); From 4772a4454f8d623764dafc8e252bdd0b56e53816 Mon Sep 17 00:00:00 2001 From: e3322-pcsk9 <107932936+e3322-pcsk9@users.noreply.github.com> Date: Sun, 31 May 2026 19:52:16 +0800 Subject: [PATCH 2/2] fix(renderer): pass through non-Nunjucks content on parse error When Nunjucks fails to parse a file and the source contains no actual Nunjucks syntax ({{ expr }}, {% block %}, %}), return the raw source instead of throwing RENDER_TEMPLATE_ERROR. This prevents shardmind update from crashing on non-template files like .test.ts that happen to contain literal {{ strings. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- source/core/renderer.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/core/renderer.ts b/source/core/renderer.ts index 0c1190d..eab2254 100644 --- a/source/core/renderer.ts +++ b/source/core/renderer.ts @@ -253,6 +253,9 @@ function renderTemplate( try { return env.renderString(source, context); } catch (err) { + if (!containsNunjucksSyntax(source)) { + return source; + } const message = err instanceof Error ? err.message : String(err); throw new ShardMindError( `Template error in ${filePath}: ${message}`, @@ -262,6 +265,10 @@ function renderTemplate( } } +function containsNunjucksSyntax(source: string): boolean { + return /%\}|{%|{{.*?}}/.test(source); +} + /** * Windows-reserved device names. NTFS refuses to create a file with any * of these as its basename (case-insensitive), WITH OR WITHOUT an