Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .claude/rules/kong-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This project uses Kong (`github.com/alecthomas/kong`) for the CLI surface. The p
## Dispatch with `Run(...) error`

- Every subcommand struct must implement a `Run(...) error` method. `main.go` dispatches via `ctx.Run(...)`.
- Do not add `switch ctx.Command()` branching in `main.go`. The README's "switch on the command string" pattern is fragile and explicitly avoided here.
- Do not add `switch ctx.Command()` branching in `main.go`. Kong's own README shows that "switch on the command string" pattern; it is fragile and explicitly avoided here.

## Cross-field validation goes in `Validate()`, not `Run()`

Expand All @@ -21,7 +21,7 @@ This project uses Kong (`github.com/alecthomas/kong`) for the CLI surface. The p

## File layout

- `cmd/cli.go` is reserved for struct definitions, Kong tags, and the top-level `CLI` type. Do not put `Run` or `Validate` method bodies here.
- `cmd/cli.go` is reserved for struct definitions, Kong tags, and the top-level `CLI` type. Do not put `Run` or `Validate` method bodies here; the one existing exception is the short `VersionCmd.Run`.
- Place `Run` and `Validate` for a subcommand in its domain file (e.g. `cmd/review.go` for `ReviewCmd`, `cmd/pr.go` for `PRCmd`, `cmd/cclocate.go` for `LocateCmd`).
- godoc on `Validate` must state what is checked, not just "validates the command". Example: `// Validate requires --output-path when --output=file.`

Expand Down
15 changes: 15 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"permissions": {
"allow": [
"Bash(go doc *)",
"Bash(go build ./...)",
"Bash(go list -m *)",
"Bash(go vet ./...)",
"Bash(go tool cover -func=coverage.out)"
],
"ask": [
"Bash(go doc *-http*)",
"Bash(go list *-mod*)"
]
}
}
2 changes: 1 addition & 1 deletion .claude/skills/upgrade-tools/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Upgrade all dev tools and sync versions across local and CI.
| `"github:golangci/golangci-lint"` | `.github/workflows/golangci-lint.yml` | `version: vX.Y.Z` in golangci-lint-action |
| `"github:goreleaser/goreleaser"` | `.github/workflows/go-releaser.yml` | `version: vX.Y.Z` in goreleaser-action |
| `"github:goreleaser/goreleaser"` | `.github/workflows/go-releaser-check.yml` | `version: vX.Y.Z` in goreleaser-action |
| `bun` | `.github/workflows/e2e.yml` | `bun-version: "X.Y.Z"` in setup-bun |
| `bun` | `.github/workflows/e2e.yml` | every `bun-version: "X.Y.Z"` in setup-bun (one per job) |

4. Run `mise run ci` to verify everything passes

Expand Down
33 changes: 11 additions & 22 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

commd is a Go CLI tool for reviewing Markdown files in an interactive TUI or a web browser. It parses Markdown files, displays them in a 2-pane interface, and supports inline commenting with feedback integration via Claude Code hooks.
commd is a Go CLI tool for reviewing Markdown files in an interactive TUI or a web browser. It parses Markdown files, displays them in a 2-pane interface, and supports inline commenting. Its Claude Code hook integration (`cchook` / `cclocate`) is deprecated.

## Absolute Rule

Expand All @@ -14,7 +14,7 @@ commd is a Go CLI tool for reviewing Markdown files in an interactive TUI or a w

## Build & Test Commands

Dev tools (Go, golangci-lint, tparse, gofumpt, octocov, goreleaser, bun) are managed by mise (`.mise.toml`). Run `mise install` to set up the toolchain.
Dev tools (Go, golangci-lint, tparse, gofumpt, octocov, goreleaser, bun, vhs) are managed by mise (`.mise.toml`). Run `mise install` to set up the toolchain.

```bash
mise run ci # Run full CI pipeline (MUST pass)
Expand All @@ -29,6 +29,8 @@ mise run fix # Run go fix (modernize)
mise run tidy # Run go mod tidy -v
mise run vuln # Run go mod verify + govulncheck
mise run install-skills # Install tuistory Claude Code skill
mise run demo # Record the README demo GIF (vhs/demo.gif; needs ttyd + ffmpeg)
mise run demo-validate # Validate vhs/demo.tape syntax without recording
go test -v ./internal/markdown # Run tests for a specific package
go test -run TestParsePreamble ./internal/markdown # Run a single test
```
Expand All @@ -41,7 +43,7 @@ Linter config: `.golangci.yml` (enabled: asciicheck, gocritic, misspell, nolintl

### Entry Point & CLI

`main.go` → `cmd/cli.go`: Kong struct-based CLI with 5 subcommands (review, pr, cclocate, cchook, version); `cclocate` and `cchook` are deprecated (hidden from `--help`, kept working until removal). Cross-field constraints are enforced via `Validate()` methods on each command struct (called by Kong post-parse). The GitHub client is wired with `kong.BindToProvider(ghclient.NewClient)` and lazily injected only into commands that take `*ghclient.Client` in their `Run()` signature (currently `PRCmd`).
`main.go` → `cmd/cli.go`: Kong struct-based CLI with 5 subcommands (review, pr, cclocate, cchook, version); `cclocate` and `cchook` are deprecated (hidden from `--help`, kept working until removal). Cross-field constraints are enforced via a `Validate()` method on the command struct that has them (called by Kong post-parse). The GitHub client is wired with `kong.BindToProvider(ghclient.NewClient)` and lazily injected only into commands that take `*ghclient.Client` in their `Run()` signature (currently `PRCmd`).

### Package Layout

Expand All @@ -66,25 +68,20 @@ Linter config: `.golangci.yml` (enabled: asciicheck, gocritic, misspell, nolintl

**PR Review**: `cmd/pr.go` → `PRCmd.Validate()` (parses URL) → `Run(client)` (client injected by Kong via `BindToProvider`) → `github.ListMDFiles()` → file picker (multi-select) → for each file: `github.FetchFileContent()` → `markdown.Parse()` → `tui.NewApp()` → `github.BuildPRReview()` → `github.SubmitReview()`

**Hook**: Claude Code (PreToolUse: ExitPlanMode, or PostToolUse: Write|Edit) → stdin JSON → `cchook.Run()` → `pane.SpawnAndWait(commd review ...)` → temp file IPC → exit 0 or 2
**Hook** (deprecated): Claude Code (PreToolUse: ExitPlanMode, or PostToolUse: Write|Edit) → stdin JSON → `cchook.Run()` → `pane.SpawnAndWait(commd review ...)` → temp file IPC → exit 0 or 2

## Development Rules

- Use `go doc` to look up library APIs and usage (more accurate than web searches)
- Do not use Python for complex scripting. Use shell scripts instead
- Edit files with the Edit tool, not `perl` / `sed` in-place substitution — a wrong delimiter or escape silently corrupts the file
- Do not use `/tmp`. Keep temporary files within the project directory

## Implementation Guidelines

### Code

- Follow Go idioms and best practices
- Keep design style consistent across subcommands (Kong struct tags, error handling patterns, etc.)
- Follow the DRY principle and eliminate duplicate code
- Keep package and function responsibilities single-purpose. Do not mix multiple responsibilities
- Keep functions unexported (lowercase) unless they are referenced externally
- Use function and variable names that clearly represent their responsibilities
- Add comments for code or logic that is not immediately obvious

### Tests

Expand All @@ -95,19 +92,18 @@ Linter config: `.golangci.yml` (enabled: asciicheck, gocritic, misspell, nolintl

### Documentation

- Keep the README clear and concise for users
- Update the README when code specifications change

## Key Design Decisions

- **goldmark AST walk** for Markdown parsing instead of regex to correctly handle `#` inside code fences
- **Mermaid → ASCII rendering** via `mermaid-ascii` library; fenced `\`\`\`mermaid` blocks are converted to ASCII art in the TUI detail pane and web view (`internal/mermaid`), with fallback to raw source on error
- **Content-hash based viewed tracking** (`state.go`): sections are tracked by title → SHA-256 hash of title+body; if content changes, the viewed mark auto-clears
- **PreToolUse (ExitPlanMode) hook** as the recommended trigger: it fires exactly once per completed plan, before Claude Code's own approval dialog, and exit 2 denies the tool with the review as the reason so Claude revises in plan mode. **PostToolUse (Write|Edit)** remains as an alternative that fires on every plan write. A Stop hook is not an option because Stop doesn't fire during plan mode
- **PreToolUse (ExitPlanMode) hook** (deprecated `cchook`) was the preferred trigger: it fires exactly once per completed plan, before Claude Code's own approval dialog, and exit 2 denies the tool with the review as the reason so Claude revises in plan mode. **PostToolUse (Write|Edit)** was the alternative, firing on every plan write. A Stop hook was not an option because Stop doesn't fire during plan mode
- **Line comments carry a source quote** (`ReviewComment.Quote`) captured at save time; the output quotes it so Claude can locate the target after line numbers shift, and removed diff lines (which no longer exist in the file) stay readable
- **Temp files for IPC** between hook process and review subprocess (which runs in a separate pane)
- **Temp files for IPC** (deprecated `cchook`) between hook process and review subprocess (which runs in a separate pane)
- **Web review is a second front end over the same model**, not a TUI-in-a-browser (no terminal emulation), but it must behave like the TUI — same keys, modes, dialogs and flow; mouse support is additive. The one deliberate difference: no pane focus (vertical keys move the content and step between sections at its edges; the section list is 20% wide by default). The server owns all state (the page only renders JSON and calls the API) and returns the same `ReviewResult`, so output formatting stays in `markdown`. It binds to 127.0.0.1 only and requires the per-session token on the API, which also blocks cross-origin requests (custom header ⇒ preflight, never approved) and DNS rebinding. Vanilla JS with no build step keeps `go build` self-contained
- **Hook always exits 0 on error** — never breaks the Claude Code workflow; uses `CC_PLAN_REVIEW_SKIP=1` env var to disable
- **Hook always exits 0 on error** (deprecated `cchook`) — never breaks the Claude Code workflow; uses `CC_PLAN_REVIEW_SKIP=1` env var to disable

## Testing Patterns

Expand All @@ -119,11 +115,4 @@ Linter config: `.golangci.yml` (enabled: asciicheck, gocritic, misspell, nolintl

### E2E Tests

E2E tests in `e2e/` use tuistory (terminal UI automation) with bun:test:

- `launchCommd()` helper spawns the built binary in a virtual terminal and waits for Bubble Tea to render
- Each test runs with a fixed terminal size (120×36 by default)
- Snapshot tests (`snapshot.test.ts`) capture full screen state for layout regression detection
- Fixtures reuse `internal/markdown/testdata/basic.md`
- `addComment()` shared helper for comment creation sequences
- `review --web` is tested at two levels: `web.test.ts` drives the JSON API over HTTP, and `web-ui*.test.ts` drive the page in headless Chromium via Playwright (the `playwright` library under bun:test, not the Playwright test runner). Like the TUI tests, the critical paths (`web-ui.test.ts`) are in the basic tier and the rest (`web-ui-keys` / `web-ui-modes` / `web-ui-edge`) only in the full suite, which aims to cover every key binding and mode. Helpers: `launchWeb()` (`helpers/web.ts`), `useBrowser()` / `openPage()` / `eventually()` (`helpers/browser.ts`; wrap assertions after server-bound keys in `eventually()` since the page updates asynchronously). `mise run e2e-cov` collects V8 coverage of the page scripts (`COMMD_JS_COVERAGE=1`) and prints it per file (`bun scripts/js-coverage.ts --uncovered` lists missed lines). The e2e tasks run `bunx playwright install chromium` first
See `e2e/CLAUDE.md` (loaded when working under `e2e/`).
10 changes: 10 additions & 0 deletions e2e/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# E2E Tests

E2E tests in `e2e/` use tuistory (terminal UI automation) with bun:test:

- `launchCommd()` helper spawns the built binary in a virtual terminal and waits for Bubble Tea to render
- Each test runs with a fixed terminal size (120×36 by default)
- Snapshot tests (`snapshot.test.ts`) capture full screen state for layout regression detection
- Fixtures reuse `internal/markdown/testdata/basic.md`
- `addComment()` shared helper for comment creation sequences
- `review --web` is tested at two levels: `web.test.ts` drives the JSON API over HTTP, and `web-ui*.test.ts` drive the page in headless Chromium via Playwright (the `playwright` library under bun:test, not the Playwright test runner). Like the TUI tests, the critical paths (`web-ui.test.ts`) are in the basic tier and the rest (`web-ui-keys` / `web-ui-modes` / `web-ui-edge`) only in the full suite, which aims to cover every key binding and mode. Helpers: `launchWeb()` (`helpers/web.ts`), `useBrowser()` / `openPage()` / `eventually()` (`helpers/browser.ts`; wrap assertions after server-bound keys in `eventually()` since the page updates asynchronously). `mise run e2e-cov` collects V8 coverage of the page scripts (`COMMD_JS_COVERAGE=1`) and prints it per file (`bun scripts/js-coverage.ts --uncovered` lists missed lines). The e2e tasks run `bunx playwright install chromium` first
Loading