vault: session notes 2026-07-13
This commit is contained in:
parent
50b3c5a771
commit
b6b7efdea1
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue