<!--invar:critical-->
## ⚡ Critical Rules

| Always | Remember |
|--------|----------|
| **Verify** | `invar guard` — NOT pytest, NOT crosshair |
| **Core** | `@pre/@post` + doctests, NO I/O imports |
| **Shell** | Returns `Result[T, E]` from `returns` library |
| **Flow** | USBV: Understand → Specify → Build → Validate |

### Contract Rules (CRITICAL)

```python
# ❌ WRONG: Lambda must include ALL parameters
@pre(lambda x: x >= 0)
def calc(x: int, y: int = 0): ...

# ✅ CORRECT: Include defaults too
@pre(lambda x, y=0: x >= 0)
def calc(x: int, y: int = 0): ...

# ❌ WRONG: @post cannot access parameters
@post(lambda result: result > x)  # 'x' not available!

# ✅ CORRECT: @post only sees 'result'
@post(lambda result: result >= 0)
```

<!--/invar:critical-->



<!--invar:managed version="5.0"-->
# Project Development Guide

> **Protocol:** Follow [INVAR.md](./INVAR.md) — includes Check-In, USBV workflow, and Task Completion requirements.

## Check-In

> See [INVAR.md#check-in](./INVAR.md#check-in-required) for full protocol.

**Your first message MUST display:** `✓ Check-In: [project] | [branch] | [clean/dirty]`

**Actions:** Read `.invar/context.md`, then show status. Do NOT run guard at Check-In.

---

## Final

Your last message for an implementation task MUST display:

```
✓ Final: guard PASS | 0 errors, 2 warnings
```

Execute `invar guard` and show this one-line summary.


This is your sign-out. Completes the Check-In/Final pair.


---


## Project Structure

```
src/{project}/
├── core/    # Pure logic (@pre/@post, doctests, no I/O)
└── shell/   # I/O operations (Result[T, E] return type)
```

**Key insight:** Core receives data (strings), Shell handles I/O (paths, files).

## Quick Reference

| Zone | Requirements |
|------|-------------|
| Core | `@pre`/`@post` + doctests, pure (no I/O) |
| Shell | Returns `Result[T, E]` from `returns` library |

### Core vs Shell (Edge Cases)

- File/network/env vars → **Shell**
- `datetime.now()`, `random` → **Inject param** OR Shell
- Pure logic → **Core**

> Full decision tree: [INVAR.md#core-shell](./INVAR.md#decision-tree-core-vs-shell)



### Document Tools (DX-76)

| I want to... | Use |
|--------------|-----|
| View document structure | `invar doc toc <file> [--format text]` |
| Read specific section | `invar doc read <file> <section>` |
| Search sections by title | `invar doc find <pattern> <files...>` |
| Replace section content | `invar doc replace <file> <section>` |
| Insert new section | `invar doc insert <file> <anchor>` |
| Delete section | `invar doc delete <file> <section>` |

**Section addressing:** slug path (`requirements/auth`), fuzzy (`auth`), index (`#0/#1`), line (`@48`)

## Tool Selection

### Calling Methods (Priority Order)

Invar tools can be called in 3 ways. **Try in order:**

1. **MCP tools** (Claude Code with MCP enabled)
   - Direct function calls: `invar_guard()`, `invar_sig()`, etc.
   - No Bash wrapper needed

2. **CLI command** (if `invar` installed in PATH)
   - Via Bash: `invar guard`, `invar sig`, etc.
   - Install: `pip install invar-tools`

3. **uvx fallback** (always available, no install needed)
   - Via Bash: `uvx invar-tools guard`, `uvx invar-tools sig`, etc.

---

### Parameter Reference

**guard** - Verify code quality
```bash
# CLI
invar guard                      # Check changed files (default)
invar guard --all                # Check all files
```

**sig** - Show function signatures and contracts
```bash
# CLI
invar sig src/foo.py
invar sig src/foo.py::function_name
```

**map** - Find entry points
```bash
# CLI
invar map [path] --top 10
```

**refs** - Find all references to a symbol
```bash
# CLI
invar refs src/foo.py::MyClass
```

**doc*** - Document tools
```bash
# CLI
invar doc toc docs/spec.md
invar doc read docs/spec.md intro
```

---

### Quick Examples

```bash
# Verify after changes (all three methods identical)
invar guard                          # CLI
uvx invar-tools guard                # uvx

# Full project check
invar guard --all                    # CLI
uvx invar-tools guard --all          # uvx

# See function contracts
invar sig src/core/parser.py
uvx invar-tools sig src/core/parser.py
```

**Note**: All three methods now have identical default behavior.


## Documentation Structure

| File | Owner | Edit? | Purpose |
|------|-------|-------|---------|
| INVAR.md | Invar | No | Protocol (`invar update` to sync) |
| CLAUDE.md | User | Yes | Project customization (this file) |
| .invar/context.md | User | Yes | Project state, lessons learned |
| .invar/project-additions.md | User | Yes | Project rules → injected into CLAUDE.md |
| .invar/examples/ | Invar | No | **Must read:** Core/Shell patterns, workflow |

> **Before writing code:** Check Task Router in `.invar/context.md`

## Visible Workflow (DX-30)

For complex tasks (3+ functions), show 3 checkpoints in TodoList:

```
□ [UNDERSTAND] Task description, codebase context, constraints
□ [SPECIFY] Contracts and design decomposition
□ [VALIDATE] Guard results, Review Gate status, integration status
```

**BUILD is internal work** — not shown in TodoList.

**Show contracts before code.** See `.invar/examples/workflow.md` for full example.

## Phase Visibility (DX-51)

Each USBV phase transition requires a visible header:

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📍 /develop → SPECIFY (2/4)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```

**Three-layer visibility:**
- **Skill** (`/develop`) — Routing announcement
- **Phase** (`SPECIFY 2/4`) — Phase header (this section)
- **Tasks** — TodoWrite items

Phase headers are SEPARATE from TodoWrite. Phase = where you are; TodoWrite = what to do.

---

## Context Management (DX-54)

Re-read `.invar/context.md` when:
1. Entering any workflow (/develop, /review, etc.)
2. Completing a TodoWrite task (before moving to next)
3. Conversation exceeds ~15-20 exchanges
4. Unsure about project rules or patterns

**Refresh is transparent** — do not announce "I'm refreshing context."
Only show routing announcements when entering workflows.


---

## Commands (User-Invokable)

| Command | Purpose |
|---------|---------|
| `/audit` | Read-only code review (reports issues, no fixes) |
| `/guard` | Run Invar verification (reports results) |

## Skills (Agent-Invoked)

| Skill | Triggers | Purpose |
|-------|----------|---------|
| `/investigate` | "why", "explain", vague tasks | Research mode, no code changes |
| `/propose` | "should we", "compare" | Decision facilitation |
| `/develop` | "add", "fix", "implement" | USBV implementation workflow |
| `/review` | After /develop, `review_suggested` | Adversarial review with fix loop |

**Note:** Skills are invoked by agent based on context. Use `/audit` for user-initiated review.

Guard triggers `review_suggested` for: security-sensitive files, escape hatches >= 3, contract coverage < 50%.

---

## Workflow Routing (MANDATORY)

When user message contains these triggers, you MUST use the **Skill tool** to invoke the skill:

| Trigger Words | Skill Tool Call | Notes |
|---------------|-----------------|-------|
| "review", "review and fix" | `Skill(skill="review")` | Adversarial review with fix loop |
| "implement", "add", "fix", "update" | `Skill(skill="develop")` | Unless in review context |
| "why", "explain", "investigate" | `Skill(skill="investigate")` | Research mode, no code changes |
| "compare", "should we", "design" | `Skill(skill="propose")` | Decision facilitation |

**CRITICAL: You must call the Skill tool, not just follow the workflow mentally.**

The Skill tool reads `.claude/skills/<skill>/SKILL.md` which contains:
- Detailed phase instructions (USBV breakdown)
- Error handling rules
- Timeout policies
- Incremental development patterns (DX-63)

**Violation check (before writing ANY code):**
- "Did I call `Skill(skill="...")`?"
- "Am I following the SKILL.md instructions?"

---

## Routing Control (DX-42)

Agent announces routing decision before entering any workflow:

```
📍 Routing: /[skill] — [trigger or reason]
   Task: [summary]
```

**User can redirect with natural language:**
- "wait" / "stop" — pause and ask for direction
- "just do it" — proceed with /develop
- "let's discuss" — switch to /propose
- "explain first" — switch to /investigate

**Simple task optimization:** For simple tasks (single file, clear target, <50 lines), agent may offer:

```
📊 Simple task. Auto-orchestrate? [Y/N]
```

- Y → Full cycle without intermediate confirmations
- N → Normal step-by-step workflow

**Auto-review (DX-41):** When Guard outputs `review_suggested`, agent automatically
enters /review. Say "skip" to bypass.

<!--/invar:managed-->

<!--invar:project-->
<!-- ========================================================================
     PROJECT REGION - INVAR PROJECT ONLY
     This section is populated by .invar/project-additions.md via `invar dev sync`.
     For other projects, this region remains empty.
     ======================================================================== -->
<!--/invar:project-->

<!--invar:user-->
<!-- ========================================================================
     USER REGION - EDITABLE
     Add your team conventions and project-specific rules below.
     This section is preserved across `invar update` and `invar dev sync`.
     ======================================================================== -->

## Escape Hatch Discipline

**Rule:** `@invar:allow` is a last resort after all proper fixes fail. Do NOT use as a shortcut.

| ❌ WRONG | ✅ CORRECT |
|----------|-----------|
| Add `@invar:allow forbidden_import` to use `pathlib` in Core | Move function to Shell |
| Add `@invar:allow shell_result` to avoid `Result[T,E]` | Wrap return in `Result.success()` |
| Vague comment: "needed for now" | Document specific constraint that blocks proper fix |

**Before ANY escape hatch:**
1. **Try proper fix** — Shell migration, Result wrapping, contract adjustment
2. **Verify impossibility** — Document why the proper fix cannot work
3. **Justify in detail** — `# @invar:allow <rule>: [Specific reason + constraint]`

**Examples of valid justifications:**
- `# @invar:allow entry_point_too_thick: CLI parser must handle 15 subcommands, refactor blocked by typer limitations`
- `# @invar:allow forbidden_import: Type checking requires runtime introspection, no Shell alternative exists`

**Invalid:** "Will fix later", "Temporary", "For convenience"

**Enforcement:** 3+ escape hatches → automatic `/review`. Expect rejection if justification is weak.

## CONTRACT Phase Discipline (CRITICAL)

When a plan step name starts with `[CONTRACT]`, you are producing a **specification**, NOT implementation.

### CONTRACT Produces
- Function signatures with type hints
- `@pre`/`@post` decorators
- Docstrings with `>>>` examples
- Body: exactly `...` — NOTHING ELSE

### CONTRACT Must NOT Produce
- Conditional logic (`if`, `for`, `while`, `match`)
- Variable assignments
- Function calls in the body
- Return statements with computed values

### WRONG (actual P2-S1 violation)
```python
@deal.post(lambda result: result >= 0)
def count_tokens_approximate(text: str) -> int:
    """>>> count_tokens_approximate("Hello world")
    3
    """
    if not text:          # ← VIOLATION
        return 0          # ← VIOLATION
    words = len(text.split())
    return int(words * 1.3) + 1
```

### CORRECT
```python
@deal.post(lambda result: result >= 0)
def count_tokens_approximate(text: str) -> int:
    """Approximate token count (~1.3 tokens/word).
    >>> count_tokens_approximate("Hello world")
    3
    >>> count_tokens_approximate("")
    0
    """
    ...
```

### Validation (MANDATORY before completing CONTRACT step)
```bash
uv run python tools/validate_contracts_only.py <file>
```
Must output `PASS`. Include in evidence.

## Agent Delegation (CRITICAL)

When claiming a vectl step, check the `agent` field (`vectl show <step>`).
Spawn the matching `subagent_type` via Task tool.

| `agent` field | `subagent_type` | Context provided |
|---------------|-----------------|------------------|
| `python-engineer` | python-engineer | Full file access |
| `blind-tester` | blind-tester | `invar sig` output + RFC spec ONLY |
| (empty) | python-engineer | Default |

**blind-tester context rule:** Do NOT include implementation source files in the
prompt. Provide only `invar sig <file>` output + relevant RFC/spec sections from
the step's `refs`. The tester must write tests from the specification, not the code.

<!--/invar:user-->

---

*Generated by `invar init` v5.0. Customize the user section freely.*


<!-- VECTL:AGENTS:BEGIN -->
## Plan Tracking (vectl)

vectl tracks this repo's implementation plan as a structured `plan.yaml`:
what to do next, who claimed it, and what counts as done (with verification evidence).

Full guide: `uvx vectl guide`
Quick view: `uvx vectl status`

### Claim-time Guidance
- `uvx vectl claim` may emit a bounded Guidance block delimited by:
  - `--- VECTL:GUIDANCE:BEGIN ---`
  - `--- VECTL:GUIDANCE:END ---`
- For automation/CI: use `uvx vectl claim --no-guidance` to keep stdout clean.

### CLI vs MCP
- Source of truth: `plan.yaml` (channel-agnostic).
- If MCP is available (IDE / Claude host), prefer MCP tools for plan operations.
- Otherwise use CLI (`uvx vectl ...`).
- Evidence requirements are identical across CLI and MCP.

### plan.yaml — Managed File (DO NOT EDIT DIRECTLY)

`plan.yaml` is exclusively owned by vectl. Direct edits (Edit, Write, sed, or
any file tool) **will** corrupt plan state — vectl performs CAS writes, lock
recalculation, and schema validation on every save, none of which run on direct
edits.

**To modify plan state, ONLY use:**
- CLI: `uvx vectl claim`, `uvx vectl complete`, `uvx vectl mutate`, etc.
- MCP: `vectl_claim`, `vectl_complete`, `vectl_mutate`, etc.

If a vectl command fails, report the error — do **not** edit `plan.yaml`
directly as a workaround. Use `uvx vectl guide stuck` for troubleshooting.

### Rules
- One claimed step at a time.
- Evidence is mandatory when completing (commands run + outputs + gaps).
- Spec uncertainty: leave `# SPEC QUESTION: ...` in code, do not guess.

### Freeze Exception Policy

During freeze/gate checkpoints, `plan.yaml` is **exempt** from the clean-tree requirement
for metadata-only mutations. This exemption is necessary because vectl writes completion
evidence (status, timestamps, evidence content) to plan.yaml as part of its normal operation.

**Allowed During Freeze (Metadata-Only):**
- Step status transitions (pending → claimed → done)
- `evidence` field updates
- `claimed_by`, `claimed_at` timestamp fields
- `completed_at` timestamps

**NOT Allowed During Freeze (Structural Changes):**
- Adding/removing steps or phases
- Modifying step descriptions or verification criteria
- Changing dependencies (`depends_on`)
- Any change to `plan.yaml` outside the metadata fields listed above

**Freeze Step Procedure (CRITICAL - Follow Exactly):**

1. **Pre-freeze Phase (BEFORE declaring freeze):**
   ```bash
   # Commit ALL structural changes first
   git add plan.yaml
   git commit -m "chore: structural plan updates before freeze"
   
   # Verify clean tree
   git status --porcelain  # Must be empty
   ```

2. **Freeze Declaration:**
   ```bash
   # Record commit hash
   git rev-parse HEAD
   
   # Verify NO structural changes exist (added or removed keys)
   git diff HEAD -- plan.yaml | grep -E '^[-+].*\<(id|name|description|depends_on|verification|refs|context)\>:' || true
   # Must be empty
   ```

3. **During Freeze (between freeze and gate):**
   - Only metadata changes allowed (status, evidence, claimed_by, claimed_at)
   - To add structural changes, must break freeze, commit, and re-declare

**Freeze Step Verification:**
```bash
# 1. Verify no non-plan files are modified
git status --porcelain | grep -v 'plan.yaml' || true
# Must be empty (non-empty overall status is allowed when ONLY plan.yaml is modified)

# 2. If plan.yaml is modified, verify metadata-only changes:
git diff plan.yaml | grep -E '^[-+].*\<(id|name|description|depends_on|verification|refs|context)\>:' || true
# Must be empty (no structural key changes)

# 3. Record freeze commit hash
git rev-parse HEAD

# 4. Verify no structural changes in working tree (same check)
git diff plan.yaml | grep -E '^[-+].*\<(id|name|description|depends_on|verification|refs|context)\>:' || true
# Must be empty
```

**Gate Step Verification:**
```bash
# 1. Confirm freeze commit hash matches expected
git rev-parse HEAD  # Compare against recorded freeze hash

# 2. Verify plan.yaml changes are metadata-only
git diff plan.yaml | grep -E '^[-+].*\<(id|name|description|depends_on|verification|refs|context)\>:' || true
# Must be empty (no structural keys changed)

# 3. Verify non-plan files are clean
git status --porcelain | grep -v 'plan.yaml' || true
# Must be empty (non-empty overall status is allowed when ONLY plan.yaml is modified)
```

**Structural Keys (MUST NOT change during freeze/gate):**
- `id` — Step/phase identifiers
- `name` — Step/phase names
- `description` — Step descriptions
- `depends_on` — Dependency edges
- `verification` — Verification criteria
- `refs` — Reference links
- `context` — Step context strings

**Violation Recovery:**
If structural changes are found during gate verification:
1. Document the violation in evidence
2. Create fix steps via vectl mutate (structural changes allowed outside freeze)
3. Commit all structural changes
4. Declare new freeze with clean tree
5. Proceed with gate review

**Violation Recovery:**
If freeze is found to have structural changes:
1. Document the violation in evidence
2. Create fix steps via vectl mutate (structural changes allowed outside freeze)
3. Commit all structural changes
4. Declare new freeze with clean tree
5. Proceed with gate review

**Rationale:** vectl's evidence-based workflow requires mutating plan.yaml to record completion evidence.
Excluding plan.yaml from freeze would require post-freeze commits just for metadata, creating a circular
dependency where "freeze → complete step → commit → freeze is broken". The metadata-only exemption
resolves this while preserving freeze integrity for actual code changes.

### For Architects / Planners
- **Design Mode**: Run `uvx vectl guide --on planning` to learn the Architect Protocol.
- **Ambiguity = Failure**: Workers will hallucinate if steps are vague.
- **Constraint Tools**:
  - `--evidence-template`: Force workers to provide specific proof (e.g., "Paste logs here").
  - `--refs`: Pin specific files (e.g., "src/auth.py") to the worker's context.
<!-- VECTL:AGENTS:END -->
