Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a2f094a
docs(01): synthesize phase context
dezween Jul 11, 2026
3c4a077
docs(01): add research and validation strategy
dezween Jul 11, 2026
c47e056
docs(01): lock test stack (testify/mockgen) and context usage
dezween Jul 11, 2026
d0fe850
docs(01): add phase plan
dezween Jul 11, 2026
628acc6
feat(domain): add pure domain package with Interval and Offset
dezween Jul 11, 2026
0985f67
feat(port): define Pointer and Keeper ports with mockgen directive
dezween Jul 11, 2026
8445690
test(port): add go.uber.org/mock dep and generate Pointer mock
dezween Jul 11, 2026
35e1bfe
feat(service): implement KeeperService with injected slog logger
dezween Jul 11, 2026
61dd982
feat(adapter): relocate cgo CoreGraphics adapter behind darwin build tag
dezween Jul 11, 2026
b33f14c
feat(cmd): add cmd/sysmon entry point and remove root main.go
dezween Jul 11, 2026
547ae3f
build(makefile): point build target at ./cmd/sysmon and pass full gate
dezween Jul 11, 2026
9c9d7f1
docs(01-hexagonal-refactor): add plan 01 execution summary
dezween Jul 11, 2026
fef8034
fix(01): enforce domain validation, typed keeper ctor, interruptible …
dezween Jul 11, 2026
eee9e09
test(01): cover cancel-during-pause path; clarify nudgeCycle docs
dezween Jul 11, 2026
960aabf
harden(01): surface nudge failures and accessibility preflight
dezween Jul 11, 2026
22f0ed1
docs(01): correct -quiet flag help to match level-raise behavior
dezween Jul 11, 2026
ee1a9d6
test(01): comprehensive coverage and edge cases
dezween Jul 11, 2026
0705407
chore: clear stale auto-chain flag
dezween Jul 11, 2026
f0921a9
build(makefile): add INTERVAL variable for run/start targets
dezween Jul 11, 2026
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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# built binary
/sysmon
sysmon

# macOS
.DS_Store
Expand Down
2 changes: 2 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ formatters:
goimports:
local-prefixes:
- sysmon
gofumpt:
module-path: sysmon

linters:
enable:
Expand Down
3 changes: 2 additions & 1 deletion .planning/PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ nearly free in resource use.
## Constraints

- **Tech stack**: Go 1.26 + cgo, macOS only (arm64) — uses CoreGraphics/ApplicationServices C API.
- **Dependencies**: standard library only — new third-party deps require justification.
- **Dependencies**: runtime/production is standard-library only (zero third-party in the binary — reason `log/slog` was chosen). Test-only deps are allowed: `testify` (assertions), `go.uber.org/mock`/mockgen (mocks from interfaces), `testcontainers` (integration tests only).
- **Platform**: requires Xcode Command Line Tools and macOS Accessibility permission.
- **Resource use**: must stay lightweight — near-zero idle CPU, tiny memory footprint, battery-friendly (runs for hours in background).

Expand All @@ -71,6 +71,7 @@ nearly free in resource use.
| Structured logging via stdlib log/slog | Levels/structure without breaking the zero-dep constraint (vs zerolog/zap) | — Pending |
| CI on macos-latest (build/lint/test) | cgo + CoreGraphics only compiles on macOS | ✓ Good |
| golangci-lint v2 (strict golden config, trimmed) | Consistent strict linting, minus project-specific/web linters | ✓ Good |
| Test stack: testify + mockgen (from interfaces) + testcontainers | Standard Go test tooling; mocks generated, not hand-written (test-only deps, binary stays zero-dep) | — Pending |

## Evolution

Expand Down
5 changes: 3 additions & 2 deletions .planning/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ Milestone: v1 — Hexagonal Refactor
3. `KeeperService` (in `service`) depends only on `domain` and `port` types, never imports `adapter`, and runs the tick loop via the `Keeper`/`Pointer` interfaces
4. `CGPointer` adapter implements `Pointer` using cgo/CoreGraphics and is isolated behind a `//go:build darwin` tag
5. `go build` succeeds, all Makefile targets (build/run/start/stop/status/clean) work unchanged, and `go test ./...` passes a `KeeperService` unit test using a fake `Pointer` that verifies nudges fire on the interval without moving the real mouse and without requiring macOS Accessibility permission
**Plans**: TBD
**Plans**: 1 plan
- [ ] 01-01-PLAN.md — Restructure flat main.go into cmd/sysmon + internal/activity {domain,port,service,adapter}, add mockgen+testify+synctest unit test, inject slog logging, update Makefile, remove root main.go; behavior preserved

## Progress

| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 1. Hexagonal Refactor | 0/? | Not started | - |
| 1. Hexagonal Refactor | 0/1 | Not started | - |
3 changes: 2 additions & 1 deletion .planning/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"post_planning_gaps": true,
"security_enforcement": true,
"security_asvs_level": 1,
"security_block_on": "high"
"security_block_on": "high",
"_auto_chain_active": false
},
"ship": {
"pr_body_sections": []
Expand Down
391 changes: 391 additions & 0 deletions .planning/phases/01-hexagonal-refactor/01-01-PLAN.md

Large diffs are not rendered by default.

263 changes: 263 additions & 0 deletions .planning/phases/01-hexagonal-refactor/01-01-SUMMARY.md

Large diffs are not rendered by default.

96 changes: 96 additions & 0 deletions .planning/phases/01-hexagonal-refactor/01-CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Phase 1 — Hexagonal Refactor — CONTEXT

**Source:** Synthesized from `docs/TASK.md` §10, PROJECT.md Key Decisions, and the
`log/slog` decision (design already locked earlier; interactive discuss-phase skipped
as redundant).

## Phase Boundary

Restructure the existing, working flat `main.go` into a hexagonal (ports &
adapters) layout, add a unit test, and wire in structured logging — **without
changing observable runtime behavior**. This is a pure refactor + tests +
logging. No new features.

In scope: ARCH-01..05, TEST-01, BUILD-01, BUILD-02, LOG-01.
Out of scope: autostart/LaunchAgent, working-hours, key-press mode, non-macOS
adapters (all v2/deferred).

## Implementation Decisions (locked)

### Structure
- Layout exactly per `docs/TASK.md` §10:
- `cmd/sysmon/main.go` — entry point: flag parsing (`-interval`, `-quiet`),
dependency wiring (manual DI), signal handling, startup. No business logic.
- `internal/activity/domain/` — pure Go value objects + rules (e.g. an
`Interval` that validates `> 0`, an `Offset`). Imports nothing from
`port`/`service`/`adapter`; no cgo/os/time-from-outside.
- `internal/activity/port/` — interfaces: driven port `Pointer` with
`Nudge(dx, dy int) error`; driving port `Keeper` with `Run(ctx) error`.
- `internal/activity/service/` — `KeeperService` implements `Keeper`: ticker
loop, on each tick `Pointer.Nudge(+1,0)` → pause → `Nudge(-1,0)`; stops
cleanly on `ctx` cancellation. Depends only on `domain` + `port`.
- `internal/activity/adapter/` — `CGPointer` implements `Pointer` via
cgo/CoreGraphics (`CGEventCreateMouseEvent`/`CGEventPost`), behind
`//go:build darwin`. All CoreFoundation objects released with `CFRelease`.
- Remove the flat `main.go` from the repo root.
- Module path stays `sysmon`; internal import paths are `sysmon/internal/activity/...`.

### Behavior preservation (must not change)
- Flags `-interval` (default 10s) and `-quiet`; real `kCGEventMouseMoved` event
via `CGEventPost(kCGHIDEventTap, …)`; 1px nudge there-and-back with the ~40ms
pause; clean exit on SIGINT/SIGTERM (code 0); `time.Ticker` sleep between ticks
(near-zero idle CPU — no busy-wait).
- Makefile targets (build/run/start/stop/status/clean) keep working unchanged.

### Logging (LOG-01)
- Use stdlib `log/slog`. Inject a `*slog.Logger` into `KeeperService` (constructor
param) — no package-global logger, so tests stay clean.
- `-quiet` raises the level threshold (e.g. warn+ only) rather than redirecting
output. Lifecycle events (start with interval, stop) logged with structure.
- Zero third-party logger — this is the whole reason slog was chosen.

### Testing (TEST-01) — locked test stack
- **Assertions: `testify`** (`github.com/stretchr/testify`) only.
- **Mocks: `mockgen`** (`go.uber.org/mock`) generated **from the `Pointer`
interface** — NOT a hand-written fake (this overrides the research's
hand-written-fake suggestion, per user convention). Use a `//go:generate
mockgen ...` directive and commit the generated mock (e.g. a `mocks` package
or `port/mock_pointer.go`).
- **Deterministic time: `testing/synctest`** (stdlib, Go 1.25+) to drive the
ticker without real-time flakiness. Composes with testify+mockgen: synctest
controls the clock, the mock records `Nudge` calls, testify asserts.
- Test asserts nudges occur per tick and the loop stops on context cancel. It
must NOT move the real mouse and must NOT require macOS Accessibility — it
never touches the `CGPointer` adapter.
- **`testcontainers`** is the convention for integration tests, but sysmon has
none — do NOT add it as a dependency.
- `go test -race ./...` must pass (CI runs it).

### Dependency policy clarification
- The runtime/production binary stays **stdlib-only** (zero third-party) — this
is why `log/slog` was chosen.
- **Test-only** deps are allowed and expected: `testify`, `go.uber.org/mock`
(mockgen). They live in `go.mod` but are not linked into the binary.

## Claude's Discretion
- Exact domain type shape (`Interval`/`Offset` naming, whether Offset is a
domain type or a constant), constructor signatures, and how the tick source is
made testable (injected clock/ticker vs short interval) — pick the cleanest
idiomatic Go. Keep it small; do not over-engineer a tiny utility.
- Whether `Keeper.Run` takes the interval/offset via constructor or method.

## Canonical References
- `docs/TASK.md` §10 — the authoritative architecture spec.
- `.planning/codebase/ARCHITECTURE.md` — current→target mapping.
- Current `main.go` — the behavior to preserve (cgo nudge, flags, signals, loop).

## Specific Ideas
- Keep the cgo C `nudge` helper essentially as-is, just relocated into the
`CGPointer` adapter with the `//go:build darwin` tag.
- `cmd/sysmon/main.go` builds a `slog.Logger`, constructs `CGPointer`, injects
both into `KeeperService`, wires SIGINT/SIGTERM to a `context.CancelFunc`, and
calls `keeper.Run(ctx)`.

## Deferred Ideas
- LaunchAgent autostart, working-hours window, key-press mode, Linux/Windows
adapters — all out of scope for this phase (tracked in REQUIREMENTS.md v2).
Loading
Loading