diff --git a/convention/ai-agent-rules.md b/convention/ai-agent-rules.md new file mode 100644 index 0000000..14e61f8 --- /dev/null +++ b/convention/ai-agent-rules.md @@ -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 diff --git a/convention/phlex-component-design.md b/convention/phlex-component-design.md new file mode 100644 index 0000000..9ebe502 --- /dev/null +++ b/convention/phlex-component-design.md @@ -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 diff --git a/convention/tdd-methodology.md b/convention/tdd-methodology.md new file mode 100644 index 0000000..48363cd --- /dev/null +++ b/convention/tdd-methodology.md @@ -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 diff --git a/howto/devcontainer-sandbox-setup.md b/howto/devcontainer-sandbox-setup.md new file mode 100644 index 0000000..65c943d --- /dev/null +++ b/howto/devcontainer-sandbox-setup.md @@ -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 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 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. diff --git a/journal/2026-07-13.md b/journal/2026-07-13.md index 46a2798..514fb1a 100644 --- a/journal/2026-07-13.md +++ b/journal/2026-07-13.md @@ -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) diff --git a/reference/design-color-rules.md b/reference/design-color-rules.md new file mode 100644 index 0000000..95f7bc7 --- /dev/null +++ b/reference/design-color-rules.md @@ -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. diff --git a/reference/state-machine-pattern.md b/reference/state-machine-pattern.md new file mode 100644 index 0000000..5788a8c --- /dev/null +++ b/reference/state-machine-pattern.md @@ -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