Skip to content

internal/config: TOML schema, defaults, validation #6

Description

@Saber5656

Title

internal/config: TOML schema, defaults, validation

Summary

Implement configuration loading exactly per DESIGN.md §5: TOML file at the
XDG path, zero-config defaults, tilde expansion, strict validation with all
errors reported at once, and unknown-key warnings.

Context

Every subcommand starts by loading config; worklog init (27) writes the
starter file; doctor (28) re-uses validation. This issue introduces the
project's single third-party dependency (github.com/BurntSushi/toml,
ADR-001).

Scope

  • internal/config/config.go, defaults.go, validate.go, tests, and
    testdata/*.toml fixtures. Adds the toml dependency to go.mod.

Detailed Requirements

  1. Structs mirroring DESIGN §5's TOML exactly (General, Output,
    Sources, SourcesZsh, SourcesGit, SourcesClaudeCode,
    SourcesCodex, LLM, Redaction, Standup), field tags matching the
    TOML key names shown there (toml:"history_file" etc.).
  2. Default() *Config returns the documented defaults verbatim (language
    "ja", git root ~/dev, llm.enabled false (opt-in), llm endpoint
    http://127.0.0.1:11434/v1, timeout_seconds 120, max_input_chars
    24000, max_output_chars 4000, temperature 0.2, redaction "on",
    standup.previous_workday true, sources enabled: all four, max_depth
    3, exclude_dirs per DESIGN §5).
  3. DefaultPath() string$XDG_CONFIG_HOME/worklog/config.toml when
    XDG_CONFIG_HOME is set and absolute, else ~/.config/worklog/config.toml.
  4. Load(path string) (*Config, []string, error):
    • path == ""DefaultPath(); missing file → defaults, no error.
    • Decode with toml.DecodeFile using md.Undecoded() to produce one
      warning string per unknown key (typo detection), returned as the second
      value — never an error.
    • After decode, merge onto defaults (decode into a copy of Default() so
      absent keys keep defaults).
  5. ExpandPath(p string) string~ and ~/ expansion via
    os.UserHomeDir; no environment-variable expansion (predictability rule,
    DESIGN §5). Applied by accessor methods (c.ZshHistoryFile(),
    c.GitRoots(), c.ClaudeProjectsDir(), c.CodexSessionsDir(),
    c.CodexSessionIndex(), c.CodexArchivedDir(), c.OutputDirectory())
    rather than mutating stored values (round-trip fidelity for init).
    c.ZshHistoryFile() implements the resolution order: configured value →
    $HISTFILE (absolute paths only) → ~/.zsh_history.
  6. Validate() []error collecting ALL violations, per DESIGN §5 rules:
    language ∈ {ja,en}; timezone loads; every regex in
    sources.zsh.exclude_patterns, redaction.extra_patterns,
    redaction.allowlist compiles as RE2; sources.enabled values ∈ the four
    known ids (unknown id = error, not warning); llm.enabled && model==""
    error text mentioning worklog doctor (reachable only via an explicit
    config file since the default is enabled=false — zero-config always
    validates); endpoint URL: scheme http/https + non-empty host (port
    optional); static loopback pre-check: if the host is an IP literal it
    must be loopback (hostnames are checked at dial time, DESIGN §5/§11);
    numeric bounds exactly as listed (timeout_seconds 1–600,
    max_input_chars 1000–1000000, max_output_chars 100–100000,
    temperature 0–2, max_depth 1–6); redaction.mode ∈ {on,off}.
  7. Starter() string returns the commented starter TOML used by
    worklog init (content = DESIGN §5 block with explanatory comments;
    [llm] section ships enabled = false with the comment
    # set enabled = true and model = "..." after running worklog doctor).
    Keep it in a const; issue 27 writes it to disk.

Acceptance Criteria

  • Zero-config: Load("") with no file present returns defaults with no
    warnings/errors.
  • Fixture matrix: full valid file; file with unknown key (warning lists
    the exact dotted key); each validation rule violated once (one fixture
    or table case per rule) with Validate() returning ALL errors
    together, not just the first.
  • ZshHistoryFile() order proven with tests manipulating HISTFILE
    (set-absolute, set-relative→ignored, unset).
  • Starter TOML from Starter() round-trips: decodes with zero unknown
    keys and equals Default() after load.
  • Only new dependency in go.mod is github.com/BurntSushi/toml.

Validation

go test -race -cover ./internal/config/ in PR; go mod tidy diff empty;
boundary script green.

Dependencies

Non-goals

Writing the config file (27), flag handling (07), dial-time loopback
enforcement (23).

Design References

  • docs/DESIGN.md §5, §11.1
  • docs/decisions/ADR-001-go-and-dependency-policy.md

Source of truth: docs/issues/05-config-package.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 05 of 33).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions