Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
686 changes: 686 additions & 0 deletions docs/DESIGN.md

Large diffs are not rendered by default.

189 changes: 189 additions & 0 deletions docs/ISSUE_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# worklog — v1 Issue Plan

Status: derived from `docs/DESIGN.md` (2026-07-10). GitHub Issues are created
from `docs/issues/NN-*.md`; when they disagree, these files win.

## v1 completion statement

When issues **01–33** are all completed and each issue's Validation section
passes, worklog v1 is complete as specified by `docs/DESIGN.md`:

> A user on macOS or Linux can install a single `worklog` binary, run
> `worklog init`, `worklog doctor`, `worklog daily`, and `worklog standup`,
> and obtain Japanese or English Markdown reports built from zsh history, git
> commits, and Claude Code / Codex session logs — with no external network
> I/O (loopback LLM only), read-only source access, confined writes, and
> redaction on by default (invariants I1–I5), verified by the CI suite.

One human gate remains outside the issue plan by design: confirming the
license (ADR-005). Note the repository is **already public**, so every push
is a publication — the standing rule is to scan content for personal data
and secrets before each push (ADR-005 §5), not a one-time go-public gate.

## Issue list (recommended execution order)

| # | File | Title (GitHub) | Wave |
|---|---|---|---|
| 01 | `issues/01-repo-scaffolding.md` | Repository scaffolding: Go module, layout, Makefile, LICENSE | 0 |
| 02 | `issues/02-ci-workflow.md` | CI workflow: build, lint, test, vulnerability and boundary checks | 0 |
| 04 | `issues/04-timeutil-package.md` | internal/timeutil: report range resolution | 0 |
| 03 | `issues/03-model-package.md` | internal/model: Event, Report, Warning types and ordering (after 04: uses `timeutil.Range`) | 0 |
| 05 | `issues/05-config-package.md` | internal/config: TOML schema, defaults, validation | 0 |
| 06 | `issues/06-sanitize-package.md` | internal/sanitize: control-char stripping and Markdown escaping | 0 |
| 07 | `issues/07-cli-skeleton.md` | internal/cli: subcommand dispatch, flags, exit codes | 0 |
| 08 | `issues/08-source-provider-collector.md` | internal/source: Provider interface and concurrent collector | 1 |
| 09 | `issues/09-zsh-history-parser.md` | zsh history parser: formats, unmetafy, multiline, caps | 1 |
| 10 | `issues/10-zsh-provider.md` | zsh source provider: resolution, filtering, warnings | 1 |
| 11 | `issues/11-git-repo-discovery.md` | git repo discovery: root walk, worktree dedup | 1 |
| 12 | `issues/12-git-commit-extraction.md` | git commit extraction: read-only log invocation and parsing | 1 |
| 13 | `issues/13-jsonl-scanner-util.md` | internal/source/jsonlutil: tolerant capped JSONL scanner | 1 |
| 14 | `issues/14-claude-code-provider.md` | Claude Code session provider | 1 |
| 15 | `issues/15-codex-provider.md` | Codex session provider | 1 |
| 16 | `issues/16-redaction-engine.md` | internal/redact: rule engine, allowlist, config integration | 2 |
| 17 | `issues/17-redaction-ruleset.md` | Built-in redaction ruleset and test corpus | 2 |
| 18 | `issues/18-aggregation.md` | internal/aggregate: sanitize/redact stage and Report assembly | 2 |
| 19 | `issues/19-standup-derivation.md` | Standup derivation: workday logic, buckets, blockers | 2 |
| 20 | `issues/20-render-daily.md` | Daily report renderer (ja/en) with golden tests | 3 |
| 21 | `issues/21-render-standup.md` | Standup report renderer (ja/en) with golden tests | 3 |
| 22 | `issues/22-output-writer.md` | internal/output: stdout/file writers with safe-write rules | 3 |
| 23 | `issues/23-llm-client.md` | internal/llm: loopback-enforced OpenAI-compatible client | 4 |
| 24 | `issues/24-llm-narrative.md` | LLM narrative: prompt builder, response handling, fallback | 4 |
| 25 | `issues/25-cmd-daily.md` | `worklog daily`: end-to-end wiring and integration tests | 5 |
| 26 | `issues/26-cmd-standup.md` | `worklog standup`: wiring and integration tests | 5 |
| 27 | `issues/27-cmd-init.md` | `worklog init`: starter config writer | 5 |
| 28 | `issues/28-cmd-doctor.md` | `worklog doctor`: environment and connectivity diagnostics | 5 |
| 29 | `issues/29-cmd-sources.md` | `worklog sources`: source listing and event counts | 5 |
| 30 | `issues/30-network-invariant-enforcement.md` | Network/read-only invariant enforcement suite | 6 |
| 31 | `issues/31-readme-and-user-docs.md` | README and user documentation (en + ja) | 6 |
| 32 | `issues/32-release-workflow.md` | Release workflow: cross-compile, checksums, tag automation | 6 |
| 33 | `issues/33-security-docs.md` | SECURITY.md and security model documentation | 6 |

## Dependency table

`A ← B` means B must be completed before A starts.

| Issue | Depends on |
|---|---|
| 01 | — |
| 02 | 01 |
| 03 | 01, 04 |
| 04 | 01 |
| 05 | 01 |
| 06 | 01 |
| 07 | 01, 05 |
| 08 | 03, 04, 05 |
| 09 | 01, 06 |
| 10 | 08, 09 |
| 11 | 01, 03 |
| 12 | 03, 04, 11 |
| 13 | 01 |
| 14 | 04, 08, 13 |
| 15 | 04, 08, 13 |
| 16 | 01, 05 |
| 17 | 16 |
| 18 | 03, 04, 06, 16, 17 |
| 19 | 04, 18 |
| 20 | 06, 18 |
| 21 | 06, 18, 19 |
| 22 | 01, 03 |
| 23 | 01, 05 |
| 24 | 05, 16, 17, 18, 23 |
| 25 | 07, 10, 12, 14, 15, 18, 20, 22, 24 |
| 26 | 19, 21, 25 |
| 27 | 05, 07, 22 |
| 28 | 07, 09, 11, 14, 15, 23 |
| 29 | 07, 08, 10, 12, 14, 15 |
| 30 | 02, 23, 25, 26, 27, 28, 29 |
| 31 | 25, 26, 27, 28, 29, 30, 32 |
| 32 | 01, 02 |
| 33 | 01, 30, 31 |

Within a wave, issues with disjoint dependencies can proceed in parallel
(e.g. 09/11/13 are independent; 16 can start any time after 05).

## Implementation waves

| Wave | Theme | Issues | Exit criterion |
|---|---|---|---|
| 0 | Foundation | 01–07 | `worklog version` builds and runs; CI green on empty-but-wired packages |
| 1 | Sources | 08–15 | Each provider returns correct Events from fixtures; read-only verified per source |
| 2 | Processing | 16–19 | Redacted, aggregated `Report` built from fixture events; standup buckets correct |
| 3 | Presentation | 20–22 | Golden Markdown (ja/en × daily/standup) renders; safe file writes proven |
| 4 | LLM | 23–24 | Narrative from `httptest` loopback fake; non-loopback blocked; fallback clean |
| 5 | Commands | 25–29 | All six subcommands work end-to-end against fixture HOME |
| 6 | Hardening & release | 30–33 | Invariant suite in CI; docs complete; tag produces release artifacts |

## Coverage: DESIGN.md sections → issues

| DESIGN.md section | Issues |
|---|---|
| §1.2 Invariants I1–I5 | cross-cutting AC in all issues; enforced by 02, 30 |
| §3.2 Module layout / §3.3 deps policy | 01, 02 |
| §4 CLI specification | 07, 25, 26, 27, 28, 29 |
| §5 Configuration | 05, 27 |
| §6.1 Provider boundary | 08 |
| §6.2–6.3 Data model | 03, 18 |
| §7.1 zsh | 09, 10 |
| §7.2 git | 11, 12 |
| §7.3 Claude Code | 13, 14 |
| §7.4 Codex | 13, 15 |
| §8 Redaction | 16, 17 |
| §9 Aggregation & standup | 18, 19 |
| §10 Output | 22 |
| §11 LLM | 23, 24 |
| §12 Security model | 06, 16, 17, 22, 23, 30, 33 (+ per-issue AC) |
| §13 Warning taxonomy | 03, 09–15, 23, 24 |
| §14 Testing strategy | 02, 25, 26, 30 (+ every issue's Validation) |
| §16 Naming/versioning/licensing | 01, 31, 32 (ADR-005) |

Every DESIGN.md behavior is owned by at least one issue; no v1 behavior lives
only in prose.

## Whole-product validation strategy

1. **Per-issue gates**: each issue's Validation section (unit tests, lint,
race detector) must pass in CI before the issue closes.
2. **Fixture realism**: parser fixtures are derived from the formats recorded
in `docs/research/*.md`; every observed hazard (metafied bytes, torn
lines, sidechains, missing session_meta, ANSI injection, fake secrets) has
a fixture line and an assertion.
3. **Integration goldens** (issues 25, 26): byte-exact CLI outputs for
ja/en × daily/standup × llm-on/off against a fixture HOME.
4. **Invariant suite** (issue 30): no-network (dialer attack tests, proxy
ignorance, import boundary script), read-only (fixture tree hash
before/after), write confinement (safe-write matrix).
5. **Release rehearsal** (issue 32): dry-run tag build produces all four
platform binaries + checksums; `go install` path verified.
6. **Docs acceptance** (issues 31, 33): README quickstart reproduced verbatim
on a clean macOS and Linux environment; SECURITY.md consistent with
DESIGN §12.

## Deferred v2 items

Weekly/monthly rollups; bash/fish histories; Gemini CLI and other agents;
subprocess plugin protocol for sources; template overrides; Obsidian
frontmatter mode; entropy-based redaction; holiday calendars; Homebrew tap;
artifact signing (Sigstore); `history.jsonl` lightweight prompt sources;
browser-search enrichment extension (explicitly network-labeled, opt-in);
LAN (non-loopback) LLM endpoints via a loud opt-in ADR; incremental state.

## Known unknowns (may create additional issues)

| # | Unknown | Likely follow-up |
|---|---|---|
| U1 | Claude/Codex schema drift across CLI versions | new fixtures + parser patch issues |
| U2 | `originator` adequacy for Codex automation filtering | heuristic/filter issue after real-world use |
| U3 | zsh EXTENDED_HISTORY off on many machines | `fc`-import or first-run guidance issue |
| U4 | Small-model narrative quality | prompt-tuning issue(s) post-v1 |
| U5 | Go toolchain absent on the reference machine | handled as issue 01 prerequisite; no code |
| U6 | Worktree/bare/submodule discovery edge cases | targeted fixture + fix issues |
| U7 | LM Studio auth quirks across versions | client compatibility patch issue |

## Conventions for implementers

- One issue = one PR = one working branch (`issue/NN-short-title`).
- Do not start an issue whose dependencies are not merged.
- Every PR must keep `go test ./...`, lint, and the CI invariant checks
green; issues 02/30 define those gates.
- If implementation reveals a conflict with DESIGN.md, stop and update
DESIGN.md first (docs are canonical), then continue.
39 changes: 39 additions & 0 deletions docs/decisions/ADR-001-go-and-dependency-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# ADR-001: Go with a stdlib-first dependency policy

Status: Accepted (user-approved language choice, 2026-07-10)

## Context

worklog is a security-sensitive local CLI for public OSS release: it parses
private files (shell history, agent logs), promises "no external network
I/O", and will be implemented largely by delegated lower-capability agents.
The user selected Go over Rust/TypeScript/Python.

## Decision

- Implementation language: **Go** (minimum toolchain 1.23).
- **Standard library first.** The only third-party module allowed in v1 is
`github.com/BurntSushi/toml` (config parsing).
- CLI flags: stdlib `flag` with per-subcommand FlagSets (no cobra).
Templates: `text/template`. HTTP: `net/http`. JSON: `encoding/json`.
- Any new dependency requires a new ADR stating why the stdlib cannot serve.

## Consequences

- Single static binary; easy audit of the no-network invariant (a tiny
dependency graph makes `grep`-level import auditing meaningful, enforced by
`scripts/check_net_imports.sh` in CI).
- Minimal npm/cargo-style supply-chain exposure; `govulncheck` + `go.sum`
pinning cover the remaining surface.
- Some conveniences (cobra help formatting, TUI-grade output) are given up;
acceptable for a report generator.
- TOML chosen over JSON/YAML for the config: comments + human editing, one
small dependency, no YAML parser complexity.

## Alternatives considered

- **Rust**: strongest guarantees, higher implementation/review cost for the
delegated-agent workflow.
- **TypeScript/Node**: fastest iteration, but runtime dependency and npm
supply-chain risk contradict the audit story.
- **Python**: distribution weight (pipx/venv) hurts OSS adoption for a CLI.
45 changes: 45 additions & 0 deletions docs/decisions/ADR-002-network-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# ADR-002: Zero external network; loopback-only LLM endpoint

Status: Accepted (user requirement + user-approved connectivity choice,
2026-07-10)

## Context

The user's core requirement: the tool must never communicate externally
("必ずローカルで外部通信しない"). The user also chose to include local-LLM
summarization in v1, connecting via an OpenAI-compatible API (Ollama,
LM Studio, llama.cpp server) — which is HTTP, i.e. technically "network".

## Decision

1. **Invariant I1**: the process performs no network I/O except HTTP(S) to a
**loopback** address for the optional LLM feature.
2. Loopback is enforced **at dial time, per resolved IP** (`net.IP.IsLoopback`
for every address the hostname resolves to), not by string-matching the
URL. A custom `http.Transport` sets `Proxy: nil` (proxy env vars ignored)
and denies all redirects.
3. Non-loopback endpoints are a **hard unsupported case in v1** — no config
escape hatch, no `--allow-remote` flag. Requests are refused with
`ErrNonLoopbackBlocked`; report generation falls back to deterministic
rendering.
4. Only `internal/llm` may import `net`/`net/http`; CI enforces the import
boundary (`scripts/check_net_imports.sh`).
5. No telemetry, no update checks, no crash reporting — ever (documented in
README + SECURITY.md).

## Consequences

- The privacy claim is testable and reviewable: unit tests attack the dialer
with public IPs, private-range IPs, and hostnames resolving to them; E2E
runs prove `HTTP_PROXY` is ignored.
- Users with LLMs on another machine (LAN) are not served in v1. Relaxing
this (e.g. explicit allowlist of private addresses) would be a new ADR and
a loud, opt-in flag — deliberately deferred.
- The "browser search enrichment" idea the user floated is confirmed as an
extension-level v2+ concept that must not weaken the v1 core invariant.

## Alternatives considered

- Subprocess llama.cpp execution (zero sockets): strongest isolation but poor
model management UX and heavy integration burden.
- Ollama-native API only: narrower compatibility for the same risk profile.
46 changes: 46 additions & 0 deletions docs/decisions/ADR-003-read-only-sources-and-stateless-runs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# ADR-003: Read-only source access and stateless runs

Status: Accepted (user requirement, 2026-07-10)

## Context

The user's second core requirement: source files are read, never written
("各種ファイルは読み取りしかしないこと"). Sources include live files (a
running shell appends to `~/.zsh_history`; agent CLIs append to session
JSONL; git repos are in active use). Naive implementations can violate
read-only in non-obvious ways: `git status` refreshes the index, opening
SQLite files creates `-wal`/`-shm` files, incremental scanners keep state.

## Decision

1. **Invariant I2**: all source access is read-only. Concretely:
- files opened `O_RDONLY`; no locks taken; torn tails tolerated;
- git invoked only with read-only subcommands (`log`, `rev-parse`) plus
`--no-optional-locks` and `GIT_OPTIONAL_LOCKS=0`; `git status` is
forbidden in the codebase;
- SQLite databases (e.g. `~/.codex/*.sqlite`) are **not opened at all**
(driverless read is unsafe; drivers create sidecar files);
- symlinked directories are not followed during discovery walks.
2. **Invariant I3 / stateless v1**: no cache, no state directory, no log
files. Each run scans fresh. Writes are limited to (a) the explicit output
target, (b) the config file written by `worklog init`.
3. Reporting uncommitted changes is a v1 non-goal because it would require
`git status`/`diff` against the worktree with index-refresh risk.

## Consequences

- A full run is verifiable: E2E test hashes the fixture HOME tree before and
after and asserts zero modifications.
- Re-scans cost more than incremental state would, but v1 data volumes
(one day of history/logs, date-sharded Codex dirs, mtime prefilters) keep
runs comfortably fast; incremental state is a v2 option with its own
security review.
- Some data is out of reach (SQLite-only Codex metadata); acceptable — JSONL
covers the needed facts.

## Alternatives considered

- go-git (pure-Go read) instead of the git binary: attractive (no exec) but a
large dependency contradicting ADR-001; revisit if exec proves brittle.
- State file for "since last report": deferred; contradicts stateless v1 and
adds a write surface.
41 changes: 41 additions & 0 deletions docs/decisions/ADR-004-redaction-default-on.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# ADR-004: Redaction on by default; agent logs contribute metadata first

Status: Accepted (user-approved, 2026-07-10)

## Context

Shell history and agent session logs routinely contain live credentials
(`export TOKEN=…`, `curl -H 'Authorization: …'`, pasted keys). worklog's
outputs are precisely the artifacts people paste into team channels (standup
reports), so leaked secrets would propagate. The user chose redaction
default-ON with configurable relaxation.

## Decision

1. **Invariant I4**: a built-in redaction ruleset (DESIGN.md §8) runs over
every event's title/body/meta **before aggregation**, and the LLM prompt
builder re-redacts its final payload (defense in depth: nothing unredacted
ever reaches the report model or the LLM).
2. Disabling requires explicit `--redact off` or `redaction.mode = "off"`,
prints a stderr warning banner, and is disclosed in the report footer.
3. Agent sessions are **metadata-first**: titles/counts/projects by default;
raw first-prompt text requires `include_prompts = true` per source, and is
still redacted.
4. Rules are RE2 with stable ids; users can extend (`extra_patterns`) and
exempt false positives (`allowlist`). Entropy-based detection is v2
(false-positive cost).

## Consequences

- Safe-by-default sharing; power users can relax per run or per pattern.
- False positives are possible (a masked non-secret) — mitigated by the
allowlist and by keeping variable *names* visible (only values masked).
- Redaction cannot be perfect; README must say so plainly (defense in depth,
not a guarantee), and the regression corpus grows with reported misses.

## Alternatives considered

- Off-by-default (raw fidelity): rejected — unsafe default for the primary
sharing use case.
- Always-strict (titles only, no relaxation): rejected — guts report utility
with no user recourse.
Loading