# Plan: OBPI-0.0.30-02 — complexity-guide Skill

## Context

OBPI-0.0.30-02 authors the `complexity-guide` operator skill — the second of
five OBPIs in ADR-0.0.30 (complexity-authoring-guidance cluster). OBPI-01 (CLI
verb `gz complexity guide`) and OBPI-03 (authoring hint engine) are both
`Completed`; this OBPI provides the operator routing surface so the now-live
CLI verb satisfies tool-skill-runbook-alignment Invariant 1.

The skill is skill-doc only (no Python source changes). Implementation is:
1. Author canonical `.gzkit/skills/complexity-guide/SKILL.md`
2. Author `tests/skills/test_complexity_guide.py` in TDD RED→GREEN order
3. Run `gz agent sync control-surfaces` to propagate vendor mirrors
4. Register BDD waiver for skill-only OBPI

## Files to Create

| File | Purpose |
|------|---------|
| `.gzkit/skills/complexity-guide/SKILL.md` | Canonical skill (create dir first) |
| `tests/skills/test_complexity_guide.py` | TDD tests covering REQ-0.0.30-02-01..06 |

## Files to Modify

| File | Change |
|------|--------|
| `data/behave_coverage_waivers.json` | Add rationale key + OBPI-0.0.30-02 waiver entry |

## Files Auto-Generated (do not edit directly)

| File | Generated by |
|------|-------------|
| `.claude/skills/complexity-guide/SKILL.md` | `gz agent sync control-surfaces` |
| `.agents/skills/complexity-guide/SKILL.md` | `gz agent sync control-surfaces` |
| `.github/skills/complexity-guide/SKILL.md` | `gz agent sync control-surfaces` |

---

## Step 1: TDD Red — Write failing tests

Create `tests/skills/test_complexity_guide.py` before the SKILL.md exists.
All tests fail on `FileNotFoundError` at this stage — that is the valid RED state.

**Test class map (REQ → class):**

| REQ | Class |
|-----|-------|
| REQ-0.0.30-02-01 | `TestSkillFrontmatter` |
| REQ-0.0.30-02-02 | `TestOperatorMoment` |
| REQ-0.0.30-02-03 | `TestOutputContract` |
| REQ-0.0.30-02-04 | `TestGzCommandResolution` |
| REQ-0.0.30-02-05 | `TestCrossReference` |
| REQ-0.0.30-02-06 | `TestVendorMirrorEquality` |
| doctrine (no @covers) | `TestNoOperatorPersonalEmail` |

**File header:**
```python
"""Skill-shape tests for `.gzkit/skills/complexity-guide/SKILL.md`.

Pins REQ-0.0.30-02-01 through REQ-0.0.30-02-06 from
`docs/design/adr/foundation/ADR-0.0.30-complexity-authoring-guidance/obpis/OBPI-0.0.30-02-complexity-guide-skill.md`.

REQ-07 (tests cover all above) is satisfied by the existence of test classes
below. REQ-08 (TDD, no subprocess spawn) is structural. REQ-09 (no PII)
is asserted by TestNoOperatorPersonalEmail without @covers.
"""
```

**Imports and paths:**
```python
from __future__ import annotations
import re
import unittest
from pathlib import Path
from typing import cast
import yaml
from gzkit.traceability import covers

PROJECT_ROOT = Path(__file__).resolve().parents[2]
SKILL_DIR = PROJECT_ROOT / ".gzkit" / "skills" / "complexity-guide"
SKILL_PATH = SKILL_DIR / "SKILL.md"
VENDOR_MIRROR_PATHS = (
    PROJECT_ROOT / ".claude" / "skills" / "complexity-guide" / "SKILL.md",
    PROJECT_ROOT / ".agents" / "skills" / "complexity-guide" / "SKILL.md",
    PROJECT_ROOT / ".github" / "skills" / "complexity-guide" / "SKILL.md",
)
```

**Helper functions** (identical to `test_complexity_advisor.py`):
- `_read_skill_text() -> str`
- `_parse_frontmatter(text) -> dict`
- `_section_body(text, heading) -> str`

**TestSkillFrontmatter** (REQ-0.0.30-02-01):
- `test_frontmatter_required_identity_fields_present`: `name=="complexity-guide"`, description length 40..1024
- `test_frontmatter_lifecycle_fields_present`: `lifecycle_state=="active"`, `owner=="gzkit-governance"`, last_reviewed is date
- `test_skill_version_is_initial_release`: `metadata["skill-version"]=="0.1.0"`
- `test_description_triggers_on_operator_phrases`: description contains "authoring-time", "complexity", "guide" + "hint" or "preview"

**TestOperatorMoment** (REQ-0.0.30-02-02):
- `test_ad_hoc_authoring_time_review_named_as_primary_surface`: body contains "authoring-time" and `gz complexity guide` CLI invocation
- `test_first_stop_authoring_surface_named`: body names this as "first-stop" or "first stop" authoring surface

**TestOutputContract** (REQ-0.0.30-02-03):
- `test_output_contract_section_present`: `_section_body(text, "Output Contract")` non-empty (>40 chars)
- `test_output_contract_names_inline_hint_prose_as_default`: Output Contract body contains "in-line hint prose" or "inline hint prose"
- `test_output_contract_names_json_as_machine_readable`: Output Contract body contains `"--json"`

**TestGzCommandResolution** (REQ-0.0.30-02-04):
- `test_gz_command_field_set_to_complexity_guide`: `fm["gz_command"] == "complexity guide"`
- `test_declared_verb_resolves_in_live_argparse_parser`: import `_build_parser`; parse `["complexity", "guide", "--help"]`; expect SystemExit(0)

**TestCrossReference** (REQ-0.0.30-02-05):
- `test_complexity_advisor_sister_skill_named`: body contains "complexity-advisor"
- `test_trigger_time_vs_authoring_time_distinction_present`: body contains both "trigger-time" (or "trigger time") and "authoring-time" (or "authoring time") and explains the distinction

**TestVendorMirrorEquality** (REQ-0.0.30-02-06):
- `test_each_vendor_mirror_matches_canonical`: each mirror in `VENDOR_MIRROR_PATHS` exists and bytes-equal to canonical

**TestNoOperatorPersonalEmail** (doctrine):
- `test_skill_contains_no_personal_email_addresses`: regex-scan skill body; only `@users.noreply.github.com` allowed
- `test_test_module_contains_no_personal_email_addresses`: same scan on test file itself

---

## Step 2: TDD Green — Author canonical SKILL.md

Create `.gzkit/skills/complexity-guide/` directory and `SKILL.md`.

**Frontmatter:**
```yaml
---
name: complexity-guide
description: Preview authoring-time complexity hints before committing. Use when the operator says "authoring-time complexity hint", "complexity guide preview", "preview before commit", or "advise-band hints".
category: code-quality
lifecycle_state: active
owner: gzkit-governance
last_reviewed: 2026-05-09
metadata:
  skill-version: "0.1.0"
  govzero-framework-version: "v6"
  govzero_layer: "Layer 3 - File Sync"
gz_command: complexity guide
model: sonnet
---
```

**Body sections (H2 headings):**

1. **`# complexity-guide`** — intro paragraph: wraps `gz complexity guide` (ADR-0.0.30 OBPI-0.0.30-01); is the authoring-time hint surface for the four-ADR cluster; use **before** `gz complexity advise` or commit.

2. **`## When to Use`** — two operator moments:
   - Ad-hoc authoring-time review (primary surface, while editing)
   - Preflight complexity check (before committing)
   Name this explicitly as the **first-stop authoring surface** (the sister `complexity-advisor` is the second stop at trigger time).

3. **`## Operator Moment: Authoring-Time Review`** — show CLI invocations:
   ```bash
   gz complexity guide src/gzkit/commands/validate.py
   gz complexity guide src/gzkit/ --json
   ```
   Describe default in-line hint prose (one block per hint with archetype, band, guidance, move). Mention `--json` emits canonical `AuthoringHint` serialization.

4. **`## Output Contract`** — declare form as **in-line hint prose** (default):
   - Archetype, Band, Guidance (doctrinal-frame headline), Move
   - `--json` emits canonical `AuthoringHint` Pydantic serialization

5. **`## Trigger-Time vs. Authoring-Time`** — short table or paragraph explaining the two surfaces (this skill vs. `complexity-advisor`), when to use each.

6. **`## Related`** — cross-references:
   - Sister skill: `.gzkit/skills/complexity-advisor/SKILL.md` (ADR-0.0.29)
   - Manpage: `docs/user/manpages/complexity-guide.md`
   - Runbook: `docs/user/runbook.md` § Complexity doctrine surfaces
   - Parent ADR: ADR-0.0.30

Run tests after authoring → all 15+ tests should go GREEN.

---

## Step 3: Vendor sync

```bash
uv run gz agent sync control-surfaces
```

Expect: `.claude/skills/complexity-guide/SKILL.md`, `.agents/skills/complexity-guide/SKILL.md`, `.github/skills/complexity-guide/SKILL.md` created and byte-identical to canonical. Post-sync diff empty.

Re-run `TestVendorMirrorEquality` tests to confirm GREEN.

---

## Step 4: Register BDD waiver

In `data/behave_coverage_waivers.json`, add:

1. A new key under `default_rationale`:
   ```json
   "adr-0.0.30-02-skill-only-bdd-deferred": "OBPI-0.0.30-02 is a skill-doc-only OBPI with no CLI or Python source changes. The verification surface is the SKILL.md document shape tested by Python unit tests in tests/skills/test_complexity_guide.py (14 tests covering REQ-01..06 via @covers). Behave BDD scenarios for the operator-runnable surface (gz complexity guide) are in OBPI-0.0.30-01 (CLI verb). Skill-layer OBPIs do not add scenario-testable CLI surfaces independent of the CLI OBPI."
   ```

2. A new entry under `waivers`:
   ```json
   "OBPI-0.0.30-02-complexity-guide-skill": {
     "rationale": "adr-0.0.30-02-skill-only-bdd-deferred",
     "status_at_landing": "Draft"
   }
   ```

---

## Step 5: Verification

```bash
uv run gz arb ruff
uv run gz arb typecheck
uv run gz arb step --name unittest -- uv run -m unittest -q
uv run gz arb step --name unittest -- uv run -m unittest tests/skills/test_complexity_guide.py -v
uv run gz validate --documents --surfaces
uv run gz agent sync control-surfaces
uv run gz covers OBPI-0.0.30-02-complexity-guide-skill --json
```

All tests GREEN; lint/type clean; docs build clean; sync produces empty diff; covers shows `uncovered_reqs == 0`.

---

## TDD Discipline Note

Tests are written **before** SKILL.md exists. RED is `FileNotFoundError` on
`SKILL_PATH.read_text(...)`. GREEN is reached by authoring the SKILL.md. No
subprocess spawning in tests — verb resolution uses `_build_parser()` directly.
Vendor mirror test passes only after Step 3 sync completes.
