invar-tools 1.3.0__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. invar/shell/claude_hooks.py +387 -0
  2. invar/shell/commands/guard.py +2 -0
  3. invar/shell/commands/hooks.py +74 -0
  4. invar/shell/commands/init.py +30 -0
  5. invar/shell/commands/template_sync.py +42 -11
  6. invar/shell/commands/test.py +1 -1
  7. invar/templates/CLAUDE.md.template +25 -5
  8. invar/templates/config/CLAUDE.md.jinja +16 -0
  9. invar/templates/config/context.md.jinja +11 -6
  10. invar/templates/context.md.template +35 -18
  11. invar/templates/hooks/PostToolUse.sh.jinja +102 -0
  12. invar/templates/hooks/PreToolUse.sh.jinja +74 -0
  13. invar/templates/hooks/Stop.sh.jinja +23 -0
  14. invar/templates/hooks/UserPromptSubmit.sh.jinja +77 -0
  15. invar/templates/hooks/__init__.py +1 -0
  16. invar/templates/manifest.toml +2 -2
  17. invar/templates/protocol/INVAR.md +105 -6
  18. invar/templates/skills/develop/SKILL.md.jinja +4 -7
  19. invar/templates/skills/investigate/SKILL.md.jinja +4 -7
  20. invar/templates/skills/propose/SKILL.md.jinja +4 -7
  21. invar/templates/skills/review/SKILL.md.jinja +63 -15
  22. {invar_tools-1.3.0.dist-info → invar_tools-1.3.1.dist-info}/METADATA +1 -1
  23. {invar_tools-1.3.0.dist-info → invar_tools-1.3.1.dist-info}/RECORD +28 -21
  24. {invar_tools-1.3.0.dist-info → invar_tools-1.3.1.dist-info}/WHEEL +0 -0
  25. {invar_tools-1.3.0.dist-info → invar_tools-1.3.1.dist-info}/entry_points.txt +0 -0
  26. {invar_tools-1.3.0.dist-info → invar_tools-1.3.1.dist-info}/licenses/LICENSE +0 -0
  27. {invar_tools-1.3.0.dist-info → invar_tools-1.3.1.dist-info}/licenses/LICENSE-GPL +0 -0
  28. {invar_tools-1.3.0.dist-info → invar_tools-1.3.1.dist-info}/licenses/NOTICE +0 -0
@@ -1,3 +1,16 @@
1
+ <!--invar:critical-->
2
+ ## ⚡ Critical Rules
3
+
4
+ | Always | Remember |
5
+ |--------|----------|
6
+ | **Verify** | `invar_guard` — NOT pytest, NOT crosshair |
7
+ | **Core** | `@pre/@post` + doctests, NO I/O imports |
8
+ | **Shell** | Returns `Result[T, E]` from `returns` library |
9
+ | **Flow** | USBV: Understand → Specify → Build → Validate |
10
+
11
+ <!--/invar:critical-->
12
+
13
+ <!--invar:managed version="5.0"-->
1
14
  # Project Development Guide
2
15
 
3
16
  > **Protocol:** Follow [INVAR.md](./INVAR.md) — includes Check-In, USBV workflow, and Task Completion requirements.
@@ -120,16 +133,23 @@ When user message contains these triggers, you MUST invoke the corresponding ski
120
133
  - "Am I in a workflow?"
121
134
  - "Did I invoke the correct skill?"
122
135
 
123
- ---
136
+ <!--/invar:managed-->
124
137
 
138
+ <!--invar:project-->
125
139
  ## Project-Specific Rules
126
140
 
127
- <!-- Add your team conventions below -->
141
+ <!-- Add your project structure and rules here -->
128
142
 
129
- ## Overrides
143
+ <!--/invar:project-->
130
144
 
131
- <!-- Document any exceptions to INVAR.md rules -->
145
+ <!--invar:user-->
146
+ <!-- ========================================================================
147
+ USER REGION - EDITABLE
148
+ Add your team conventions and project-specific rules below.
149
+ This section is preserved across invar update and sync-self.
150
+ ======================================================================== -->
151
+ <!--/invar:user-->
132
152
 
133
153
  ---
134
154
 
135
- *Generated by `invar init`. Customize freely.*
155
+ *Generated by `invar init` v5.0. Customize the user section freely.*
@@ -1,3 +1,19 @@
1
+ <!--invar:critical-->
2
+ ## ⚡ Critical Rules
3
+
4
+ | Always | Remember |
5
+ |--------|----------|
6
+ {% if syntax == "mcp" -%}
7
+ | **Verify** | `invar_guard` — NOT pytest, NOT crosshair |
8
+ {% else -%}
9
+ | **Verify** | `invar guard` — NOT pytest, NOT crosshair |
10
+ {% endif -%}
11
+ | **Core** | `@pre/@post` + doctests, NO I/O imports |
12
+ | **Shell** | Returns `Result[T, E]` from `returns` library |
13
+ | **Flow** | USBV: Understand → Specify → Build → Validate |
14
+
15
+ <!--/invar:critical-->
16
+
1
17
  <!--invar:managed version="{{ version }}"-->
2
18
  # Project Development Guide
3
19
 
@@ -55,17 +55,22 @@
55
55
 
56
56
  ## Documentation Structure
57
57
 
58
- | File | Owner | Edit? |
59
- |------|-------|-------|
60
- | INVAR.md | Invar | No — use `invar update` |
61
- | CLAUDE.md | User | Yes |
62
- | .invar/context.md | User | Yes (this file) |
63
- | .invar/examples/ | Invar | No |
58
+ | File | Owner | Edit? | When to Read |
59
+ |------|-------|-------|--------------|
60
+ | INVAR.md | Invar | No — use `invar update` | Protocol details, Six Laws |
61
+ | CLAUDE.md | User | Yes | Project rules |
62
+ | .invar/context.md | User | Yes (this file) | Session start, refresh |
63
+ | .invar/examples/ | Invar | No | Learning Core/Shell patterns |
64
64
 
65
65
  **Decision rule:** Is this Invar protocol or project-specific?
66
66
  - Protocol content → Already in INVAR.md, don't duplicate
67
67
  - Project-specific → Add to CLAUDE.md or here
68
68
 
69
+ **When to consult INVAR.md:**
70
+ - Unsure about Core/Shell separation rules
71
+ - Need to understand Six Laws principles
72
+ - Checking USBV workflow details
73
+
69
74
  ## Technical Debt
70
75
 
71
76
  {% if syntax == "mcp" -%}
@@ -2,11 +2,7 @@
2
2
 
3
3
  *Last updated: [DATE]*
4
4
 
5
- ## Current State
6
-
7
- - Phase: [current development phase]
8
- - Working on: [current task or feature]
9
- - Blockers: None
5
+ <!-- DX-58: Keep this file under 200 lines for efficient Check-In -->
10
6
 
11
7
  ## Key Rules (Quick Reference)
12
8
 
@@ -20,7 +16,7 @@
20
16
  1. Understand → 2. Specify (contracts first) → 3. Build → 4. Validate
21
17
 
22
18
  ### Verification
23
- - `invar guard` = static + doctests + CrossHair + Hypothesis
19
+ - `invar_guard` = static + doctests + CrossHair + Hypothesis
24
20
  - Final must show: `✓ Final: guard PASS | ...`
25
21
 
26
22
  ## Self-Reminder
@@ -41,13 +37,39 @@
41
37
 
42
38
  ---
43
39
 
44
- ## Recent Decisions
40
+ ## Current State
41
+
42
+ - **Phase:** [current development phase]
43
+ - **Working on:** [current task or feature]
44
+ - **Blockers:** None
45
+
46
+ ## Active Work
47
+
48
+ **Current focus:** [describe current focus]
45
49
 
46
- 1. [Decision summary] - [Brief rationale]
50
+ **Completed recently:**
51
+ - [recent completion 1]
52
+ - [recent completion 2]
53
+
54
+ ---
47
55
 
48
56
  ## Lessons Learned
49
57
 
50
- 1. [Pitfall or issue] [Solution or workaround]
58
+ <!-- DX-58: Keep last 5-10 lessons here; older ones can be moved to .invar/archive/ if needed -->
59
+
60
+ 1. [Lesson] - [Brief explanation]
61
+
62
+ ---
63
+
64
+ ## Tool Priority
65
+
66
+ | Task | Primary | Fallback |
67
+ |------|---------|----------|
68
+ | See contracts | `invar sig` | — |
69
+ | Find entry points | `invar map --top` | — |
70
+ | Verify | `invar guard` | — |
71
+
72
+ ---
51
73
 
52
74
  ## Documentation Structure
53
75
 
@@ -62,6 +84,8 @@
62
84
  - Protocol content → Already in INVAR.md, don't duplicate
63
85
  - Project-specific → Add to CLAUDE.md or here
64
86
 
87
+ ---
88
+
65
89
  ## Technical Debt
66
90
 
67
91
  *Run `invar guard` to check current status.*
@@ -70,15 +94,8 @@
70
94
  |------|---------|----------|
71
95
  | (none) | — | — |
72
96
 
73
- ## Key Files
74
-
75
- | File | Purpose |
76
- |------|---------|
77
- | INVAR.md | Protocol reference (Invar-managed) |
78
- | CLAUDE.md | Project guide (customize freely) |
79
- | src/core/ | Pure business logic |
80
- | src/shell/ | I/O adapters |
81
-
82
97
  ---
83
98
 
99
+ <!-- ARCHIVE: For full session history, create .invar/archive/ directory -->
100
+
84
101
  *Update this file when: completing major tasks, making design decisions, discovering pitfalls.*
@@ -0,0 +1,102 @@
1
+ #!/bin/bash
2
+ # Invar PostToolUse Hook
3
+ # Protocol: v{{ protocol_version }} | Generated: {{ generated_date }}
4
+ # DX-57: Git-based change detection with fallback
5
+
6
+ TOOL_NAME="$1"
7
+
8
+ # Check if hooks are disabled
9
+ [[ -f ".claude/hooks/.invar_disabled" ]] && exit 0
10
+
11
+ # Use session-specific state directory
12
+ STATE_DIR="${CLAUDE_STATE_DIR:-/tmp/invar_hooks_$(id -u)}"
13
+ mkdir -p "$STATE_DIR" 2>/dev/null
14
+
15
+ CHANGES_FILE="$STATE_DIR/changes"
16
+ LAST_GUARD="$STATE_DIR/last_guard"
17
+ LAST_CHECK_MARKER="$STATE_DIR/last_check"
18
+
19
+ # ============================================
20
+ # Reset state on guard run (MCP or CLI)
21
+ # ============================================
22
+ # MCP: invar_guard tool call
23
+ if [[ "$TOOL_NAME" == "mcp__invar__invar_guard" ]]; then
24
+ date +%s > "$LAST_GUARD"
25
+ rm -f "$CHANGES_FILE"
26
+ touch "$LAST_CHECK_MARKER"
27
+ exit 0
28
+ fi
29
+
30
+ # CLI: Bash command containing "invar guard"
31
+ if [[ "$TOOL_NAME" == "Bash" ]]; then
32
+ TOOL_INPUT="$2"
33
+ if echo "$TOOL_INPUT" | grep -qE '"command"[^}]*invar\s+guard'; then
34
+ date +%s > "$LAST_GUARD"
35
+ rm -f "$CHANGES_FILE"
36
+ touch "$LAST_CHECK_MARKER"
37
+ exit 0
38
+ fi
39
+ fi
40
+
41
+ # ============================================
42
+ # Detect changes (git with fallback)
43
+ # ============================================
44
+ is_git_repo() {
45
+ git rev-parse --git-dir >/dev/null 2>&1
46
+ }
47
+
48
+ detect_changes() {
49
+ if is_git_repo; then
50
+ # Primary: Git-based detection (includes staged + unstaged)
51
+ { git diff --name-only -- '*.py' 2>/dev/null; git diff --cached --name-only -- '*.py' 2>/dev/null; } | sort -u
52
+ elif [[ -f "$LAST_CHECK_MARKER" ]]; then
53
+ # Fallback: Timestamp-based detection (approximate)
54
+ find . -name "*.py" -newer "$LAST_CHECK_MARKER" -type f 2>/dev/null | \
55
+ grep -v __pycache__ | grep -v '.venv' | head -20
56
+ fi
57
+ # Update marker for next check
58
+ touch "$LAST_CHECK_MARKER" 2>/dev/null
59
+ }
60
+
61
+ # Track changes
62
+ CHANGED=$(detect_changes)
63
+ if [[ -n "$CHANGED" ]]; then
64
+ echo "$CHANGED" >> "$CHANGES_FILE"
65
+ sort -u "$CHANGES_FILE" -o "$CHANGES_FILE" 2>/dev/null
66
+ fi
67
+
68
+ # ============================================
69
+ # Smart trigger evaluation
70
+ # ============================================
71
+ CHANGE_COUNT=$(wc -l < "$CHANGES_FILE" 2>/dev/null | tr -d ' ' || echo 0)
72
+ LAST_TIME=$(cat "$LAST_GUARD" 2>/dev/null || echo 0)
73
+ NOW=$(date +%s)
74
+ ELAPSED=$((NOW - LAST_TIME))
75
+
76
+ SHOULD_REMIND=false
77
+ REASON=""
78
+
79
+ # Trigger 1: Accumulated changes (3+ files)
80
+ if [[ $CHANGE_COUNT -ge 3 ]]; then
81
+ SHOULD_REMIND=true
82
+ REASON="$CHANGE_COUNT files changed"
83
+ fi
84
+
85
+ # Trigger 2: Core file changed (high priority)
86
+ if grep -qE "/core/|/contracts/" "$CHANGES_FILE" 2>/dev/null; then
87
+ SHOULD_REMIND=true
88
+ REASON="core/ files modified"
89
+ fi
90
+
91
+ # Trigger 3: Time threshold (>5 min with changes)
92
+ if [[ $ELAPSED -gt 300 && $CHANGE_COUNT -gt 0 ]]; then
93
+ SHOULD_REMIND=true
94
+ REASON=">5 min since last guard"
95
+ fi
96
+
97
+ # Output reminder if triggered
98
+ if [[ "$SHOULD_REMIND" == "true" ]]; then
99
+ echo ""
100
+ echo "⚠️ Verification suggested: $REASON"
101
+ echo " Run: {{ guard_cmd }} --changed"
102
+ fi
@@ -0,0 +1,74 @@
1
+ #!/bin/bash
2
+ # Invar PreToolUse Hook
3
+ # Protocol: v{{ protocol_version }} | Generated: {{ generated_date }}
4
+ # DX-57: Smart blocking with auto-escape for pytest/crosshair
5
+
6
+ TOOL_NAME="$1"
7
+ TOOL_INPUT="$2"
8
+
9
+ # Check if hooks are disabled
10
+ [[ -f ".claude/hooks/.invar_disabled" ]] && exit 0
11
+
12
+ # Only process Bash commands
13
+ [[ "$TOOL_NAME" != "Bash" ]] && exit 0
14
+
15
+ # Parse command from JSON input
16
+ # Primary: jq (accurate), Fallback: grep/sed (basic)
17
+ if command -v jq &>/dev/null; then
18
+ CMD=$(echo "$TOOL_INPUT" | jq -r '.command // empty' 2>/dev/null)
19
+ else
20
+ # Fallback: Extract command field using grep/sed (handles simple cases)
21
+ CMD=$(echo "$TOOL_INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"\(.*\)"/\1/')
22
+ fi
23
+ [[ -z "$CMD" ]] && exit 0
24
+
25
+ # ============================================
26
+ # pytest blocking with smart escape
27
+ # ============================================
28
+ if echo "$CMD" | grep -qE '\bpytest\b|python.*-m\s+pytest\b'; then
29
+
30
+ # Auto-escape 1: Debugging mode (--pdb, --debug, --tb=long)
31
+ if echo "$CMD" | grep -qE '\-\-pdb|\-\-debug|\-\-tb='; then
32
+ echo "⚠️ pytest debugging allowed. Run {{ guard_cmd }} after."
33
+ exit 0
34
+ fi
35
+
36
+ # Auto-escape 2: External/vendor tests
37
+ if echo "$CMD" | grep -qE 'vendor/|third_party/|external/|node_modules/'; then
38
+ exit 0
39
+ fi
40
+
41
+ # Auto-escape 3: Explicit coverage collection
42
+ if echo "$CMD" | grep -qE '\-\-cov'; then
43
+ echo "⚠️ pytest coverage allowed. Run {{ guard_cmd }} for contract verification."
44
+ exit 0
45
+ fi
46
+
47
+ # Auto-escape 4: Environment variable override
48
+ if [[ "$INVAR_ALLOW_PYTEST" == "1" ]]; then
49
+ exit 0
50
+ fi
51
+
52
+ # Default: Block with helpful message
53
+ echo "❌ Use {{ guard_cmd }} instead of pytest"
54
+ echo " {{ guard_cmd }} = static + doctests + CrossHair + Hypothesis"
55
+ echo ""
56
+ echo " Auto-allowed: pytest --pdb (debug), pytest --cov (coverage)"
57
+ echo " Manual escape: INVAR_ALLOW_PYTEST=1 pytest ..."
58
+ exit 1
59
+ fi
60
+
61
+ # ============================================
62
+ # crosshair blocking (always redirect)
63
+ # ============================================
64
+ if echo "$CMD" | grep -qE '\bcrosshair\b'; then
65
+ if [[ "$INVAR_ALLOW_CROSSHAIR" == "1" ]]; then
66
+ exit 0
67
+ fi
68
+
69
+ echo "❌ Use {{ guard_cmd }} (includes CrossHair by default)"
70
+ echo " Manual escape: INVAR_ALLOW_CROSSHAIR=1 crosshair ..."
71
+ exit 1
72
+ fi
73
+
74
+ exit 0
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+ # Invar Stop Hook
3
+ # Protocol: v{{ protocol_version }} | Generated: {{ generated_date }}
4
+ # DX-57: Session cleanup and unverified changes warning
5
+
6
+ # Use session-specific state
7
+ STATE_DIR="${CLAUDE_STATE_DIR:-/tmp/invar_hooks_$(id -u)}"
8
+ CHANGES_FILE="$STATE_DIR/changes"
9
+
10
+ # Check for unverified changes
11
+ if [[ -f "$CHANGES_FILE" ]]; then
12
+ CHANGE_COUNT=$(wc -l < "$CHANGES_FILE" 2>/dev/null | tr -d ' ' || echo 0)
13
+ if [[ $CHANGE_COUNT -gt 0 ]]; then
14
+ echo ""
15
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
16
+ echo "⚠️ Invar: $CHANGE_COUNT Python files not verified"
17
+ echo " Run before commit: {{ guard_cmd }} --changed"
18
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
19
+ fi
20
+ fi
21
+
22
+ # Cleanup session state
23
+ rm -rf "$STATE_DIR" 2>/dev/null
@@ -0,0 +1,77 @@
1
+ #!/bin/bash
2
+ # Invar UserPromptSubmit Hook
3
+ # Protocol: v{{ protocol_version }} | Generated: {{ generated_date }}
4
+ # DX-57: Protocol refresh with full INVAR.md injection
5
+
6
+ USER_MESSAGE="$1"
7
+
8
+ # Check if hooks are disabled
9
+ [[ -f ".claude/hooks/.invar_disabled" ]] && exit 0
10
+
11
+ # Use session-specific state
12
+ STATE_DIR="${CLAUDE_STATE_DIR:-/tmp/invar_hooks_$(id -u)}"
13
+ mkdir -p "$STATE_DIR" 2>/dev/null
14
+
15
+ # Session detection: Reset if state is stale (>4 hours)
16
+ SESSION_MARKER="$STATE_DIR/session_start"
17
+ MAX_AGE_SECONDS=14400 # 4 hours
18
+
19
+ reset_session() {
20
+ rm -f "$STATE_DIR/msg_count" "$STATE_DIR/changes" 2>/dev/null
21
+ date +%s > "$SESSION_MARKER"
22
+ }
23
+
24
+ if [[ -f "$SESSION_MARKER" ]]; then
25
+ MARKER_TIME=$(cat "$SESSION_MARKER" 2>/dev/null || echo 0)
26
+ NOW=$(date +%s)
27
+ AGE=$((NOW - MARKER_TIME))
28
+ if [[ $AGE -gt $MAX_AGE_SECONDS ]]; then
29
+ reset_session
30
+ fi
31
+ else
32
+ reset_session
33
+ fi
34
+
35
+ COUNT_FILE="$STATE_DIR/msg_count"
36
+ COUNT=$(cat "$COUNT_FILE" 2>/dev/null || echo 0)
37
+ COUNT=$((COUNT + 1))
38
+ echo "$COUNT" > "$COUNT_FILE"
39
+
40
+ # ============================================
41
+ # Keyword triggers (independent of count)
42
+ # ============================================
43
+
44
+ # pytest intent → immediate correction
45
+ if echo "$USER_MESSAGE" | grep -qiE "run.*pytest|pytest.*test|用.*pytest"; then
46
+ echo "<system-reminder>Use {{ guard_cmd }}, not pytest.</system-reminder>"
47
+ fi
48
+
49
+ # Implementation intent → workflow reminder (after warmup)
50
+ if [[ $COUNT -gt 3 ]]; then
51
+ if echo "$USER_MESSAGE" | grep -qiE "^implement|^fix|^add|^实现|^修复|^添加"; then
52
+ echo "<system-reminder>USBV: Specify contracts → Build → Validate</system-reminder>"
53
+ fi
54
+ fi
55
+
56
+ # ============================================
57
+ # Progressive refresh based on message count
58
+ # ============================================
59
+
60
+ # Message 15: Lightweight checkpoint
61
+ if [[ $COUNT -eq 15 ]]; then
62
+ echo "<system-reminder>"
63
+ echo "Checkpoint: guard=verify, sig=contracts, USBV workflow."
64
+ echo "</system-reminder>"
65
+ fi
66
+
67
+ # Message 25+: Full INVAR.md injection every 10 messages
68
+ # SSOT: Inject entire protocol to ensure no content drift
69
+ if [[ $COUNT -ge 25 && $((COUNT % 10)) -eq 0 ]]; then
70
+ echo "<system-reminder>"
71
+ echo "=== Protocol Refresh (message $COUNT) ==="
72
+ echo ""
73
+ cat << 'INVAR_EOF'
74
+ {{ invar_protocol }}
75
+ INVAR_EOF
76
+ echo "</system-reminder>"
77
+ fi
@@ -0,0 +1 @@
1
+ # Invar Claude Code hook templates (DX-57)
@@ -103,8 +103,8 @@ overwrite = ["INVAR.md", ".invar/examples/"]
103
103
  merge = ["CLAUDE.md", ".claude/skills/"]
104
104
  skip = [".invar/context.md"]
105
105
 
106
- [commands.sync_self]
107
- # For Invar project only
106
+ [commands.dev_sync]
107
+ # For Invar project only (invar dev sync)
108
108
  syntax = "mcp"
109
109
  inject_project_additions = true
110
110
 
@@ -36,6 +36,31 @@
36
36
 
37
37
  **Forbidden in Core:** `os`, `sys`, `subprocess`, `pathlib`, `open`, `requests`, `datetime.now`
38
38
 
39
+ ### Decision Tree: Core vs Shell
40
+
41
+ ```
42
+ Does this function...
43
+
44
+ ├─ Read or write files? ──────────────────→ Shell
45
+ ├─ Make network requests? ─────────────────→ Shell
46
+ ├─ Access current time (datetime.now)? ────→ Shell OR inject as parameter
47
+ ├─ Generate random values? ────────────────→ Shell OR inject as parameter
48
+ ├─ Print to console? ──────────────────────→ Shell (return data, Shell logs)
49
+ ├─ Access environment variables? ──────────→ Shell
50
+
51
+ └─ None of the above? ─────────────────────→ Core
52
+ ```
53
+
54
+ **Pattern:** Inject impure values as parameters:
55
+ ```python
56
+ # Core: receives 'now' as parameter (pure)
57
+ def is_expired(expiry: datetime, now: datetime) -> bool:
58
+ return now > expiry
59
+
60
+ # Shell calls with actual time
61
+ expired = is_expired(token.expiry, datetime.now())
62
+ ```
63
+
39
64
  ## Core Example (Pure Logic)
40
65
 
41
66
  ```python
@@ -76,6 +101,50 @@ def read_config(path: Path) -> Result[dict, str]:
76
101
 
77
102
  More examples: `.invar/examples/`
78
103
 
104
+ ## Contract Rules
105
+
106
+ ### Lambda Signature (Critical)
107
+
108
+ ```python
109
+ # WRONG: Lambda only takes first parameter
110
+ @pre(lambda x: x >= 0)
111
+ def calculate(x: int, y: int = 0): ...
112
+
113
+ # CORRECT: Lambda must include ALL parameters (even defaults)
114
+ @pre(lambda x, y=0: x >= 0)
115
+ def calculate(x: int, y: int = 0): ...
116
+ ```
117
+
118
+ Guard's `param_mismatch` rule catches this as ERROR.
119
+
120
+ ### Meaningful Contracts
121
+
122
+ ```python
123
+ # Redundant - type hints already check this
124
+ @pre(lambda x: isinstance(x, int))
125
+ def calc(x: int): ...
126
+
127
+ # Meaningful - checks business logic
128
+ @pre(lambda x: x > 0)
129
+ def calc(x: int): ...
130
+
131
+ # Meaningful - checks relationship between params
132
+ @pre(lambda start, end: start < end)
133
+ def process_range(start: int, end: int): ...
134
+ ```
135
+
136
+ ### @post Scope
137
+
138
+ ```python
139
+ # WRONG: @post cannot access function parameters
140
+ @post(lambda result: result > x) # 'x' not available!
141
+ def calc(x: int) -> int: ...
142
+
143
+ # CORRECT: @post can only use 'result'
144
+ @post(lambda result: result >= 0)
145
+ def calc(x: int) -> int: ...
146
+ ```
147
+
79
148
  ## Check-In (Required)
80
149
 
81
150
  Your first message MUST display:
@@ -184,27 +253,57 @@ When rule violation has valid architectural justification:
184
253
  def flask_handler(): ...
185
254
  ```
186
255
 
187
- See `invar rules` for all rule names.
256
+ **Valid rule names for @invar:allow:**
257
+ - `shell_result` — Shell function without Result return type
258
+ - `entry_point_too_thick` — Entry point exceeds 15 lines
259
+ - `forbidden_import` — I/O import in Core (rare, justify carefully)
260
+
261
+ Run `invar rules` for complete rule catalog with hints.
188
262
 
189
263
  ## Commands
190
264
 
191
265
  ```bash
192
- invar guard # Full: static + doctests + CrossHair + Hypothesis (default)
266
+ invar guard # Full: static + doctests + CrossHair + Hypothesis
193
267
  invar guard --static # Static only (quick debug, ~0.5s)
194
268
  invar guard --changed # Modified files only
269
+ invar guard --coverage # Collect branch coverage
195
270
  invar sig <file> # Show contracts + signatures
196
271
  invar map --top 10 # Most-referenced symbols
272
+ invar rules # List all rules with detection/hints (JSON)
197
273
  ```
198
274
 
199
275
  ## Configuration
200
276
 
201
277
  ```toml
278
+ # pyproject.toml or invar.toml
202
279
  [tool.invar.guard]
203
- core_paths = ["src/myapp/core"]
204
- shell_paths = ["src/myapp/shell"]
205
- # DX-22: Doctest lines are always excluded from size calculations by default
280
+ core_paths = ["src/myapp/core"] # Default: ["src/core", "core"]
281
+ shell_paths = ["src/myapp/shell"] # Default: ["src/shell", "shell"]
282
+ max_file_lines = 500 # Default: 500 (warning at 80%)
283
+ max_function_lines = 50 # Default: 50
284
+ # Doctest lines are excluded from size calculations
206
285
  ```
207
286
 
287
+ ## Troubleshooting
288
+
289
+ ### Size Limits (Agent Quick Reference)
290
+
291
+ | Rule | Limit | Fix |
292
+ |------|-------|-----|
293
+ | `function_too_long` | **50 lines** | Extract helper: `_impl()` + main with docstring |
294
+ | `file_too_long` | **500 lines** | Split by responsibility |
295
+ | `entry_point_too_thick` | **15 lines** | Delegate to Shell functions |
296
+
297
+ *Doctest lines excluded from counts. Limits configurable in `pyproject.toml`.*
298
+
299
+ ### Common Errors
300
+
301
+ | Symptom | Cause | Fix |
302
+ |---------|-------|-----|
303
+ | `param_mismatch` error | Lambda missing params | Include ALL params (even defaults) |
304
+ | `shell_result` error | Shell func no Result | Add Result[T,E] or @invar:allow |
305
+ | `is_failure()` not found | Wrong Result check | Use `isinstance(result, Failure)` |
306
+
208
307
  ---
209
308
 
210
- *Protocol v5.0 — USBV workflow (DX-32) | [Guide](docs/guide.md) | [Examples](.invar/examples/)*
309
+ *Protocol v5.0 — USBV workflow (DX-32) | [Examples](.invar/examples/)*
@@ -1,13 +1,11 @@
1
- <!--invar:skill version="{{ version }}"-->
2
- <!-- ========================================================================
3
- SKILL REGION - DO NOT EDIT
4
- This section is managed by Invar and will be overwritten on update.
5
- To add project-specific extensions, use the "extensions" region below.
6
- ======================================================================== -->
7
1
  ---
8
2
  name: develop
9
3
  description: Implementation phase following USBV workflow. Use when task is clear and actionable - "add", "implement", "create", "fix", "update", "build", "write". Requires Check-In at start and Final at end.
4
+ _invar:
5
+ version: "{{ version }}"
6
+ managed: skill
10
7
  ---
8
+ <!--invar:skill-->
11
9
 
12
10
  # Development Mode
13
11
 
@@ -303,7 +301,6 @@ Agent:
303
301
  ✓ Final: guard PASS | 0 errors, 1 warning
304
302
  ```
305
303
  <!--/invar:skill-->
306
-
307
304
  <!--invar:extensions-->
308
305
  <!-- ========================================================================
309
306
  EXTENSIONS REGION - USER EDITABLE
@@ -1,13 +1,11 @@
1
- <!--invar:skill version="{{ version }}"-->
2
- <!-- ========================================================================
3
- SKILL REGION - DO NOT EDIT
4
- This section is managed by Invar and will be overwritten on update.
5
- To add project-specific extensions, use the "extensions" region below.
6
- ======================================================================== -->
7
1
  ---
8
2
  name: investigate
9
3
  description: Exploration and understanding phase. Use when task is vague, needs analysis, or requires understanding before action. Triggers on "why", "what is", "how does", "explain", "understand", "analyze", "investigate", "explore". NO CODE CHANGES in this phase.
4
+ _invar:
5
+ version: "{{ version }}"
6
+ managed: skill
10
7
  ---
8
+ <!--invar:skill-->
11
9
 
12
10
  # Investigation Mode
13
11
 
@@ -91,7 +89,6 @@ Before any workflow action:
91
89
  **Next step?**
92
90
  ```
93
91
  <!--/invar:skill-->
94
-
95
92
  <!--invar:extensions-->
96
93
  <!-- ========================================================================
97
94
  EXTENSIONS REGION - USER EDITABLE