Title
Daily report renderer (ja/en) with golden tests
Summary
Implement the deterministic Markdown renderer for daily reports: embedded
text/template templates for Japanese and English, the section structure of
DESIGN.md §9.1, escaping via internal/sanitize, and byte-exact golden
tests.
Context
The deterministic report IS the product (the LLM only adds a narrative
paragraph). Golden tests here become the regression net for every upstream
change, so template output must be stable, timezone-explicit, and
locale-independent.
Scope
internal/render/render.go, funcs.go,
templates/daily.ja.md.tmpl, templates/daily.en.md.tmpl (embedded via
embed.FS), testdata/golden/*.md, tests.
Detailed Requirements
func Daily(r *model.Report) (string, error) — selects template by
r.Lang (ja/en only; anything else = error, config already
validated).
- Section structure exactly per DESIGN §9.1, in order:
# 日報 2026-07-10 (木) / # Daily report 2026-07-10 (Thu) — date +
weekday; timezone shown when not system-local? Simpler fixed rule:
always show IANA zone in the header line's suffix
(— Asia/Tokyo).
- Summary line: totals sentence (commits, sessions, commands, active
span HH:MM–HH:MM); zero-activity day renders the explicit
no-activity sentence instead (ja: 記録された活動はありません。 /
en: No recorded activity.) and skips sections 3–5.
- Narrative: rendered only when
r.Narrative.Text != "", under heading
## サマリー(ローカルLLM: <model>) / ## Summary (local model: <model>). Allowed narrative surface: plain paragraphs only — the
renderer applies sanitize.EscapeText per line (control chars were
already stripped by 24; this pass additionally HTML-escapes </&
and neutralizes leading list/heading markers), so LLM output cannot
inject headings, HTML, or list structure into the report.
- Per-project sections
## <project> — the project name is rendered
through sanitize.EscapeText (heading position is text context;
neutralizes structure forgery; the name was already
sanitized+redacted in aggregation): commits table
(| time | hash | subject | +/− |, hash in backticks, subject via
sanitize.EscapeCell), sessions table
(| agent | span | msgs | tasks | title |, title via EscapeCell).
Omit an empty table entirely (no headers over nothing).
## シェル操作 / ## Shell activity: | command | count | first–last |
top groups + total line; omitted when zero commands.
- Warnings (when any):
## 注記 / ## Notes — bullet per warning
(- [source] code: message, message via EscapeText).
- Footer (always): one line —
Generated by worklog <version> · sources: zsh,git,… · LLM: <model|deterministic> · redaction: on|off.
- Formatting rules: times
15:04 in report tz; dates 2006-01-02;
weekday names: ja (月火水木金土日), en Mon… — via lookup tables, NOT
locale (time.Format weekday is English-only; map it).
Insertions/deletions cell: +12/−3 (U+2212 not needed — ASCII hyphen).
All numbers via strconv (no locale grouping).
- Template funcs registered from internal/sanitize only (
escCell,
escText) plus tiny format helpers; NO business logic in templates
(tables built from prepared row structs in Go).
- Output ends with exactly one trailing newline; no trailing spaces on any
line (golden-enforced; makes diffs clean).
- Golden tests: fixtures for (ja, en) × (full report incl. narrative +
warnings, empty day, no-narrative day, adversarial content report —
subjects with |, backticks, &/< (ANSI already stripped upstream),
long Japanese titles, a malicious project name like
# fake | heading, and a narrative containing ## injected +
<script>). -update flag regenerates goldens.
Acceptance Criteria
Validation
go test -race -cover ./internal/render/ in PR; attach one full ja golden
file in the PR description for human review of tone/format.
Dependencies
06, 18.
Non-goals
Standup templates (21), custom/user templates (v2), HTML output, file
writing (22).
Design References
docs/DESIGN.md §9.1, §12.2 (markdown injection row), §1.1 (report
languages)
Source of truth: docs/issues/20-render-daily.md (PR #1, branch docs/v1-design). If this issue and the repo docs disagree, the docs win. Execution order and dependencies: docs/ISSUE_PLAN.md (this is issue 20 of 33).
Title
Daily report renderer (ja/en) with golden tests
Summary
Implement the deterministic Markdown renderer for daily reports: embedded
text/templatetemplates for Japanese and English, the section structure ofDESIGN.md §9.1, escaping via internal/sanitize, and byte-exact golden
tests.
Context
The deterministic report IS the product (the LLM only adds a narrative
paragraph). Golden tests here become the regression net for every upstream
change, so template output must be stable, timezone-explicit, and
locale-independent.
Scope
internal/render/render.go,funcs.go,templates/daily.ja.md.tmpl,templates/daily.en.md.tmpl(embedded viaembed.FS),testdata/golden/*.md, tests.Detailed Requirements
func Daily(r *model.Report) (string, error)— selects template byr.Lang(ja/enonly; anything else = error, config alreadyvalidated).
# 日報 2026-07-10 (木)/# Daily report 2026-07-10 (Thu)— date +weekday; timezone shown when not system-local? Simpler fixed rule:
always show IANA zone in the header line's suffix
(
— Asia/Tokyo).span
HH:MM–HH:MM); zero-activity day renders the explicitno-activity sentence instead (ja:
記録された活動はありません。/en:
No recorded activity.) and skips sections 3–5.r.Narrative.Text != "", under heading## サマリー(ローカルLLM: <model>)/## Summary (local model: <model>). Allowed narrative surface: plain paragraphs only — therenderer applies
sanitize.EscapeTextper line (control chars werealready stripped by 24; this pass additionally HTML-escapes
</&and neutralizes leading list/heading markers), so LLM output cannot
inject headings, HTML, or list structure into the report.
## <project>— the project name is renderedthrough
sanitize.EscapeText(heading position is text context;neutralizes structure forgery; the name was already
sanitized+redacted in aggregation): commits table
(
| time | hash | subject | +/− |, hash in backticks, subject viasanitize.EscapeCell), sessions table(
| agent | span | msgs | tasks | title |, title viaEscapeCell).Omit an empty table entirely (no headers over nothing).
## シェル操作/## Shell activity:| command | count | first–last |top groups + total line; omitted when zero commands.
## 注記/## Notes— bullet per warning(
- [source] code: message, message viaEscapeText).Generated by worklog <version> · sources: zsh,git,… · LLM: <model|deterministic> · redaction: on|off.15:04in report tz; dates2006-01-02;weekday names: ja
(月火水木金土日), enMon…— via lookup tables, NOTlocale (
time.Formatweekday is English-only; map it).Insertions/deletions cell:
+12/−3(U+2212 not needed — ASCII hyphen).All numbers via
strconv(no locale grouping).escCell,escText) plus tiny format helpers; NO business logic in templates(tables built from prepared row structs in Go).
line (golden-enforced; makes diffs clean).
warnings, empty day, no-narrative day, adversarial content report —
subjects with
|, backticks,&/<(ANSI already stripped upstream),long Japanese titles, a malicious project name like
# fake | heading, and a narrative containing## injected+<script>).-updateflag regenerates goldens.Acceptance Criteria
tests set
TZ=UTCexplicitly and the Report carries its own zone).|rendersas one cell (the rendered file re-parsed with a naive
|split givesthe expected column count per row — asserted in code, not by eye).
structure); adversarial narrative renders with
## injectedand<script>neutralized (heading count of the document unchanged —asserted in code).
tables/headings.
jaandentemplates produce structurally identical documents(same heading count/order) for the same Report — structural test.
(
{{in a subject stays literal — injection test).Validation
go test -race -cover ./internal/render/in PR; attach one full ja goldenfile in the PR description for human review of tone/format.
Dependencies
06, 18.
Non-goals
Standup templates (21), custom/user templates (v2), HTML output, file
writing (22).
Design References
docs/DESIGN.md§9.1, §12.2 (markdown injection row), §1.1 (reportlanguages)
Source of truth:
docs/issues/20-render-daily.md(PR #1, branchdocs/v1-design). If this issue and the repo docs disagree, the docs win. Execution order and dependencies:docs/ISSUE_PLAN.md(this is issue 20 of 33).