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
93 changes: 93 additions & 0 deletions .agents/skills/architecture-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
name: architecture-review
description: Use when reviewing a PR, branch, or staged diff in the openboot repo. Audits the change against project invariants from AGENTS.md — subprocess hygiene, HTTP wrapper usage, error wrapping, dry-run gating, UI helper usage, test placement. Trigger when user says "review this", "check this PR", "audit the diff", or asks about whether a change follows project conventions.
---

# Architecture review for openboot

Run this checklist on any diff before approving it. The checks map 1:1 to
the rules in AGENTS.md → "Project-specific conventions" and the
fitness functions in `internal/archtest`.

## Step 1 — Diff scope

Confirm what's changed:

```bash
git diff --stat main...HEAD # files
git diff main...HEAD -- 'internal/**/*.go' # production Go
```

If the diff spans more than ~10 files or mixes feature + refactor +
docs, call that out and suggest splitting — one thing per commit is a
project convention.

## Step 2 — Run the mechanical checks first

```bash
go vet ./...
make test-unit # includes archtest
golangci-lint run ./... # if available
```

These catch ~80% of what a human reviewer would otherwise comment on.
**If they don't pass, stop reviewing and ask the author to fix first.**

## Step 3 — Architecture invariants (manual review of the diff)

For each changed file under `internal/` or `cmd/`:

| Check | Look for | If violated |
|---|---|---|
| Subprocess | New `exec.Command` / `exec.CommandContext` call | Should be in `internal/system` or wrapped in a Runner. Reject + suggest `internal/system.RunCommand`. |
| HTTP | New `http.NewRequest`, `http.Get`, `http.DefaultClient` | Should go through `internal/httputil.Do`. Reject + suggest wrapper. |
| Home dir | `os.Getenv("HOME")`, hardcoded `/Users/...`, hardcoded `"~/"` | Use `os.UserHomeDir()`. |
| Error wrap | `return err` without context | Should be `fmt.Errorf("doing X: %w", err)` unless the error is already wrapped one frame up. |
| UI output | `fmt.Println`, `fmt.Printf` in non-UI packages | Use `ui.Info`, `ui.Success`, etc. (legacy violations are baselined — only flag NEW ones). |
| Dry run | New destructive operation (mkdir, write, exec, network mutate) | Must check `cfg.DryRun` and print `[DRY-RUN] Would X` instead. |
| Embedded data | New `data/*.yaml` or schema change | Confirm fallback path still loads. |
| State path | New file under `~/.openboot/` | Confirm the path is created via `os.UserHomeDir()`, perms are `0o600` for secrets / `0o755` for dirs. |

## Step 4 — Test layer check

For each non-trivial change:

- **Pure logic** → must have an L1 test in `internal/<pkg>/`. If missing, request it.
- **Subprocess interaction** → fake via Runner in `internal/<pkg>/` OR add a
real-subprocess test under `test/integration/` (still L1, no build tag).
- **CLI flag parsing** → table-driven L1 test.
- **Destructive op** → must run cleanly under `--dry-run` in a test.

If the change is small enough to land without tests (e.g. comment
rewording, typo fix), say so explicitly rather than letting it pass
unmentioned.

## Step 5 — Behaviour invariants

| Check | Why it matters |
|---|---|
| Does the change break the curl\|bash install path? | `scripts/install.sh` is the primary install vector. The smoke test CI job covers this — make sure it still passes. |
| Does the change alter the contract schema (config / snapshot JSON)? | If yes, the schema needs updating in `openboot-contract` repo and bumping. Otherwise the L2 contract job will fail. |
| Does the change add or change a CLI flag? | Confirm `--help` output is updated, and old-cli compat job still passes (previous release × new mock server). |
| Does the change touch `data/presets.yaml`? | Three presets must still parse and resolve. |
| Does the change touch `~/.openboot/*` file format? | Confirm backward compat with old files (snapshot, auth, state) or write a migration. |

## Step 6 — Risk assessment

End the review with:

- **Risk:** low / medium / high (one sentence on blast radius).
- **Rollback:** how a user / operator would revert (CLI flag, file delete,
brew downgrade).
- **Observability:** what would tell us this broke in production
(telemetry, GitHub issue pattern, smoke job).

## What the reviewer SHOULD NOT do

- Do not approve a change that adds an archtest violation without an
updated `baseline/` file and a justification in the commit message.
- Do not approve mixed-purpose commits — request a split.
- Do not approve changes to `.github/workflows/` without confirming the
workflow still runs (`act` locally or trigger on a draft PR).
- Do not approve direct edits to `data/packages.yaml` without confirming
the source-of-truth is openboot.dev (see AGENTS.md "Where to look").
128 changes: 128 additions & 0 deletions .agents/skills/bootstrap-feature/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
name: bootstrap-feature
description: >-
Use when adding a new CLI subcommand or feature to openboot. Walks through
the canonical pattern: cobra command registration, runner wiring, archtest
awareness, and test placement. Trigger when the user says "add a command",
"new subcommand", "implement feature X for the CLI", or starts editing
under internal/cli/.
---

# Bootstrap a new openboot feature

This skill gives the canonical recipe for adding a new CLI command or
feature to openboot without violating project invariants.

## Step 1 — Where the code goes

| Kind of thing | Location |
|---|---|
| New CLI subcommand | `internal/cli/<verb>.go`, register in `internal/cli/root.go` `init()` |
| Subprocess call (any binary) | `internal/system.RunCommand` / `RunCommandSilent` — do **not** call `exec.Command` directly |
| HTTP call (any URL) | `internal/httputil.Do` — handles 429 + Retry-After |
| Path under `~` | `os.UserHomeDir()` — never `os.Getenv("HOME")`, never hardcode `~` |
| User-visible output | `internal/ui.*` helpers — never raw `fmt.Println` |
| Destructive action | guarded by `cfg.DryRun` check |
| Error returned to caller | wrapped: `fmt.Errorf("context: %w", err)` |

These are enforced (or planned) by `internal/archtest`. The rule that
covers each invariant is listed in [AGENTS.md](../../../AGENTS.md).

## Step 2 — Cobra command skeleton

```go
// internal/cli/myverb.go
package cli

import "github.com/spf13/cobra"

func newMyVerbCmd() *cobra.Command {
var flag string
cmd := &cobra.Command{
Use: "myverb [arg]",
Short: "One-line description",
Long: "Longer description if useful.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runMyVerb(cmd.Context(), args[0], flag)
},
}
cmd.Flags().StringVar(&flag, "flag", "", "Description of the flag")
return cmd
}
```

Register it in `internal/cli/root.go`:

```go
rootCmd.AddCommand(newMyVerbCmd())
```

## Step 3 — Runner interface for testability

If your feature shells out, **use the Runner pattern** so L1 tests can fake
subprocess calls. Example from `internal/brew/runner.go`:

```go
type Runner interface {
Output(args ...string) ([]byte, error)
CombinedOutput(args ...string) ([]byte, error)
// ...
}

// realRunner uses exec.Command — this file is in execAllowedPaths.
type realRunner struct{}

func New() *Brew { return &Brew{r: &realRunner{}} }
```

Then in tests:

```go
type fakeRunner struct { ... }
func (f *fakeRunner) Output(args ...string) ([]byte, error) { ... }
```

This is the pattern that lets the fake-runner half of L1 stay fast and hermetic.

## Step 4 — Test placement

| Scope | Tier | Build tag | Where |
|---|---|---|---|
| Pure logic + fakes | L1 | none | `<pkg>/<feature>_test.go` |
| Real subprocess in temp dir | L1 | none | `test/integration/<feature>_integration_test.go` |
| Compiled binary, no installs | L3 | `e2e` | `test/e2e/...` |
| Real installs on macOS | L4 (VM) | `e2e,vm` | `test/e2e/...` |

Default to faked-runner L1 unless the thing you're testing only exists when a real
brew/git/npm is on the path — then add an integration test under `test/integration/`
(no build tag; it runs as part of L1).

## Step 5 — Verify before committing

```bash
go vet ./...
make test-unit # ~75s, includes archtest + integration
```

If archtest fails with a new violation, fix the code rather than
baselining — the baseline is for *intentional* exceptions and they
require justification in the commit message.

## Step 6 — Conventional commit

`feat: add openboot myverb command for X`

One thing per commit. If you also fixed an unrelated bug along the way,
split it into a separate commit.

## Common mistakes

1. **Calling `exec.Command` from the feature file** — refactor through
`internal/system` or add a Runner. archtest will catch this.
2. **Skipping `cfg.DryRun` check** — destructive ops must be a no-op
under `--dry-run`. Print "[DRY-RUN] Would X" instead of doing X.
3. **Hardcoding `~/`** — always `os.UserHomeDir()` then `filepath.Join`.
4. **Raw `fmt.Println`** — use `ui.Info`, `ui.Success`, `ui.Warn`, `ui.Error`.
5. **Forgetting to register the command in `root.go`** — cobra silently
does nothing if `AddCommand` is missed.
Loading
Loading