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
25 changes: 25 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2

[*.go]
indent_style = tab

[*.{pkl,ts,vue}]
indent_size = 2

[*.md]
indent_size = 2
trim_trailing_whitespace = false

[Taskfile.yml]
indent_size = 2

[Makefile]
indent_style = tab
24 changes: 24 additions & 0 deletions .githooks/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/sh
set -eu

msg_file="$1"
subject="$(sed -n '1p' "$msg_file")"

case "$subject" in
Merge\ *|Revert\ *|fixup!\ *|squash!\ *)
exit 0
;;
esac

line_count="$(grep -v '^[[:space:]]*#' "$msg_file" | sed '/^[[:space:]]*$/d' | wc -l | tr -d ' ')"
if [ "$line_count" -ne 1 ]; then
echo "commit-msg: use exactly one non-empty line" >&2
exit 1
fi

pattern='^(feat|fix|chore|refactor|test|docs|perf|build)\((app|api|auth|carport|cli|config|daemon|driverkit|drivers|eventstore|policy|proto|repo|ci|docs|agents)\): [a-z0-9][a-z0-9 ,'\''`._:/()+-]*[^.]$'
if ! printf '%s\n' "$subject" | grep -Eq "$pattern"; then
echo "commit-msg: expected '<prefix>(<scope>): <lowercase imperative subject>'" >&2
echo "commit-msg: example: feat(config): add semantic apply messages" >&2
exit 1
fi
37 changes: 37 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/sh
set -eu

cd "$(git rev-parse --show-toplevel)"

staged="$(git diff --cached --name-only --diff-filter=ACMR)"

if printf '%s\n' "$staged" | grep -Eq '\.go$'; then
echo "pre-commit: checking Go formatting"
task fmt-check

if command -v golangci-lint >/dev/null 2>&1; then
echo "pre-commit: running Go lint"
task lint
else
echo "pre-commit: golangci-lint not found; CI will lint"
fi
fi

if printf '%s\n' "$staged" | grep -Eq '\.proto$'; then
if command -v buf >/dev/null 2>&1; then
echo "pre-commit: checking generated proto"
PATH="$PATH:$(go env GOPATH)/bin" task proto
git diff --exit-code gen/
else
echo "pre-commit: buf not found; CI will verify proto generation"
fi
fi

if printf '%s\n' "$staged" | grep -Eq '\.pkl$'; then
if command -v pkl >/dev/null 2>&1; then
echo "pre-commit: checking Pkl config evaluation"
go test -tags=integration -run TestEvaluator_ValidConfig ./internal/config/
else
echo "pre-commit: pkl not found; CI will run integration coverage"
fi
fi
18 changes: 18 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/sh
set -eu

cd "$(git rev-parse --show-toplevel)"

echo "pre-push: unit tests"
task test

echo "pre-push: race tests"
task test:race

echo "pre-push: integration tests"
if command -v pkl >/dev/null 2>&1; then
task test:integration
else
echo "pre-push: pkl not found; skipping config integration package"
go test -tags=integration $(go list -tags=integration ./... | grep -v 'internal/config')
fi
10 changes: 6 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Go build artifacts
dist/
bin/
/gohomed
/gohome
/switchyardd
/switchyard
*.exe
*.test
*.out
Expand All @@ -14,8 +14,8 @@ var/
*.db
*.db-shm
*.db-wal
/gohomed.lock
/gohomed.sock
/switchyardd.lock
/switchyardd.sock

# Embedded web asset build outputs; keep the placeholder committed.
!internal/web/dist/
Expand All @@ -24,6 +24,8 @@ internal/web/dist/*

# Docs build output
docs/site/
docs/.cache/
docs/.venv/

# Editor
.idea/
Expand Down
126 changes: 126 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Working in Switchyard

This is the canonical repo-onboarding doc. `CLAUDE.md` is a symlink to it, so
tools that look for `AGENTS.md` and tools that look for `CLAUDE.md` read the
same content.

If you are a human, this doc still applies.

## Setup

First run `task setup`. Skipping this means your commits will likely be
rejected. This activates the mandatory Git hooks via `core.hooksPath`.

A clean clone also needs Go 1.25+, Node.js 20+, Task, Buf, Pkl, and
golangci-lint. Standard commands:

| Action | Command |
|--------------|-------------------------|
| Setup | `task setup` |
| Build | `task build` |
| Test | `task test` |
| Race test | `task test:race` |
| Integration | `task test:integration` |
| Format | `task fmt` |
| Format check | `task fmt-check` |
| Lint | `task lint` |
| Check | `task check` |
| Full check | `task check:full` |

Run `task check` before claiming broad repo work is done. Run
`task check:full` when touching runtime concurrency, integration surfaces, or
release-critical paths. For narrow changes, run the smallest relevant subset
and say exactly what passed.

## Repo Layout

```
Switchyard/
|-- AGENTS.md # this file (CLAUDE.md is a symlink to it)
|-- Taskfile.yml # setup, build, test, fmt, lint, check
|-- app/ # Vue admin UI
|-- cmd/ # switchyardd, switchyard, and test binaries
|-- docs/ # documentation site and long-lived notes
|-- docs/agents/ # agent-authored working docs
| |-- specs/ # design specs: what and why
| `-- plans/ # implementation plans: how
|-- docs/adrs/ # architecture decision records
|-- drivers/ # first-party out-of-process drivers
|-- examples/ # sample config files and config fragments
|-- gen/ # committed generated protobuf code
|-- internal/ # main daemon and CLI internals
|-- proto/ # protobuf API and event schemas
|-- switchyard-driverkit/ # public Go driver SDK module
`-- testdata/ # golden fixtures and integration data
```

Rules:

- Product docs and long-lived architecture notes go in `docs/`.
- Agent-authored specs and plans go in `docs/agents/`.
- Cross-cutting durable decisions go in `docs/adrs/`.
- The root Go module stays at `.`. Do not move daemon code under a new top-level
module directory.
- Do not add a new top-level directory without first updating this layout and
explaining why the existing boundaries do not fit.

## Code Conventions

- **Go:** use Go 1.25+. Keep package APIs explicit and small. Prefer standard
library types unless an existing local abstraction already owns the invariant.
- **Errors:** do not silence errors. Return explicit errors with useful context.
Use sentinel or typed errors when callers need to branch. Do not string-match
errors unless the dependency leaves no better option, and document that case.
- **Logging:** use structured logging. Log and return/propagate; logging is not
a substitute for handling an error.
- **Config:** Pkl is the source config language. Generated config snapshots are
protobuf artifacts. Config validation errors should include a stable code
where practical, plus file/line/field context when known.
- **Proto:** preserve field numbers, reserve removed fields, and run
`task proto` after schema changes. Generated code under `gen/` is committed.
- **Frontend:** keep UI code in `app/`. Use `task app:typecheck`,
`task app:test`, and `task app:build` for app-only verification.
- **Comments:** explain constraints and surprising decisions. Do not narrate
obvious control flow.

## Semantic Messages

Commit messages are semantic, one line maximum. No body. No trailers.

```
feat(config): add semantic apply messages
fix(app): preserve scene filter on reload
chore(ci): cache app dependencies
docs(agents): document repo workflow
```

- **Allowed prefixes:** `feat`, `fix`, `chore`, `refactor`, `test`, `docs`,
`perf`, `build`.
- **Allowed scopes:** `app`, `api`, `auth`, `carport`, `cli`, `config`,
`daemon`, `driverkit`, `drivers`, `eventstore`, `policy`, `proto`, `repo`,
`ci`, `docs`, `agents`.
- **Subject:** imperative, lowercase, no trailing period.

Config apply messages use the same one-line shape and the `config` prefix:

```
config(scene): add evening kitchen scene
config(area): move office devices
config(driver): rotate hue bridge credentials
```

Allowed config scopes are `area`, `automation`, `driver`, `entity`, `page`,
`policy`, `scene`, `script`, `auth`, `mcp`, and `repo`.

Never include `Co-Authored-By:` trailers, generated-by footers, tool
watermarks, or agent attribution.

## Workflow Expectations

1. Read the issue or local spec first.
2. Write or update a spec under `docs/agents/specs/` before non-trivial design
work.
3. Write or update a plan under `docs/agents/plans/` for multi-step execution.
4. Keep patches surgical. Do not refactor unrelated code.
5. Verify locally before claiming done. Treat local failures the same way CI
will.
21 changes: 0 additions & 21 deletions CLAUDE.md

This file was deleted.

1 change: 1 addition & 0 deletions CLAUDE.md
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,17 @@ Both modules are linked by a Go workspace (`go.work`), so `go build ./...` and `
- [Pkl](https://pkl-lang.org) — `brew install pkl` (config schema)
- Node.js 20+ — for the web UI

## Setup

```bash
task setup # activates repo Git hooks
```

## Building

```bash
task build # builds switchyardd + switchyard binaries into dist/
task web:build # builds the web UI (required before task build)
task app:build # builds the web UI
```

## Testing
Expand All @@ -37,6 +43,8 @@ task web:build # builds the web UI (required before task build)
task test # unit tests
task test:race # race detector
task test:integration # integration tests (real disk I/O)
task check # standard local verification suite
task check:full # standard checks plus race and integration tests
```

## Drivers
Expand All @@ -50,7 +58,10 @@ go build ./...

## Documentation

Full documentation lives in [`docs/`](./docs) and is published via Zensical. Design specs and implementation plans live in [`docs/design/`](./docs/design).
Full documentation lives in [`docs/`](./docs) and is published via Zensical.
Agent-authored specs and implementation plans live in
[`docs/agents/`](./docs/agents). Cross-cutting decisions live in
[`docs/adrs/`](./docs/adrs).

## License

Expand Down
Loading
Loading