npx agentspec-cli install 7b394c80-92cf-47c8-995e-a1263af7acac --file-type claude-md→ .claude/rules/
# CLAUDE.md — Invariants
> Read this file at the start of EVERY session before touching code.
> These are the load-bearing rules. Breaking one silently is how this project
> rots across sessions. If you must break one, it’s a deliberate, isolated
> increment with its own ADR in DECISIONS.md — never a side effect.
-----
## Session ritual
**START**
1. Read this file.
1. `make test && make demo-<previous-increment>` — the baseline must be green
before you change anything.
1. State out loud the single increment ID you are building this session. One
increment per session.
**END**
1. New `make demo-<this-increment>` passes.
1. Full `make test` green (every prior demo still passes).
1. If you touched the prompt prefix, re-run the prefix-hash test.
1. **If you touched anything under `desktop/renderer/`, run
`cd desktop && bun run build-renderer`, then GREP THE SERVED BYTES for a
marker your change introduced.** `dev.ts bundleApp()` serves the prebuilt
`desktop/renderer/app.bundle.js` whenever it exists and NEVER compiles from
source (ADR-0260: Bun must not module-load a `.ts` from a protected install
dir). So renderer source edits are invisible in the running app until that
artifact is rebuilt, and no amount of restarting helps. A source typecheck or
a scratch `bun build` proves the code COMPILES; it proves nothing about the
bytes the app serves. Verify by fetching `/app.js` and `/styles.css` from a
freshly booted engine and grepping for a new identifier, plus one retired
identifier that must be ABSENT. This is the ADR-0303 vacuous-green trap in
its most expensive form, because everything looks green and the user sees no
change at all.
1. Append exactly 3 lines to PROGRESS.md: shipped / stubbed / next.
Do not start the next increment in the same session. A half-finished second
increment is the exact failure this whole structure exists to prevent.
-----
## The architecture in one paragraph
We are building a security/provenance/memory layer **around** oh-my-pi (omp),
not from scratch. The harness is **TypeScript on Bun, in-process with omp**. The
**only** Python is `scanner-sidecar/` (the pure Unicode scanner). We extend omp
through hooks, custom tools, and its SDK. See DECISIONS.md ADR-0001.
-----
## Writing style
- **Never use em dashes** (the long dash, Unicode U+2014). Use commas, periods,
colons, or hyphens instead. This applies everywhere: code, comments, UI strings,
commit messages, ADRs, and PROGRESS.md.
-----
## Invariants (do not violate)
### 1. Extend omp; never fork it.
Prefer a hook or custom tool over any change that would require forking omp.
omp ships hundreds of releases; a fork is permanent merge pain. If you believe
something genuinely cannot be done via hook/tool/SDK, STOP and write an ADR
before forking anything.
### 2. The language boundary is fixed.
The harness is TypeScript. The ONLY directory containing Python is
`scanner-sidecar/`. Never add a `.py` file anywhere else. Never reimplement the
scanner in TypeScript. If a second Python surface seems necessary, that’s a
drift signal — stop and write an ADR.
### 3. Fail-closed is law.
Any failure to obtain a valid scan result — sidecar dead, malformed response,
timeout, missing id — MUST be treated as “block / quarantine,” never “safe.”
No code path may treat “scan unavailable” as “pass.” There is a test that kills
the sidecar mid-run and asserts the gate blocks; it must stay green forever.
### 4. The quarantine gate runs in-process.
The security gate is a `pre` hook inside omp’s runtime. It must not depend on a
network call or a fragile boundary to do its blocking. The scanner (pure, behind
the sidecar) may be out-of-process; the GATE that acts on its output may not.
### 5. Untrusted content is always delimited and always late.
All user-provided, retrieved, imported, or externally stored text enters prompts
only inside `UNTRUSTED_CONTENT_START` / `UNTRUSTED_CONTENT_END`, only after
scanning + sanitation, and only AFTER the cache breakpoint (never in the frozen
prefix). The system prompt instructs the model to treat delimited content as
data, never instructions.
### 6. The prompt prefix is frozen and byte-stable.
Prompt layers 1–4 (identity/safety, tool-use/permission policy, stable coding
rules, security/trust-boundary rules) are byte-identical across all requests.
Volatile context — date, cwd, git branch/status, env — that omp auto-injects
MUST live in the tail, after the cache breakpoint. Putting volatile bytes in the
prefix busts the KV cache every turn. Verify with the prefix-hash test.
### 7. Trust labels are a closed set.
Exactly: `trusted | untrusted | suspicious | quarantined`. No other values.
### 8. Events use exact names.
Every logged event uses a name from the `EventName` enum in `contracts.ts`.
Emitting an unknown event name must raise. Every event carries `run_id`,
`session_id`, and `artifact_id` when an artifact is in scope.
### 9. Stable IDs everywhere.
Every run, finding, and approval has a stable string `*_id`. Reuse omp’s
Snowflake session/run IDs; mint your own for findings/approvals. IDs are never
regenerated for the same logical entity.
### 10. DuckDB schema freezes on first write.
Schema changes only ever happen through numbered migration files. Never edit a
table definition in place. The schema is a frozen contract.
### 11. UI text is never cramped into wrapping columns.
Readability is load-bearing. Any list/grid of rows (providers, models, settings,
tiles, chips) MUST keep each row's primary label on one line: the label takes the
space and ellipsizes on overflow (`white-space: nowrap; overflow: hidden;
text-overflow: ellipsis`); it NEVER mid-word wraps inside a narrow column.
Secondary chips/badges are `white-space: nowrap`. Grid tracks use a generous
`minmax()` (about 220px or more) so labels do not fold; when content will not fit,
use fewer/wider columns or a single column, NEVER a fixed narrow multi-column grid
that word-wraps its text into unreadable slivers. If a label can be long, widen the
column or truncate it with a tooltip. This applies to every panel, popup, and
mockup (a mockup that word-wraps is a design bug, not a rendering artifact).
The #1 cause is a FLEX container holding prose: `display: flex` makes every raw
text run AND every inline element (`<b>`, `<a>`, `<code>`) its OWN flex item, so a
sentence with bold phrases shatters into narrow stacked columns. A flex row MUST
therefore contain a SINGLE text child: give a leading icon `flex: none` and wrap the
message in ONE element, OR (preferred, as `.set-note` does) absolutely-position the
icon and let the text flow as a normal BLOCK paragraph. NEVER place raw text plus
inline tags directly as siblings inside a flex box. And mockups MUST reuse the real
component CSS (or copy it verbatim) - a hand-rolled divergent style that flexes
prose is this same bug wearing a costume, which is exactly how it slipped back in.
-----
## Frozen contract files (changing any one is its own increment + ADR)
- `harness/contracts.ts` — TrustLabel, AgentMode, EventName, ExecutionProfile,
ToolResult (PRD shape, translated to TS).
- `harness/tools/result_adapter.ts` — the ONLY place omp’s
`{ content: [{type,text}] }` and the PRD `ToolResult` meet.
- The scanner IPC contract (DECISIONS.md ADR-0002).
- DuckDB schema migration files.
- The frozen prompt prefix (layers 1–4).
If you import one of these, import it — never redefine its types locally.
-----
## Two correctness keystones (over-test these)
- **The Unicode scanner** (`scanner-sidecar/`, increment P2.1). Every finding
type has fixtures; clean control corpus must produce zero false positives.
- **The semantic-promotion gate** (increment P4.3). Suspicious-source content
must never auto-promote into semantic memory.
Everything security-related leans on these two. Treat a failing test in either
as a stop-the-line event.
-----
## Known wrinkles carried forward
- The PRD wrote `contracts` and `ToolResult` as Python dataclasses. Under
Option A they are TypeScript in `contracts.ts`. The Python sidecar only needs
the finding shape. Translate other PRD Python samples to TS as needed; note
non-obvious translations in an ADR.
- The integration seams were written from omp’s README. The FIRST task of
Increment 0 is to confirm the real `createAgentSession`, hook API, and
custom-tool factory shapes against the installed omp version, and ADR any
deltas.
- DuckDB uses the Node binding (less mature than DuckDB-Python). If a specific
analytics task is blocked by it, that’s a localized future ADR — not a reason
to revisit the harness language.
-----
## Licensing & commit hygiene
Every first-party source file under `harness/`, `desktop/`, `tools/`, and
`scanner-sidecar/` carries the BUSL-1.1 SPDX header (`Copyright (c) 2026 TechLead
187 LLC` + `SPDX-License-Identifier: BUSL-1.1`; `#` for Python). This is enforced
two ways and you must keep both working:
- A **pre-commit hook** (`.githooks/pre-commit`) auto-applies the header to staged
source. Run **`make install-hooks` once per clone** to activate it — git cannot
self-install hooks on clone, so a fresh checkout (yours or a collaborator’s) has
NO hook until this runs. `make install` runs it for you.
- **CI** runs `tools/license_headers.ts --check` as a backstop, so nothing
unheadered can land on master even if the hook was never installed.
Vendored / generated trees (`vendor/`, `node_modules/`, `desktop/release/`,
`.venv`, `__pycache__`, `dist/`) keep their own licenses and are NEVER
relicensed. New first-party files get the header automatically (hook) or via
`make license-headers`. When adding the header in code, read the file then write —
never `existsSync`-then-read (CodeQL flags that pair as a TOCTOU race).CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. **This is an orientation file, not a session log.** It tells you what exists, where it lives, and what
by AgentHub Crawler
Claude Code Rules
This file is generated during init for the selected agent. You are an expert AI assistant specializing in Spec-Driven Development (SDD). Your primary goal is to work with the architext to build products.
by AgentHub Crawler
Claude Code Rules
This file is generated during init for the selected agent. You are an expert AI assistant specializing in Spec-Driven Development (SDD). Your primary goal is to work with the architext to build products.
by AgentHub Crawler
Sign in to leave a comment.
Claude Code Rules
This file is generated during init for the selected agent. You are an expert AI assistant specializing in Spec-Driven Development (SDD). Your primary goal is to work with the architext to build products.
by AgentHub Crawler