--- /dev/null
+---
+name: check
+description: Format, lint, test, and build. Use before every commit.
+---
+
+# Check
+
+Run every command in the Nix development shell. Use
+`nix develop -c COMMAND` when direnv does not load the shell.
+
+## Procedure
+
+1. Format all files with `bin/format`.
+2. Run the checks with `bin/check`. Its golangci-lint config keeps the
+ standard linters and adds more linters, such as `errorlint`,
+ `gocritic`, `testifylint`, `revive`, `wrapcheck`, `nlreturn`, and
+ `wsl_v5`. The settings in `.golangci.yml` match the Go rules.
+3. Run `git add` on new files, then run `nix build` and
+ `nix flake check`. The package is Linux-only, so run both on Linux
+ before a change to Go or Nix code is complete.
+4. Fix every failure and run the steps again. Do not disable a linter or
+ add a `nolint` directive to get a pass, unless the user agrees.
+5. Review the final diff against the rules. Passing tools do not replace
+ this review.
+ - Read `AGENTS.md` and the `SKILL.md` of each skill of the task again
+ from disk now. Do not work from memory of an earlier read.
+ - Go through the diff one rule at a time, and for each rule look at
+ every changed line that it covers. For Go code, check each section
+ of `go-code`, also for the tests.
+ - Fix each finding, then run steps 1 to 4 again.
+
+Tell the user about each check that cannot run and each failure that
+stays.
--- /dev/null
+---
+name: commit
+description: Commit procedure and message rules. Use for every commit.
+---
+
+# Commit
+
+## Procedure
+
+1. Do the `check` skill. All checks must pass before you commit.
+2. Stage only the files that belong to this change. Keep unrelated
+ changes in separate commits.
+3. Write the message to a file or a here-document and commit with
+ `git commit -F`. Never put `\n` in `git commit -m`.
+4. When `.githooks/commit-msg` rejects the message, fix the message and
+ commit again. Do not skip the hook with `--no-verify`.
+
+## Size
+
+- Commit often. Split the work into small, coherent, working steps, and
+ do not wait for a full feature.
+- Before a push, rebase on the latest `master` and fix conflicts during
+ the rebase. Never make a merge commit.
+
+## Subject
+
+- Use 50 characters or fewer.
+- Write a short summary in the present-tense imperative mood.
+- Capitalize the first word. Do not end with a period.
+- Do not use a subsystem or prefix.
+
+## Body
+
+- Separate the subject from the body with exactly one blank line.
+- Always write a body. Wrap each line at 72 characters. Fill each line
+ of a paragraph before you wrap it.
+- Write in the present-tense declarative mood.
+- Start with a direct summary of the complete change. Do not only repeat
+ the subject.
+- Explain what the change does and why it is necessary. Describe the
+ resulting behavior and design.
+- Do not describe the development process, list the changed files, or
+ give obvious implementation steps.
+
+## Trailers
+
+- When the change belongs to an issue, end the message with one `Issue:`
+ trailer with the git-bug ID of that issue. Add it with
+ `git commit --trailer`. Never use a closing keyword such as "Fixes" or
+ "Closes".
+- Include a stable archive URL when a change comes from a mailing-list
+ or forum discussion.
+- Do not add an AI tool as co-author. Do not add attribution trailers.
+
+## Example
+
+```
+Print the program name
+
+OneHouse prints its name and exits. The program gives the Nix package a
+binary that the install check can run, so the build pipeline is tested
+before the first feature.
+```
--- /dev/null
+---
+name: docs
+description: Docs and comment rules. Use before you write any prose.
+---
+
+# Documentation and comments
+
+Change documentation and comments in the same change as the behavior,
+interface, or constraint that they describe.
+
+## Documentation
+
+- `README.rst` is the entry point. It says what the project is and how
+ to build, run, and check it. Keep it short.
+- `doc/` holds one document for each subject: a contract, an
+ integration, or a device. Add to the document of the subject before
+ you add a new document.
+- Use reStructuredText. Use Markdown only for files that a tool requires
+ by name, such as `AGENTS.md` and `SKILL.md`.
+- Follow
+ [Google's timeless documentation guide](https://developers.google.com/style/timeless-documentation).
+ Describe what the checked-in code does. Do not use labels that depend
+ on time, such as "new", "now", "currently", and "previously".
+- Follow
+ [Google's tense guide](https://developers.google.com/style/tense). Use
+ the present tense for general behavior. Use the future tense only when
+ the timing of an action requires it, not for planned features.
+- Keep previous designs, development history, roadmaps, and future work
+ in issues.
+- State exact support and limits instead of labels such as "prototype".
+ Keep implemented, tested, and unverified behavior apart.
+- Keep safety requirements and known limits explicit. For example, say
+ when emulator tests do not verify compatibility with the physical
+ device.
+- Keep compatibility and old-schema details when the code or the
+ migration tests require them. They describe an active contract, not
+ history.
+- Do not remove a warning or claim support only to remove historical
+ wording. Check the implementation and the tests before you change a
+ claim.
+- Compare claims, examples, paths, and links with the implementation and
+ the tests. Make sure that examples use supported interfaces and
+ contain no secrets.
+
+## Lists
+
+- Give each list item one paragraph only. Put a code block or a second
+ paragraph after the list, because the formatters add an empty line
+ between all items of a list that has one.
+
+## Comments
+
+- A comment explains the declaration that it belongs to: the contract,
+ invariants, non-obvious reasons, and required compatibility behavior.
+ Do not repeat what the code shows or describe unrelated caller
+ behavior.
+- State input constraints, results, errors, and concurrency or resource
+ ownership rules when a caller needs them.
+- Keep a comment next to the code that it describes. Update or remove a
+ comment when its code changes.
+- Write prose comments as continuous paragraphs, with one space between
+ words and sentences. Do not use semicolons in prose.
+- Reflow each changed comment paragraph with `bin/reflow`. Do not reflow
+ by hand or with your own editor configuration.
+- Give `bin/reflow` the first and the last line of the paragraph only.
+ Lists, examples, and directives in a comment keep their line breaks.
+
+This example reflows the comment on lines 12 to 15:
+
+```sh
+bin/reflow main.go 12 15
+```
+
+## Go comments
+
+- Follow [Go Doc Comments](https://go.dev/doc/comment). Write complete
+ sentences. Start a declaration comment with the name of the symbol.
+- Do not write a package comment.
+- Document each exported declaration and its contract.
+
+## Swift comments
+
+These rules apply to the iOS app in `ios/`. They follow the
+[Swift API Design Guidelines](https://www.swift.org/documentation/api-design-guidelines/)
+and Apple's
+[symbol documentation guide](https://developer.apple.com/documentation/xcode/writing-symbol-documentation-in-your-source-files).
+
+- Use `///` for a documentation comment and `//` for an implementation
+ comment.
+- Write a documentation comment for each type and for each member whose
+ use is not clear from its name.
+- Start a documentation comment with a summary that ends with a period.
+ For a function, say what it does and what it returns. For a type or a
+ property, say what it is. The summary does not start with the symbol
+ name, and it can be a sentence fragment.
+- Add `- Parameter`, `- Returns`, or `- Throws` only when the summary
+ does not make them clear.
+- Describe the behavior of the code and the server API that it uses. Do
+ not write which server features are missing or will come. Write what
+ the app does without them, and put the missing feature in an issue.
+- Text that the app shows is not a code comment.
--- /dev/null
+---
+name: go-code
+description: Go style rules. Use before you write or review Go code.
+---
+
+# Go code
+
+## Packages and files
+
+- Put commands in `cmd/` and shared packages at the repository root
+ beside `cmd/`. Do not add an `internal` package.
+- Give each package one clear responsibility and a small public API.
+ Keep implementation types and helpers private.
+- Keep composition and dispatch in the main service type. Put distinct
+ work in focused private types or standalone functions with explicit
+ inputs. Do not spread the methods of the service type across files to
+ make a file shorter.
+- Keep `main.go` limited to the entry point. Name other files for their
+ function. Do not create `main_test.go`.
+- A file carries its own context. Keep each struct and all of its
+ methods in the file that declares the struct. Give a separate
+ responsibility its own type.
+- Name a file that holds a struct after that struct, in snake_case. Use
+ the platform suffix for a platform-specific file. When platforms share
+ the struct, put the platform details in standalone helpers.
+- Write all the letters of an initialism in one case. `revive` checks
+ common initialisms.
+- Put entry points first, then constructors with a `new` or `New`
+ prefix, then each struct's methods as a group, then standalone utility
+ functions. Do not put a utility function between the methods of one
+ type.
+- In each group, order functions by first use in the file.
+- Group related constants in one `const (...)` block and related
+ variables in one `var (...)` block. Put an empty line between entries
+ that have comments.
+
+## Entry point and context
+
+- `main` creates the SIGINT/SIGTERM signal context and defers its
+ cancellation. Pass that context through operations and let cleanup
+ finish before `main` exits. Do not use `log.Fatal`.
+- Use `ctx` for a context and `cancel` for its cancellation function.
+ Call `defer cancel()` immediately after you create the context.
+- Reuse `ctx` when a child context replaces its parent. Use a different
+ name only when both contexts stay in use. Return `ctx.Err()` directly.
+- Derive each context from the context of the caller. When work must
+ finish even after the caller is cancelled, such as a final write, use
+ `context.WithoutCancel` of the caller's context. Use
+ `context.Background()` only where no caller context exists: in `main`,
+ and in methods that follow `io.Closer`.
+- Name a duration that limits one operation after that operation, and
+ end the name with `Timeout`, so that the name says what it limits
+ without its comment. Define it in the file that applies it, not in the
+ file of another type.
+
+## Errors
+
+- Handle every returned error. Do not discard an error with the blank
+ identifier. `errcheck` checks this rule.
+- Reuse an existing `err` for later operations in the same scope. Use a
+ different variable only to keep or join errors.
+- Use `errors.Is` and `errors.As` to examine errors. Do not compare an
+ error directly with a sentinel, because errors can be wrapped.
+ `errorlint` checks comparisons and type assertions on errors.
+- Handle a documented sentinel error explicitly when it does not show a
+ failure.
+- When a function has more than one error path, wrap each returned error
+ with the context of its operation. A function with one error path can
+ return the error directly. `wrapcheck` checks package and interface
+ calls, with the exceptions in `.golangci.yml`. Review local calls and
+ the number of error paths by hand.
+- When deferred cleanup can fail, use a named `err` result and join the
+ cleanup error with `errors.Join`.
+
+## Logs and cancellation
+
+- Log with `log/slog`. Write the message as a short event in plain
+ words, such as "receive failed". Put names, IDs, counts, and durations
+ in attributes with short keys. Put the cause in `err`. Do not format
+ values into the message.
+- Use info for normal events, warn for a failure that is retried or
+ loses nothing, and error for a failure that loses data or stops a
+ part.
+- Cancellation of a service or request context is an expected stop, not
+ a failure. Do not log it as an error, and do not hide all errors only
+ because the context is canceled. A joined error with an independent
+ failure stays a failure.
+- Use the worker or request context for this check. A device request
+ that reaches its own timeout while its worker is active stays a
+ failure.
+- A client that cancels an HTTP request does not get a server-error
+ response for that cancellation.
+- Never log secrets or tokens.
+
+## Style
+
+- Keep function signatures on one line. Write function bodies on more
+ than one line, also for anonymous functions, deferred cleanup, and
+ test callbacks.
+- Do not use short `if` initializers.
+- Use a one-letter receiver name for each method.
+- Use field names in struct literals. Put each field on its own line
+ when you set more than one field. This includes the cases of a
+ table-driven test.
+- Put an empty line between logical blocks, such as two independent `if`
+ statements. Put an empty line before and after loops, switches,
+ selects, standalone blocks, and multiline declarations, assignments,
+ literals, and calls, when there is an adjacent statement. These
+ boundaries also apply when a loop immediately uses the declaration
+ before it.
+- Keep an operation with its immediate error or result check. Keep a
+ short declaration with its immediate use, except at the boundaries
+ above. `wsl_v5` and `nlreturn` check part of these rules. Review the
+ remaining logical blocks and immediate-use groups by hand.
+- Group imports in this order: standard library, external dependencies,
+ project imports. `gci` checks the groups.
+- Use GNU-style command flags: two dashes for long flags, such as
+ `--cwd`, and one dash only for one-letter flags, such as `-h`.
+- Prefer the Go standard library. Prefer Go over cgo.
+- Do not add compile-time interface assertions. Prove compatibility
+ through actual assignments and calls.
+- Use `// nolint: name` for lint directives.
+
+## Dependencies
+
+- After a change to `go.mod` or `go.sum`, update `vendorHash` in the Nix
+ package.
+
+## Tests
+
+- Add tests for behavior changes. Do not add tests that only repeat
+ implementation details, and do not add a test file without behavior
+ worth a test.
+- Put tests in a file that matches the tested source file.
+- Use `testify/require` for assertions. `testifylint` checks the
+ assertion forms.
+- Name a table-driven case slice `testCases` and its range variable
+ `tc`. A subtest name describes the behavior, not a generic outcome or
+ input.
+- In a test file, put constants immediately after the imports, then the
+ tests, then supporting types, methods, and helpers. Keep each
+ supporting struct with its methods.
+- Keep a helper that only one test file uses in that file. Put a helper
+ that more than one test file uses in a file without tests. Name that
+ file for what it holds, and end the name with `_helper_test.go`.
+- Supply valid required dependencies in tests. Do not make a dependency
+ optional or add a nil fallback only to make a test simpler.
--- /dev/null
+---
+name: issues
+description: Read and write issues with git-bug. Use for any issue work.
+---
+
+# Issues
+
+Issues live in git-bug, inside the Git repository, not in files. The
+`overview` issue lists the phases and the open decisions. Run the
+commands in the Nix development shell.
+
+## Read
+
+- List the open issues with `git bug bug status:open`.
+- Read an issue and all its comments with `git bug bug show ID` before
+ you start work on it.
+
+## Write
+
+- Give the title and the body with `-t` and `-m`. Do not use `-F`: it
+ drops every line that starts with `#`, so Markdown headings are lost.
+- Write the title like a commit subject: 50 characters or fewer, in the
+ imperative mood, with no final period.
+- Write the body in Markdown. State the result for the user, the scope,
+ and how to check it.
+- Format each body and comment with `mdformat -` in the repository, so
+ that it uses `.mdformat.toml`. mdformat joins trailer lines into one
+ line, so format the text first and add the trailers after it.
+- Quote a long text as a blockquote below the paragraph or list, not as
+ inline code. mdformat cannot wrap inline code.
+- Add a comment with `git bug bug comment new ID -m TEXT` for progress
+ that matters: decisions made with the user, measurements, and checks
+ that could not run.
+- When a question to the user is still open at the end of a session, add
+ it as a comment with the options and your recommendation. Add the
+ answer as a comment when it comes.
+- Give the reason with each decision, so that a later agent does not
+ undo it.
+- Close an issue with `git bug bug status close ID` only when its check
+ passes.
+- Label a phase `phase`, an open question `decision`, and the plan
+ `overview`, with `git bug bug label new ID LABEL`.
+
+## Relations
+
+git-bug has no links between issues. Write them as trailers in the last
+paragraph of the body, one per line, as in a commit message:
+
+- `Parent: ID` names the issue that this issue belongs to. A child has
+ one parent.
+- `Depends-on: ID, ID` names the issues that must be done first.
+- A parent lists the IDs of its children in its body.
+
+`git interpret-trailers --parse` reads the trailers of a body.
+
+Find the issues that relate to an issue with `git bug bug ID`. The
+search finds every issue that mentions the ID, so read the trailers of
+each match. Do not search for more than one word: git-bug finds issues
+that match any of the words, and it reads `Parent:` as a query
+qualifier. Nothing checks the trailers. Before you close a parent, make
+sure that its children are closed.
+
+Push the issues with `git bug push` when the repository has a remote.
+
+This example creates an issue:
+
+```sh
+body=$(mdformat - < body.md)
+git bug bug new --non-interactive -t 'Keep camera buffers through slow disk writes' \
+ -m "$body
+
+Parent: 180a674"
+```
--- /dev/null
+../.agents/skills
\ No newline at end of file
--- /dev/null
+#!/bin/sh
+# Rejects a commit message that breaks the rules in AGENTS.md: a subject of 50
+# characters or fewer without a final period, one blank line, a body, body lines
+# of 72 characters or fewer, and real line breaks instead of "\n". Prose
+# paragraphs fill each line before wrapping. Formatted blocks keep their line
+# breaks. A body line without spaces, such as a URL, may be longer.
+set -eu
+
+file=$1
+subject=$(sed -n 1p "$file")
+case $subject in
+ "Merge "* | "Revert "* | "fixup! "* | "squash! "*) exit 0 ;;
+esac
+
+fail() {
+ printf 'commit-msg: %s\n' "$1" "write the message to a file and commit with git commit -F." >&2
+ exit 1
+}
+
+if grep -q '\\n' "$file"; then
+ fail 'the message has a literal "\n"; use real line breaks'
+fi
+
+[ ${#subject} -le 50 ] || fail "the subject has ${#subject} characters; the limit is 50"
+
+case $subject in
+ *.) fail "the subject ends with a period" ;;
+esac
+
+[ -z "$(sed -n 2p "$file")" ] || fail "the second line must be blank"
+
+sed -n '3,$p' "$file" | grep -q '[^[:space:]]' || fail "the message has no body"
+
+long=$(sed -n '3,$p' "$file" | awk 'length($0) > 72 && $0 ~ / / { print NR + 2; exit }')
+[ -z "$long" ] || fail "line $long is longer than 72 characters"
+
+short=$(awk '
+ function check_paragraph( i, word) {
+ if (!formatted && !bad_line) {
+ for (i = 1; i < count; i++) {
+ word = lines[i + 1]
+ sub(/[[:space:]].*$/, "", word)
+ if (length(lines[i]) + 1 + length(word) <= 72) {
+ bad_line = numbers[i]
+ break
+ }
+ }
+ }
+
+ count = 0
+ formatted = 0
+ }
+
+ NR <= 2 { next }
+
+ fence != "" {
+ if ($0 ~ /^(`{3,}|~{3,})[[:space:]]*$/ && substr($0, 1, 1) == fence) {
+ match($0, /^(`+|~+)/)
+ if (RLENGTH >= fence_length) fence = ""
+ }
+ next
+ }
+
+ /^(`{3,}|~{3,})/ {
+ check_paragraph()
+ match($0, /^(`+|~+)/)
+ fence = substr($0, 1, 1)
+ fence_length = RLENGTH
+ next
+ }
+
+ /^[[:space:]]*$/ {
+ check_paragraph()
+ next
+ }
+
+ {
+ if ($0 ~ /^[[:space:]>#|]/ ||
+ $0 ~ /^([-*+][[:space:]]|[0-9]+[.)][[:space:]])/ ||
+ $0 ~ /^[-=*_]{3,}$/ ||
+ $0 ~ /^[[:alnum:]][[:alnum:]-]*:[[:space:]]/) {
+ formatted = 1
+ }
+
+ lines[++count] = $0
+ numbers[count] = NR
+ }
+
+ END {
+ check_paragraph()
+ if (bad_line) print bad_line
+ }
+' "$file")
+[ -z "$short" ] || fail "unnecessary line break after line $short; reflow the paragraph to 72 characters"
--- /dev/null
+/.direnv/
+/.devenv/
+/result*
+/AGENTS.local.md
--- /dev/null
+version: "2"
+
+linters:
+ default: standard
+ enable:
+ - errname
+ - errorlint
+ - gocritic
+ - godot
+ - makezero
+ - misspell
+ - nlreturn
+ - predeclared
+ - revive
+ - testifylint
+ - unconvert
+ - wrapcheck
+ - wsl_v5
+ settings:
+ errcheck:
+ # go-code: Do not discard an error with the blank identifier.
+ check-type-assertions: true
+ check-blank: true
+ errorlint:
+ errorf: true
+ asserts: true
+ comparison: true
+ godot:
+ scope: declarations
+ capital: true
+ govet:
+ enable-all: true
+ disable:
+ - fieldalignment
+ misspell:
+ locale: US
+ wsl_v5:
+ # go-code: Keep an operation with its immediate error check.
+ enable:
+ - err
+ - after-block
+ - after-decl
+ - after-defer
+ - after-expr
+ - after-go
+ # go-code: Keep a short declaration with its immediate use.
+ allow-first-in-block: true
+ allow-whole-block: true
+ cuddle-max-statements: 2
+ disable:
+ # go-code: Keep a short declaration with its immediate assignment use.
+ - assign
+ # go-code: Keep a short declaration, its operation, and its error check
+ # together.
+ - if
+ # go-code: Keep a declared cleanup with its immediate defer.
+ - defer
+ # go-code: Keep a short declaration with its immediate append use.
+ - append
+ # go-code: Keep a short declaration with its immediate worker setup.
+ - go
+ nlreturn:
+ # go-code: Keep a short declaration with its immediate use.
+ block-size: 2
+ wrapcheck:
+ # go-code: Return ctx.Err() directly.
+ extra-ignore-sigs:
+ - "(context.Context).Err() error"
+ exclusions:
+ rules:
+ # docs: Do not write a package comment.
+ - linters: [revive]
+ text: "package-comments"
+
+formatters:
+ enable:
+ - gofumpt
+ - gci
+ settings:
+ gofumpt:
+ module-path: onehouse
+ gci:
+ # go-code: Group imports as standard library, external, project.
+ sections:
+ - standard
+ - default
+ - prefix(onehouse)
+ custom-order: true
+
+issues:
+ max-issues-per-linter: 0
+ max-same-issues: 0
--- /dev/null
+wrap = 72
+number = true
--- /dev/null
+# Agent guide
+
+Read this file in full. It holds the rules for every task.
+
+## References
+
+- `README.rst`: project scope, setup, and commands.
+- `doc/`: one document for each subject. Read the document of a subject
+ before you change its code.
+- `AGENTS.local.md`: the preferences of one person, machine, or account.
+ Git ignores it. Read it when it exists. When it conflicts with this
+ guide, this guide wins.
+
+## Skills
+
+Read the `SKILL.md` of a skill before you start the task that it covers:
+
+| Task | Skill |
+| ----------------------------------------------- | ------------------------ |
+| Write or change Go code or Go tests | `.agents/skills/go-code` |
+| Write or change documentation or prose comments | `.agents/skills/docs` |
+| Format and check the repository | `.agents/skills/check` |
+| Make any commit | `.agents/skills/commit` |
+| Read or write an issue | `.agents/skills/issues` |
+
+## Rules
+
+- Use ASD-STE100 Simplified Technical English for communication with the
+ user, documentation, and code comments.
+- Every file describes the current state only. Do not write history in
+ files: no dates of changes, no "was", "previously", or "no longer",
+ and no changelogs or work logs. Git records what changed and why.
+ Issues hold plans and future work.
+- Keep the implementation small. Add a feature, dependency, or
+ integration only when a task requires it.
+- Change only what the task asks for. Do not change unrelated code to
+ match a style rule.
+- Nix is the reference environment. The pinned flake supplies the Go
+ toolchain, the tools, and every dependency. Keep Nix code in `nix/`.
+ Do not install host dependencies or add a dependency that works only
+ outside Nix. Xcode on macOS is the only exception.
+- Write only ASCII characters in files, commit messages, and issues. Use
+ `<-` and `->` for arrows, `-` for dashes, and straight quotes. Use
+ other characters only in text that an application shows to the user,
+ in names in a house configuration, and in test data that needs them.
+- Keep secrets and local runtime files out of Git and Nix sources.
+- Do not reveal infrastructure in files, code comments, commit messages,
+ or issues. This includes host, server, and domain names, IP addresses,
+ ports, network devices, user names, paths outside the repository, and
+ deploy steps. Values from `AGENTS.local.md` or the local machine are
+ private too.
+- Use a neutral placeholder, such as `example.com` or `HOST`, or
+ describe the condition instead. Write a real value only when the user
+ explicitly allows that value for the current task.
+- Change `AGENTS.md` and `.agents/` only when the user asks.
+- Put personal, machine, or account preferences in `AGENTS.local.md`,
+ never in a tracked file.
+
+## Decisions
+
+Stop and ask the user before you act when:
+
+- the task can be read in more than one way;
+- two or more reasonable solutions give different results;
+- the task conflicts with this guide or a skill.
+
+Ask one short question. Give the options and your recommendation.
+Continue only with work that the answer cannot change.
--- /dev/null
+AGENTS.md
\ No newline at end of file
--- /dev/null
+##########
+ OneHouse
+##########
+
+OneHouse is one program that controls a whole house. Linux is supported.
+
+*************
+ Development
+*************
+
+Nix supplies the toolchain and every tool. Enter the development shell:
+
+.. code:: console
+
+ nix develop
+
+With direnv, the shell loads when you enter the checkout. The shell sets
+the Git hooks path to ``.githooks``.
+
+Format all files, then run the checks:
+
+.. code:: console
+
+ bin/format
+ bin/check
+ nix build
+ nix flake check
+
+``AGENTS.md`` holds the rules for work in this repository.
--- /dev/null
+#!/usr/bin/env bash
+set -euo pipefail
+cd "$(dirname "$0")/.."
+
+# Lists the tracked and the new, not ignored files that match a pattern.
+files() {
+ git ls-files --cached --others --exclude-standard -- "$@"
+}
+
+unformatted=$(golangci-lint fmt --diff)
+if [ -n "$unformatted" ]; then
+ printf 'golangci-lint fmt: unformatted files:\n%s\n' "$unformatted" >&2
+ exit 1
+fi
+
+golangci-lint run
+go test -race ./...
+files '*.nix' | xargs nixfmt --check
+files '*.rst' | xargs rstfmt --check
+files '*.md' | xargs mdformat --check
+shellcheck bin/check bin/format bin/reflow .githooks/commit-msg
--- /dev/null
+#!/usr/bin/env bash
+set -euo pipefail
+cd "$(dirname "$0")/.."
+
+# Lists the tracked and the new, not ignored files that match a pattern.
+files() {
+ git ls-files --cached --others --exclude-standard -- "$@"
+}
+
+golangci-lint fmt
+files '*.nix' | xargs nixfmt
+files '*.rst' | xargs rstfmt
+files '*.md' | xargs mdformat
--- /dev/null
+#!/usr/bin/env bash
+# Reflows the prose comment on lines FIRST to LAST of FILE. It uses the
+# Vim from the Nix shell without a user configuration, so the result is
+# the same on every machine. The filetype plugin of Vim supplies the
+# comment markers.
+set -euo pipefail
+
+if [ $# -ne 3 ]; then
+ echo 'usage: bin/reflow FILE FIRST LAST' >&2
+ exit 2
+fi
+
+file=$1
+first=$2
+last=$3
+
+vim -u NONE -i NONE -N -n -es \
+ -c 'filetype plugin on' \
+ -c "edit $file" \
+ -c 'set textwidth=80 nojoinspaces formatoptions=cq' \
+ -c "${first},${last}s/\\S\\zs \\+/ /ge" \
+ -c "normal! ${first}GV${last}Ggq" \
+ -c 'wq'
--- /dev/null
+package main
+
+import (
+ "fmt"
+ "os"
+)
+
+func main() {
+ _, err := fmt.Println("onehouse")
+ if err != nil {
+ os.Exit(1)
+ }
+}
--- /dev/null
+{
+ "nodes": {
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1786599213,
+ "narHash": "sha256-yNJd40f11EzXBjSByCB7IPpeFFAdeoSKKM67dGkfFoU=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "0e251e24a4f24e036a084b6b4b2d2491af4167f4",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nixpkgs": "nixpkgs",
+ "systems": "systems"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1791244623,
+ "narHash": "sha256-uuanbkFk8a313Zfrxdcz8Us/yQtaxNnKJtX8v0ZPpd8=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "d36eeed142f88b5a636f2e3856a53c61654cf29b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
--- /dev/null
+{
+ description = "OneHouse";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+ systems.url = "github:nix-systems/default";
+ };
+
+ outputs =
+ {
+ self,
+ nixpkgs,
+ systems,
+ }:
+ let
+ allSystems = import systems;
+ # The program runs on Linux. The development shell also supports macOS.
+ linuxSystems = builtins.filter (nixpkgs.lib.hasSuffix "-linux") allSystems;
+ forEachSystem = nixpkgs.lib.genAttrs allSystems;
+ forEachLinuxSystem = nixpkgs.lib.genAttrs linuxSystems;
+ date = self.lastModifiedDate;
+ revision = self.shortRev or self.dirtyShortRev or "unknown";
+ version = "${builtins.substring 0 4 date}-${builtins.substring 4 2 date}-${builtins.substring 6 2 date}-${revision}";
+ in
+ {
+ packages = forEachLinuxSystem (
+ system:
+ let
+ pkgs = nixpkgs.legacyPackages.${system};
+ onehouse = pkgs.callPackage ./nix/package.nix {
+ src = self;
+ inherit version;
+ };
+ in
+ {
+ inherit onehouse;
+ default = onehouse;
+ }
+ );
+
+ devShells = forEachSystem (system: {
+ default = import ./nix/devshell.nix {
+ pkgs = nixpkgs.legacyPackages.${system};
+ };
+ });
+
+ checks = forEachLinuxSystem (system: {
+ package = self.packages.${system}.default;
+ });
+
+ formatter = forEachSystem (system: nixpkgs.legacyPackages.${system}.nixfmt);
+ };
+}
--- /dev/null
+module onehouse
+
+go 1.26.5
--- /dev/null
+{ pkgs }:
+pkgs.mkShell {
+ packages = with pkgs; [
+ git
+ git-bug
+ go
+ gofumpt
+ golangci-lint
+ gopls
+ nixfmt
+ (python3.withPackages (p: [
+ p.mdformat
+ p.mdformat-gfm
+ p.mdformat-frontmatter
+ ]))
+ rstfmt
+ shellcheck
+ vim
+ ];
+
+ GOTOOLCHAIN = "local";
+
+ shellHook = ''
+ git config core.hooksPath .githooks 2>/dev/null || true
+ '';
+}
--- /dev/null
+{
+ lib,
+ buildGoModule,
+ src,
+ version,
+}:
+buildGoModule {
+ pname = "onehouse";
+ inherit src version;
+
+ subPackages = [ "cmd/onehouse" ];
+ vendorHash = null;
+
+ doInstallCheck = true;
+ installCheckPhase = ''
+ runHook preInstallCheck
+ [[ "$("$out/bin/onehouse")" == "onehouse" ]]
+ runHook postInstallCheck
+ '';
+
+ meta = {
+ description = "OneHouse";
+ mainProgram = "onehouse";
+ platforms = lib.platforms.linux;
+ };
+}