vault: session notes 2026-07-13

This commit is contained in:
Jared Swanson 2026-07-13 14:36:59 -04:00
parent 50b3c5a771
commit b6b7efdea1
7 changed files with 346 additions and 0 deletions

View File

@ -0,0 +1,44 @@
---
type: convention
title: AI Agent Operating Rules
summary: Baseline behavioral rules for AI agents operating in a role-based multi-agent workspace — role boundaries, mandatory test-after-change discipline, and architecture-change escalation. Answers "what is an agent allowed to do unsupervised?"
tags:
- type/convention
- domain/agent-orchestration
scope: global
last_updated: 2026-07-13
date: 2026-07-13
related:
- tdd-methodology
source: hyperthrive_dev
---
# AI Agent Operating Rules
## Purpose
Governs the default boundaries an AI agent should respect when working inside a role-partitioned codebase/workspace, independent of any single project's specifics.
## Core Principles
**1. Act only within your assigned role, and read only what that role needs.**
Reading or acting outside an assigned layer (e.g. a developer agent editing architecture docs) is how role boundaries erode and unreviewed decisions slip in. Treat "not in my role" as a hard stop, not a judgment call to make silently.
**2. Never infer context outside your layer — ask instead.**
An agent that fills gaps by guessing at another role's intent produces confident-looking but ungrounded work. When information needed to proceed correctly isn't available in-role, surface the uncertainty rather than assume.
**3. Verify behavior after every change that could affect it, not just at the end of a task.**
"Assume code works" is the default failure mode for agents — run tests immediately after writing new code, refactoring, updating dependencies, or any change with behavioral surface, not only when the agent decides the task is "done."
**4. Architecture changes always route through an explicit architecture step.**
Never let an agent restructure system boundaries as a side effect of a feature task. Document the decision (in whatever this project's architecture-decision system is) and update any affected top-level docs before proceeding.
## Anti-Patterns
- **Agent edits files or reads context outside its assigned role "because it seemed relevant"** → role boundary violation; escalate instead
- **Task marked done without running tests after the last change** → unverified behavior, the single most common agent failure mode
- **Silently making an architectural decision inside an unrelated task** → skips review; route through the architecture-decision process explicitly
- **Large, multi-concern PRs/diffs from an agent** → harder to review and revert; keep changes small and single-purpose
## Related
- [[tdd-methodology]] — the concrete test-isolation patterns this rule's "test after every change" discipline relies on

View File

@ -0,0 +1,54 @@
---
type: convention
title: Phlex Component Design
summary: Rules for building Rails UI components in Phlex (pure-Ruby templating) instead of ERB/ViewComponent — when to use it, how to structure and test a component, and the common lifecycle-hook mistakes.
tags:
- type/convention
- domain/software-design
- tool/phlex
- tool/rails
scope: global
last_updated: 2026-07-13
date: 2026-07-13
related:
- sandi-metz-code-philosophy
- tdd-methodology
source: hyperthrive_dev
---
# Phlex Component Design
## Purpose
Governs when to reach for Phlex over ViewComponent/ERB, and how to structure a Phlex component so it stays testable and safe by default.
## Core Principles
**1. Every component is a plain Ruby class with no template file.**
Phlex trades template-language flexibility for structural safety (XSS protection by default) and full unit-testability — treat a component like any other Ruby object with explicit dependencies.
**2. Explicit dependencies, injected via `initialize`.**
Pass all data a component needs through its constructor rather than reaching into globals or helpers. This is what makes a component testable in isolation.
**3. Sandi Metz limits apply.** Classes < 100 lines, methods < 5 lines, `initialize` params < 4 a component doing more than one UI concern should be split. See [[sandi-metz-code-philosophy]].
**4. Coverage floor: ≥ 90%, covering init/render/conditional-branch/edge cases.**
A component's tests are the spec for its behavior; untested branches are unverified behavior in production markup.
## Patterns
**Choosing Phlex vs ViewComponent.** Use Phlex for anything new: components with slots/composition, or anything needing unit tests. Keep ViewComponent only for legacy `.html.erb` templates already in that form; don't introduce it for new work. One-off, never-reused markup doesn't need a component at all — inline it and extract only once duplicated.
**Test shape.** Cover four categories per component: initialization (params, defaults, edge cases), rendering (HTML structure/classes/text), conditional logic (every branch), and edge cases (nil/empty/boundary values).
## Anti-Patterns
- **Defining `template` instead of `view_template`** → wrong lifecycle hook name, silently no-ops
- **`include ActionView::Helpers::*` directly** → use the `Phlex::Rails::Helpers::*` adapters instead
- **Overriding a lifecycle hook (e.g. `before_render`) without calling `super`** → breaks the parent's setup silently
- **Building markup inside `initialize`** → markup belongs only in `view_template`; `initialize` is for storing dependencies
- **`render raw(user_input)`** → reintroduces XSS; wrap trusted HTML in `safe()` instead, never wrap untrusted input
## Related
- [[sandi-metz-code-philosophy]] — the size/responsibility limits Phlex components must obey
- [[tdd-methodology]] — the red-green-refactor loop these components are built with

View File

@ -0,0 +1,70 @@
---
type: convention
title: TDD and Test Isolation Methodology
summary: Rules for writing isolated, non-flaky tests — setup-based test data over fixtures, ENV cleanup, and avoiding config caching that breaks test overrides. Answers "why is this test flaky / coupled to other tests?"
tags:
- type/convention
- domain/testing
scope: global
last_updated: 2026-07-13
date: 2026-07-13
related:
- phlex-component-design
- ai-agent-rules
source: hyperthrive_dev
---
# TDD and Test Isolation Methodology
## Purpose
Governs how to set up test data and handle process-global state (ENV, constants) so tests stay isolated, parallel-safe, and non-flaky.
## Core Principles
**1. Build test data explicitly in `setup`, not via fixtures.**
Fixtures hide what data exists behind a separate file, creating coupling between unrelated tests that happen to share a fixture. Explicit `setup` blocks make dependencies visible in the test itself, isolate each test's data, and work cleanly under parallel test execution.
**2. Never cache dynamic/environment values in constants.**
A constant like `COMPANY_NAME = ENV.fetch(...).freeze` is captured once at class-load time — tests can't override it afterward because it's frozen before the test process even starts. Read ENV (or any config source that a test might need to vary) dynamically via a method call, not a frozen constant, so tests can verify fallback and override behavior.
**3. Any test that mutates process-global state must restore it, unconditionally.**
Tests run in parallel processes; a leaked ENV change or global mutation from one test can flake an unrelated test. Always restore in an `ensure` block, not just at the end of the happy path — restoration must run even when the test body raises.
## Patterns
**Setup-based fixtures.**
```ruby
setup do
@company = Company.create!(name: "Test Company")
@contact = Contact.create!(name: "John Doe", company: @company)
end
```
**ENV mutation with guaranteed restore.**
```ruby
test "with custom ENV" do
original = ENV["MY_VAR"]
ENV["MY_VAR"] = "test value"
# ...
ensure
original ? ENV["MY_VAR"] = original : ENV.delete("MY_VAR")
end
```
**Dynamic config reads.**
```ruby
# GOOD — tests can override via ENV at runtime
def self.company_name = ENV.fetch("COMPANY_NAME", "Default")
```
## Anti-Patterns
- **Fixture files for test data** → hidden coupling across tests; use `setup` blocks instead
- **`CONST = ENV.fetch(...).freeze` in a service/class body** → frozen at load time, untestable; wrap in a method
- **ENV mutation without `ensure`** → leaks into parallel test runs, causes flaky failures unrelated to the actual change
- **Testing an ActiveJob without `ActiveJob::TestHelper`** → can't assert `assert_enqueued_with`/perform behavior correctly
## Related
- [[phlex-component-design]] — the ≥90% coverage bar these test patterns support
- [[ai-agent-rules]] — the "test after every change" rule this methodology implements

View File

@ -0,0 +1,66 @@
---
type: howto
title: Set Up a DevContainer Sandbox for Autonomous Claude Code
summary: How to run Claude Code in `--dangerously-skip-permissions` mode safely, by isolating it inside a Docker devcontainer instead of the host machine.
tags:
- type/howto
- domain/agent-orchestration
- tool/docker
- tool/claude-code
scope: global
last_updated: 2026-07-13
date: 2026-07-13
update_note: experience-driven
related: []
source: design-mode
---
# Set Up a DevContainer Sandbox for Autonomous Claude Code
## Opening
Reach for this when you want to let Claude Code run unsupervised with `--dangerously-skip-permissions` (fully autonomous, no per-action confirmation) but don't want it to have that power directly against your host filesystem — a Docker container gives it a disposable, reproducible environment instead.
## Prerequisites
- [ ] Docker + Docker Compose installed on the host
- [ ] A `docker-compose.yml` defining the sandbox service, running as a non-root user (root is refused by `--dangerously-skip-permissions`)
- [ ] `ANTHROPIC_API_KEY` available to export or place in a `.env` file
- [ ] Dev tooling baked into the image: git, gh, curl/wget, python3, nodejs (for the Claude Code runtime), ripgrep, jq at minimum
## Steps
### Step 1: Build and start the sandbox
```bash
docker compose up -d --build
```
Builds the image (if changed) and starts the container in the background.
### Step 2: Export your API key before first use
```bash
export ANTHROPIC_API_KEY=sk-ant-...
docker compose up -d --build
```
Or place `ANTHROPIC_API_KEY=sk-ant-...` in a `.env` file next to the compose file — either works, but the key must be present before the container starts if Claude Code will authenticate via env var.
### Step 3: Enter the sandbox and run Claude Code unsupervised
```bash
docker exec -it <container-name> bash
claude --dangerously-skip-permissions
```
Runs as the container's non-root user. Because the blast radius is the container's filesystem, not the host's, autonomous mode is safe to use here in a way it would not be run directly on the host.
### Step 4: Stop or reset when done
```bash
docker compose down # stop, keep persisted home volume
docker compose down -v # stop AND wipe the persisted home volume (full reset)
```
## Verification
`docker exec -it <container-name> bash` drops you into a shell as the non-root user; `whoami` should not return `root`, and `claude --version` should succeed inside the container.
## Gotchas
- **`claude --dangerously-skip-permissions` refuses to run as root** — the container user must be non-root (with passwordless sudo if you need to install packages ad hoc); running the container as root will silently block autonomous mode.
- **Mounting `~/.claude/` read-write shares host state into the sandbox** — this carries over slash commands, settings, and MCP config, but also lets the container modify host `~/.claude/` files. Mount it `:ro` if you want the container's autonomy contained to the container's own filesystem only.
- **`network_mode: host` skips Docker's port mapping** — services in the container bind directly to host ports; if you instead use bridge networking you must add explicit port mappings or exposed ports won't be reachable.
- **A named volume for the home directory persists Claude Code auth and shell state across container restarts**`docker compose down` alone preserves it; only `-v` wipes it. Use the volume when you want to avoid re-authenticating every session, and `-v` deliberately when you want a truly clean environment.

View File

@ -1648,3 +1648,10 @@ tags: [scope/global, type/log]
**Reason:** prompt_input_exit
**Vault notes touched:**
(none)
## Session — 2026-07-13T17:32:29Z
**Project:** /home/jared/dev/cc-os
**Reason:** prompt_input_exit
**Vault notes touched:**
(none)

View File

@ -0,0 +1,42 @@
---
type: reference
subtype: design-rules
title: UI Color Rules for Functional Interfaces
summary: Standards for applying color in dashboards/admin/SaaS UIs so that color always signals meaning (status, hierarchy, interactivity) rather than decoration, plus how to calibrate urgency treatments.
tags:
- type/reference
- domain/ui-design
scope: global
last_updated: 2026-07-13
date: 2026-07-13
related: []
source: design-mode
---
# UI Color Rules for Functional Interfaces
## Purpose
Standards to check a color choice against when designing functional/professional UIs (dashboards, admin panels, SaaS tools, data-heavy interfaces).
## Rules / Standards
- **Color must always serve meaning, never decoration** — assign color only for status, state, importance, interactivity, or (in emotional/atmospheric briefs) atmosphere; if it conveys nothing, remove it
- **Neutral base, ~90% grayscale** — background off-white/cool-gray, cards white, dividers subtle gray; reserve color for accents and data, not surfaces
- **One primary accent color** — reserved for "click here / this is important / this is active"; diluting it across multiple "primary" colors removes the visual anchor
- **Red/yellow/orange are reserved for risk and urgency** — red = error/blocker, amber/yellow = warning, orange = attention/deadline; green = success/completion; blue/purple = neutral functional state
- **Saturation encodes importance** — strong accent (primary actions) → medium tint (active states) → light tint (backgrounds) → desaturated (low importance)
- **State colors follow universal mental models** — progression runs cool→warm (backlog=blue → in-progress=amber → done=green); error=red, stalled=amber, neutral info=gray/blue
- **Don't color numbers/text unless the color means something** — red=negative, green=positive, blue/purple=link; otherwise leave neutral
- **Match urgency-icon metaphor to the actual situation** — time-based (stale/aging) → clock/hourglass; needs-review → dot/eye/flag; risk/caution → triangle/shield; actual failure → exclamation/X
## Examples
- Kanban columns showing progression via subtle left-border color: blue (todo) → teal (preparing) → amber (active) → green (complete)
- A "this item has been sitting a while" state gets a clock icon and muted tint, not a red exclamation mark — reserve alarm iconography for actual failures
- Neutral gray avatar circles with initials instead of arbitrary rainbow avatar-background colors
## Exceptions
**Briefs with an explicit emotional/atmospheric direction** (cozy, playful, dark, vibrant) override the 90%-grayscale default for the *surface* palette — e.g. warm amber backgrounds for "cozy," inverted dark palette for "brutalist dark mode." Even then, color hierarchy (saturation = importance), state-color mental models, and "color must mean something" still apply; only the base surface choice flexes.
**The anxiety test.** Before applying a warning/critical treatment, ask whether a user seeing it would feel informed or anxious. Most "needs attention" states are not emergencies — dial back visual intensity (icon choice, saturation, contrast) to match actual severity rather than defaulting to alarm treatments.

View File

@ -0,0 +1,63 @@
---
type: reference
subtype: pattern/framework
title: State Machine Pattern — Transition Ownership
summary: When and how to model a multi-stage workflow as an explicit state machine, centered on the rule that the domain object — not the orchestrator or workers — owns its own legal transitions.
tags:
- type/reference
- domain/software-design
scope: global
last_updated: 2026-07-13
date: 2026-07-13
related:
- sandi-metz-code-philosophy
source: delta-refinery
---
# State Machine Pattern — Transition Ownership
## Purpose
Reference for deciding when a workflow needs a state machine and how to assign responsibility so the machine stays deterministic as the workflow grows.
## Core Principles
**Transition ownership is the root idea everything else derives from.** The domain object (the thing whose state is changing) is the only class allowed to decide whether a transition is legal. Orchestrators route based on state; workers request transitions via named events. Once this is right, most other design problems in the workflow disappear — and most drift traces back to this eroding.
## Decision Framework
Reach for a state machine when: the workflow has named stages, only certain transitions between them are valid, and the next action depends on current state. If you're seeing scattered `if`/`case` statements checking or setting a status field across multiple classes, that's the tell that an implicit state machine already exists and needs to be made explicit.
## Patterns
### Domain object owns transitions
The domain object exposes named event methods (`activate!`, `submit_for_review!`, `accept!`, `fail!(reason)`), never a raw setter. Only it decides if a transition from the current state is legal.
### Workers trigger events, never assign state
A worker that finishes its job calls `requirement.submit_for_review!`; it never does `requirement.state = :review_pending`. This keeps the legality check in one place.
### Runner routes by inspecting state
The orchestrator picks the next eligible unit of work by reading current state (`pipeline.next_requirement_for(:programmer)`), not by following a hardcoded sequence. A hardcoded sequence works for a demo but breaks the moment retries, recovery, or parallel work enter the picture — state-based routing doesn't care how an item got to its current state.
### State change as the signal
Workers don't need a separate notification channel back to the orchestrator. The orchestrator inspects the domain object's state after the worker returns to decide what happens next.
### Invalid transitions fail loudly
Guard every event method so an illegal transition (e.g. `accept!` from `:queued`) raises rather than silently succeeding or no-op'ing.
### Explicit failure policy
Pick one of: fail-fast (stop the whole run on first failure — good for single-item sequential runs), best-effort (skip failed items, continue routing others — good for batch runs), or recovery-and-retry (route failures to a recovery step, then re-enter the loop). The state machine itself is agnostic to which policy you pick; the runner encodes it.
## Anti-Patterns
- **Multiple classes assign `state = ...` directly** → transition-ownership violation; route through named events on the domain object
- **Runner hardcodes a fixed sequence of steps** → breaks under retries/recovery/parallelism; route by inspecting state instead
- **Workers fetch their own next unit of work or know about other workers** → boundary violation; that's the runner's job
- **Ambiguous or catch-all states (e.g. generic `:pending`)** → unclear terminal/guard semantics; keep states small and specific
- **Invalid transitions silently ignored** → hides bugs; raise instead
## Known Limitations
This model assumes a single domain object with a single state field per workflow unit. Cross-object transitions (where two domain objects' states must change atomically) need an explicit coordination layer on top of this pattern — it doesn't cover that case by itself.
## Related
- [[sandi-metz-code-philosophy]] — the "one reason to change" / registry-over-conditional principles this pattern applies to state routing specifically