From 75911faeebffb32affcb5087769252cb09a806c8 Mon Sep 17 00:00:00 2001 From: warren Date: Mon, 6 Apr 2026 10:15:02 -0400 Subject: [PATCH] fix: replace all non-ASCII characters with safe ASCII equivalents Replace box-drawing characters (U+2500) in session log with ASCII dashes. Replace em dashes (U+2014) in CLAUDE.md template with double hyphens. Remove em dash from comment in run.go. Add ASCII-guard tests for session log output and seed templates. Prevents mojibake on Windows terminals that misinterpret UTF-8 as CP1252. Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/seed/templates.go | 12 ++++++------ internal/seed/templates_test.go | 25 +++++++++++++++++++++++++ internal/session/log.go | 4 ++-- internal/session/log_test.go | 28 ++++++++++++++++++++++++++-- internal/session/run.go | 2 +- 5 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 internal/seed/templates_test.go diff --git a/internal/seed/templates.go b/internal/seed/templates.go index b5372fe..0faa8a5 100644 --- a/internal/seed/templates.go +++ b/internal/seed/templates.go @@ -17,11 +17,11 @@ This workspace is scoped to a single task. Keep work focused on the task describ ## Files -- task.yaml — Task metadata (machine-managed, do not edit) -- notes.md — Task log (human/agent-managed) -- context/ — Reference documents (user-managed) -- output/ — Task deliverables and artifacts -- logs/ — Session logs (automatic session snapshots) +- task.yaml -- Task metadata (machine-managed, do not edit) +- notes.md -- Task log (human/agent-managed) +- context/ -- Reference documents (user-managed) +- output/ -- Task deliverables and artifacts +- logs/ -- Session logs (automatic session snapshots) ## Session Handoff @@ -32,7 +32,7 @@ Before ending a session, append a brief summary to notes.md with: - Open follow-ups or unfinished work - How to continue from here -Keep it concise — a few bullet points is enough. This helps the developer (or a future session) resume without losing context. +Keep it concise -- a few bullet points is enough. This helps the developer (or a future session) resume without losing context. `, slug, category, workspacePath) } diff --git a/internal/seed/templates_test.go b/internal/seed/templates_test.go new file mode 100644 index 0000000..df0b28c --- /dev/null +++ b/internal/seed/templates_test.go @@ -0,0 +1,25 @@ +package seed + +import ( + "testing" +) + +func TestClaudeMDIsASCII(t *testing.T) { + content := ClaudeMD("test-slug", "general", "/tmp/test") + for i, b := range []byte(content) { + if b > 127 { + t.Errorf("non-ASCII byte 0x%02x at position %d in CLAUDE.md template", b, i) + break + } + } +} + +func TestNotesMDIsASCII(t *testing.T) { + content := NotesMD("test title") + for i, b := range []byte(content) { + if b > 127 { + t.Errorf("non-ASCII byte 0x%02x at position %d in notes.md template", b, i) + break + } + } +} diff --git a/internal/session/log.go b/internal/session/log.go index 11dbb05..630f7b6 100644 --- a/internal/session/log.go +++ b/internal/session/log.go @@ -40,7 +40,7 @@ func FormatSessionEntry(info *SessionInfo) string { duration := info.EndTime.Sub(info.StartTime) timeFmt := "2006-01-02 15:04:05" - fmt.Fprintf(&b, "── Session %s ──\n", info.StartTime.Format(timeFmt)) + fmt.Fprintf(&b, "-- Session %s --\n", info.StartTime.Format(timeFmt)) fmt.Fprintf(&b, "Agent: %s\n", info.Agent) fmt.Fprintf(&b, "Mode: %s\n", info.Mode) fmt.Fprintf(&b, "Start: %s\n", info.StartTime.Format(timeFmt)) @@ -96,7 +96,7 @@ func FormatSessionEntry(info *SessionInfo) string { b.WriteString("(No changes detected)\n") } - b.WriteString("────────────────────────────────\n") + b.WriteString("--------------------------------\n") return b.String() } diff --git a/internal/session/log_test.go b/internal/session/log_test.go index 64175ad..1c8afb8 100644 --- a/internal/session/log_test.go +++ b/internal/session/log_test.go @@ -127,8 +127,8 @@ func TestAppendSessionLog(t *testing.T) { content := string(data) // Should contain both sessions - if strings.Count(content, "── Session") != 2 { - t.Errorf("expected 2 session entries, got %d", strings.Count(content, "── Session")) + if strings.Count(content, "-- Session") != 2 { + t.Errorf("expected 2 session entries, got %d", strings.Count(content, "-- Session")) } } @@ -167,3 +167,27 @@ func TestManifestCleanupOnSuccess(t *testing.T) { t.Error("manifest should be cleaned up after successful logging") } } + +func TestSessionLogIsASCII(t *testing.T) { + start := time.Date(2026, 4, 5, 14, 30, 22, 0, time.UTC) + end := time.Date(2026, 4, 5, 15, 45, 10, 0, time.UTC) + + info := &SessionInfo{ + Agent: "claude", + Mode: "local", + StartTime: start, + EndTime: end, + Diff: &ManifestDiff{ + Added: []string{"output/result.md"}, + Modified: []string{"notes.md"}, + }, + } + + entry := FormatSessionEntry(info) + for i, b := range []byte(entry) { + if b > 127 { + t.Errorf("non-ASCII byte 0x%02x at position %d in session log output", b, i) + break + } + } +} diff --git a/internal/session/run.go b/internal/session/run.go index a50caf4..af10ee1 100644 --- a/internal/session/run.go +++ b/internal/session/run.go @@ -33,7 +33,7 @@ func Run(opts LaunchOpts) error { startManifest, err := CaptureManifest(opts.WsDir) if err != nil { fmt.Fprintf(os.Stderr, "[ctask] warning: failed to capture start manifest: %v\n", err) - // Continue anyway — never block the user + // Continue anyway -- never block the user } if startManifest != nil {