invar-tools 1.14.0__py3-none-any.whl → 1.15.0__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.
- invar/shell/commands/init.py +71 -41
- invar/shell/pi_tools.py +120 -0
- invar/templates/hooks/UserPromptSubmit.sh.jinja +31 -0
- invar/templates/hooks/pi/invar.ts.jinja +35 -1
- invar/templates/pi-tools/invar/index.ts +207 -0
- invar/templates/skills/invar-reflect/CONFIG.md +154 -29
- {invar_tools-1.14.0.dist-info → invar_tools-1.15.0.dist-info}/METADATA +6 -3
- {invar_tools-1.14.0.dist-info → invar_tools-1.15.0.dist-info}/RECORD +13 -11
- {invar_tools-1.14.0.dist-info → invar_tools-1.15.0.dist-info}/WHEEL +0 -0
- {invar_tools-1.14.0.dist-info → invar_tools-1.15.0.dist-info}/entry_points.txt +0 -0
- {invar_tools-1.14.0.dist-info → invar_tools-1.15.0.dist-info}/licenses/LICENSE +0 -0
- {invar_tools-1.14.0.dist-info → invar_tools-1.15.0.dist-info}/licenses/LICENSE-GPL +0 -0
- {invar_tools-1.14.0.dist-info → invar_tools-1.15.0.dist-info}/licenses/NOTICE +0 -0
invar/shell/commands/init.py
CHANGED
|
@@ -23,6 +23,7 @@ from invar.shell.mcp_config import (
|
|
|
23
23
|
get_recommended_method,
|
|
24
24
|
)
|
|
25
25
|
from invar.shell.pi_hooks import install_pi_hooks
|
|
26
|
+
from invar.shell.pi_tools import install_pi_tools
|
|
26
27
|
from invar.shell.templates import (
|
|
27
28
|
add_config,
|
|
28
29
|
create_directories,
|
|
@@ -60,6 +61,7 @@ FILE_CATEGORIES: dict[str, list[tuple[str, str]]] = {
|
|
|
60
61
|
("CLAUDE.md", "Agent instructions (Pi compatible)"),
|
|
61
62
|
(".claude/skills/", "Workflow automation (Pi compatible)"),
|
|
62
63
|
(".pi/hooks/", "Pi-specific hooks"),
|
|
64
|
+
(".pi/tools/", "Pi custom tools (invar_guard, invar_sig, invar_map)"),
|
|
63
65
|
],
|
|
64
66
|
}
|
|
65
67
|
|
|
@@ -153,29 +155,43 @@ def _get_prompt_style():
|
|
|
153
155
|
|
|
154
156
|
# @shell_complexity: Interactive prompt with cursor selection
|
|
155
157
|
def _prompt_agent_selection() -> list[str]:
|
|
156
|
-
"""Prompt user to select
|
|
158
|
+
"""Prompt user to select agent(s) using checkbox (DX-81: multi-agent support)."""
|
|
157
159
|
import questionary
|
|
158
160
|
|
|
159
|
-
console.print("\n[bold]Select
|
|
160
|
-
console.print("[dim]
|
|
161
|
+
console.print("\n[bold]Select agent(s) to configure:[/bold]")
|
|
162
|
+
console.print("[dim]Space to toggle, Enter to confirm (can select multiple)[/dim]\n")
|
|
161
163
|
|
|
162
164
|
choices = [
|
|
163
|
-
questionary.Choice(
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
questionary.Choice(
|
|
166
|
+
"Claude Code (recommended)",
|
|
167
|
+
value="claude",
|
|
168
|
+
checked=True # Default selection
|
|
169
|
+
),
|
|
170
|
+
questionary.Choice(
|
|
171
|
+
"Pi Coding Agent",
|
|
172
|
+
value="pi",
|
|
173
|
+
checked=False
|
|
174
|
+
),
|
|
175
|
+
questionary.Choice(
|
|
176
|
+
"Other (AGENT.md)",
|
|
177
|
+
value="generic",
|
|
178
|
+
checked=False
|
|
179
|
+
),
|
|
166
180
|
]
|
|
167
181
|
|
|
168
|
-
selected = questionary.
|
|
182
|
+
selected = questionary.checkbox(
|
|
169
183
|
"",
|
|
170
184
|
choices=choices,
|
|
171
185
|
instruction="",
|
|
172
186
|
style=_get_prompt_style(),
|
|
173
187
|
).ask()
|
|
174
188
|
|
|
175
|
-
# Handle Ctrl+C
|
|
189
|
+
# Handle Ctrl+C or empty selection
|
|
176
190
|
if not selected:
|
|
177
|
-
|
|
178
|
-
|
|
191
|
+
console.print("[yellow]No agents selected, using Claude Code as default.[/yellow]")
|
|
192
|
+
return ["claude"]
|
|
193
|
+
|
|
194
|
+
return selected
|
|
179
195
|
|
|
180
196
|
|
|
181
197
|
# @shell_complexity: Interactive file selection with cursor navigation
|
|
@@ -205,9 +221,10 @@ def _prompt_file_selection(agents: list[str]) -> dict[str, bool]:
|
|
|
205
221
|
console.print()
|
|
206
222
|
console.print("[dim]Use arrow keys to move, space to toggle, enter to confirm[/dim]\n")
|
|
207
223
|
|
|
208
|
-
# Build choices with categories as separators
|
|
224
|
+
# Build choices with categories as separators (DX-81: deduplicate shared files)
|
|
209
225
|
choices: list[questionary.Choice | questionary.Separator] = []
|
|
210
226
|
file_list: list[str] = []
|
|
227
|
+
seen_files: set[str] = set()
|
|
211
228
|
|
|
212
229
|
for category, files in available.items():
|
|
213
230
|
if category == "required":
|
|
@@ -217,12 +234,19 @@ def _prompt_file_selection(agents: list[str]) -> dict[str, bool]:
|
|
|
217
234
|
category_name = "Claude Code"
|
|
218
235
|
elif category == "pi":
|
|
219
236
|
category_name = "Pi Coding Agent"
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
237
|
+
|
|
238
|
+
# Filter out files already seen (shared between categories)
|
|
239
|
+
unique_files = [(f, d) for f, d in files if f not in seen_files]
|
|
240
|
+
|
|
241
|
+
# Only add separator if there are unique files to show
|
|
242
|
+
if unique_files:
|
|
243
|
+
choices.append(questionary.Separator(f"── {category_name} ──"))
|
|
244
|
+
for file, desc in unique_files:
|
|
245
|
+
choices.append(
|
|
246
|
+
questionary.Choice(f"{file:28} {desc}", value=file, checked=True)
|
|
247
|
+
)
|
|
248
|
+
file_list.append(file)
|
|
249
|
+
seen_files.add(file)
|
|
226
250
|
|
|
227
251
|
selected = questionary.checkbox(
|
|
228
252
|
"Select files to install:",
|
|
@@ -390,10 +414,7 @@ def init(
|
|
|
390
414
|
"""
|
|
391
415
|
from invar import __version__
|
|
392
416
|
|
|
393
|
-
#
|
|
394
|
-
if claude and pi:
|
|
395
|
-
console.print("[red]Error:[/red] Cannot use --claude and --pi together.")
|
|
396
|
-
raise typer.Exit(1)
|
|
417
|
+
# DX-81: Multi-agent support - removed mutual exclusivity check
|
|
397
418
|
|
|
398
419
|
if mcp_only and (claude or pi):
|
|
399
420
|
console.print("[red]Error:[/red] --mcp-only cannot be combined with --claude or --pi.")
|
|
@@ -458,8 +479,10 @@ def init(
|
|
|
458
479
|
console.print(f"[red]Error:[/red] Invalid language '{language}'. Must be one of: {valid}")
|
|
459
480
|
raise typer.Exit(1)
|
|
460
481
|
|
|
461
|
-
# Header
|
|
462
|
-
if claude:
|
|
482
|
+
# Header (DX-81: Support multi-agent display)
|
|
483
|
+
if claude and pi:
|
|
484
|
+
console.print(f"\n[bold]Invar v{__version__} - Quick Setup (Claude Code + Pi)[/bold]")
|
|
485
|
+
elif claude:
|
|
463
486
|
console.print(f"\n[bold]Invar v{__version__} - Quick Setup (Claude Code)[/bold]")
|
|
464
487
|
elif pi:
|
|
465
488
|
console.print(f"\n[bold]Invar v{__version__} - Quick Setup (Pi)[/bold]")
|
|
@@ -468,27 +491,30 @@ def init(
|
|
|
468
491
|
console.print("=" * 45)
|
|
469
492
|
console.print(f"[dim]Language: {language} | Existing files will be MERGED.[/dim]")
|
|
470
493
|
|
|
471
|
-
# Determine agents and files
|
|
472
|
-
if claude:
|
|
473
|
-
# Quick mode:
|
|
474
|
-
agents = [
|
|
494
|
+
# DX-81: Determine agents and files (multi-agent support)
|
|
495
|
+
if claude or pi:
|
|
496
|
+
# Quick mode: Build agent list from flags
|
|
497
|
+
agents = []
|
|
498
|
+
if claude:
|
|
499
|
+
agents.append("claude")
|
|
500
|
+
if pi:
|
|
501
|
+
agents.append("pi")
|
|
502
|
+
|
|
503
|
+
# Build selected_files from all agents' categories
|
|
475
504
|
selected_files: dict[str, bool] = {}
|
|
476
|
-
for
|
|
477
|
-
|
|
478
|
-
selected_files[file] = True
|
|
479
|
-
# DX-79: Default feedback enabled for quick mode
|
|
480
|
-
feedback_enabled = True
|
|
481
|
-
console.print("\n[dim]📊 Feedback collection enabled by default (stored locally in .invar/feedback/)[/dim]")
|
|
482
|
-
console.print("[dim] To disable: Set feedback.enabled=false in .claude/settings.local.json[/dim]")
|
|
483
|
-
elif pi:
|
|
484
|
-
# Quick mode: Pi defaults
|
|
485
|
-
agents = ["pi"]
|
|
486
|
-
selected_files = {}
|
|
487
|
-
for category in ["optional", "pi"]:
|
|
505
|
+
for agent in agents:
|
|
506
|
+
category = AGENT_CONFIGS[agent]["category"]
|
|
488
507
|
for file, _ in FILE_CATEGORIES.get(category, []):
|
|
489
508
|
selected_files[file] = True
|
|
509
|
+
|
|
510
|
+
# Add optional files
|
|
511
|
+
for file, _ in FILE_CATEGORIES["optional"]:
|
|
512
|
+
selected_files[file] = True
|
|
513
|
+
|
|
490
514
|
# DX-79: Default feedback enabled for quick mode
|
|
491
515
|
feedback_enabled = True
|
|
516
|
+
if len(agents) > 1:
|
|
517
|
+
console.print(f"\n[dim]📊 Configuring for {len(agents)} agents: {', '.join(agents)}[/dim]")
|
|
492
518
|
console.print("\n[dim]📊 Feedback collection enabled by default (stored locally in .invar/feedback/)[/dim]")
|
|
493
519
|
console.print("[dim] To disable: Set feedback.enabled=false in .claude/settings.local.json[/dim]")
|
|
494
520
|
else:
|
|
@@ -598,6 +624,10 @@ def init(
|
|
|
598
624
|
if "pi" in agents and selected_files.get(".pi/hooks/", True):
|
|
599
625
|
install_pi_hooks(path, console)
|
|
600
626
|
|
|
627
|
+
# Install Pi custom tools if selected
|
|
628
|
+
if "pi" in agents and selected_files.get(".pi/tools/", True):
|
|
629
|
+
install_pi_tools(path, console)
|
|
630
|
+
|
|
601
631
|
# Add feedback configuration (DX-79 Phase C)
|
|
602
632
|
if "claude" in agents or "pi" in agents:
|
|
603
633
|
feedback_result = add_feedback_config(path, feedback_enabled, console)
|
|
@@ -622,7 +652,7 @@ def init(
|
|
|
622
652
|
# Completion message
|
|
623
653
|
console.print(f"\n[bold green]✓ Initialized Invar v{__version__}[/bold green]")
|
|
624
654
|
|
|
625
|
-
# Show agent-specific tips
|
|
655
|
+
# Show agent-specific tips (DX-81: show all relevant tips)
|
|
626
656
|
if "claude" in agents:
|
|
627
657
|
console.print()
|
|
628
658
|
console.print(
|
|
@@ -633,7 +663,7 @@ def init(
|
|
|
633
663
|
border_style="dim",
|
|
634
664
|
)
|
|
635
665
|
)
|
|
636
|
-
|
|
666
|
+
if "pi" in agents:
|
|
637
667
|
console.print()
|
|
638
668
|
console.print(
|
|
639
669
|
Panel(
|
invar/shell/pi_tools.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pi Coding Agent custom tools for Invar.
|
|
3
|
+
|
|
4
|
+
Provides Invar CLI commands as Pi custom tools for better LLM integration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import shutil
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
from returns.result import Failure, Result, Success
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
|
|
18
|
+
# Pi tools directory
|
|
19
|
+
PI_TOOLS_DIR = ".pi/tools/invar"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_pi_tools_template_path() -> Path:
|
|
23
|
+
"""Get the path to Pi tools template."""
|
|
24
|
+
return Path(__file__).parent.parent / "templates" / "pi-tools" / "invar"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def install_pi_tools(
|
|
28
|
+
project_path: Path,
|
|
29
|
+
console: Console,
|
|
30
|
+
) -> Result[list[str], str]:
|
|
31
|
+
"""
|
|
32
|
+
Install Pi custom tools for Invar.
|
|
33
|
+
|
|
34
|
+
Creates .pi/tools/invar/index.ts with:
|
|
35
|
+
- invar_guard: Wrapper for invar guard command
|
|
36
|
+
- invar_sig: Wrapper for invar sig command
|
|
37
|
+
- invar_map: Wrapper for invar map command
|
|
38
|
+
"""
|
|
39
|
+
tools_dir = project_path / PI_TOOLS_DIR
|
|
40
|
+
tools_dir.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
|
|
42
|
+
console.print("\n[bold]Installing Pi custom tools...[/bold]")
|
|
43
|
+
console.print(" Tools provide:")
|
|
44
|
+
console.print(" ✓ invar_guard - Smart verification (static + doctests + symbolic)")
|
|
45
|
+
console.print(" ✓ invar_sig - Show function signatures and contracts")
|
|
46
|
+
console.print(" ✓ invar_map - Symbol map with reference counts")
|
|
47
|
+
console.print("")
|
|
48
|
+
|
|
49
|
+
template_path = get_pi_tools_template_path()
|
|
50
|
+
tool_file = template_path / "index.ts"
|
|
51
|
+
|
|
52
|
+
if not tool_file.exists():
|
|
53
|
+
return Failure(f"Template not found: {tool_file}")
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
# Copy the template file
|
|
57
|
+
dest_file = tools_dir / "index.ts"
|
|
58
|
+
shutil.copy2(tool_file, dest_file)
|
|
59
|
+
|
|
60
|
+
console.print(f" [green]Created[/green] {PI_TOOLS_DIR}/index.ts")
|
|
61
|
+
console.print("\n [bold green]✓ Pi custom tools installed[/bold green]")
|
|
62
|
+
console.print(" [dim]Pi will auto-discover tools in .pi/tools/[/dim]")
|
|
63
|
+
console.print(" [yellow]⚠ Restart Pi session for tools to take effect[/yellow]")
|
|
64
|
+
|
|
65
|
+
return Success(["index.ts"])
|
|
66
|
+
except Exception as e:
|
|
67
|
+
return Failure(f"Failed to install Pi tools: {e}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def remove_pi_tools(
|
|
71
|
+
project_path: Path,
|
|
72
|
+
console: Console,
|
|
73
|
+
) -> Result[None, str]:
|
|
74
|
+
"""Remove Pi custom tools."""
|
|
75
|
+
tools_dir = project_path / PI_TOOLS_DIR
|
|
76
|
+
tool_file = tools_dir / "index.ts"
|
|
77
|
+
|
|
78
|
+
if tool_file.exists():
|
|
79
|
+
tool_file.unlink()
|
|
80
|
+
console.print(f" [red]Removed[/red] {PI_TOOLS_DIR}/index.ts")
|
|
81
|
+
|
|
82
|
+
# Remove directory if empty
|
|
83
|
+
try:
|
|
84
|
+
tools_dir.rmdir()
|
|
85
|
+
console.print(f" [red]Removed[/red] {PI_TOOLS_DIR}/")
|
|
86
|
+
except OSError:
|
|
87
|
+
pass # Directory not empty, keep it
|
|
88
|
+
|
|
89
|
+
console.print("[bold green]✓ Pi custom tools removed[/bold green]")
|
|
90
|
+
else:
|
|
91
|
+
console.print("[dim]No Pi custom tools installed[/dim]")
|
|
92
|
+
|
|
93
|
+
return Success(None)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def pi_tools_status(
|
|
97
|
+
project_path: Path,
|
|
98
|
+
console: Console,
|
|
99
|
+
) -> Result[dict[str, str], str]:
|
|
100
|
+
"""Check status of Pi custom tools."""
|
|
101
|
+
tools_dir = project_path / PI_TOOLS_DIR
|
|
102
|
+
tool_file = tools_dir / "index.ts"
|
|
103
|
+
|
|
104
|
+
status: dict[str, str] = {}
|
|
105
|
+
|
|
106
|
+
if not tool_file.exists():
|
|
107
|
+
console.print("[dim]No Pi custom tools installed[/dim]")
|
|
108
|
+
return Success({"status": "not_installed"})
|
|
109
|
+
|
|
110
|
+
status["status"] = "installed"
|
|
111
|
+
|
|
112
|
+
# Try to check file size (basic validation)
|
|
113
|
+
try:
|
|
114
|
+
size = tool_file.stat().st_size
|
|
115
|
+
status["size"] = f"{size} bytes"
|
|
116
|
+
console.print(f"[green]✓ Pi custom tools installed[/green] ({size} bytes)")
|
|
117
|
+
except OSError:
|
|
118
|
+
console.print("[green]✓ Pi custom tools installed[/green]")
|
|
119
|
+
|
|
120
|
+
return Success(status)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# Invar UserPromptSubmit Hook
|
|
3
3
|
# Protocol: v{{ protocol_version }} | Generated: {{ generated_date }}
|
|
4
4
|
# DX-57: Protocol refresh with full INVAR.md injection
|
|
5
|
+
# DX-79: Automatic feedback trigger via message count
|
|
5
6
|
|
|
6
7
|
USER_MESSAGE="$1"
|
|
7
8
|
|
|
@@ -82,3 +83,33 @@ if [[ $COUNT -ge 25 && $((COUNT % 10)) -eq 0 ]]; then
|
|
|
82
83
|
INVAR_EOF
|
|
83
84
|
echo "</system-reminder>"
|
|
84
85
|
fi
|
|
86
|
+
|
|
87
|
+
# ============================================
|
|
88
|
+
# DX-79: Feedback trigger at threshold
|
|
89
|
+
# ============================================
|
|
90
|
+
|
|
91
|
+
# Read feedback configuration
|
|
92
|
+
FEEDBACK_ENABLED=true
|
|
93
|
+
MIN_MESSAGES=30
|
|
94
|
+
|
|
95
|
+
if [[ -f ".claude/settings.local.json" ]]; then
|
|
96
|
+
# Try to parse with jq if available, otherwise use defaults
|
|
97
|
+
if command -v jq &> /dev/null; then
|
|
98
|
+
FEEDBACK_ENABLED=$(jq -r '.feedback.enabled // true' .claude/settings.local.json 2>/dev/null)
|
|
99
|
+
MIN_MESSAGES=$(jq -r '.feedback.min_messages // 30' .claude/settings.local.json 2>/dev/null)
|
|
100
|
+
fi
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# Trigger feedback prompt at threshold
|
|
104
|
+
if [[ "$FEEDBACK_ENABLED" == "true" && $COUNT -eq $MIN_MESSAGES ]]; then
|
|
105
|
+
echo "<system-reminder>"
|
|
106
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
107
|
+
echo "📊 Invar: Auto-triggering usage feedback ($COUNT messages)"
|
|
108
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
109
|
+
echo ""
|
|
110
|
+
echo "Session has reached $COUNT messages. Consider running /invar-reflect"
|
|
111
|
+
echo "to generate usage feedback."
|
|
112
|
+
echo ""
|
|
113
|
+
echo "To disable: Set feedback.enabled=false in .claude/settings.local.json"
|
|
114
|
+
echo "</system-reminder>"
|
|
115
|
+
fi
|
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
* Invar Pi Hook
|
|
3
3
|
* Protocol: v{{ protocol_version }} | Generated: {{ generated_date }}
|
|
4
4
|
* LX-04: Full feature parity with Claude Code hooks
|
|
5
|
+
* DX-79: Automatic feedback trigger via message count
|
|
5
6
|
*
|
|
6
7
|
* Features:
|
|
7
8
|
* - pytest/crosshair blocking via tool_call
|
|
8
9
|
* - Protocol injection via pi.send() for long conversations
|
|
10
|
+
* - Automatic /invar-reflect trigger at message threshold
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
import type { HookAPI } from "@mariozechner/pi-coding-agent/hooks";
|
|
14
|
+
import * as fs from "fs";
|
|
12
15
|
|
|
13
16
|
// Blocked commands (same as Claude Code)
|
|
14
17
|
{% if language == "python" -%}
|
|
@@ -22,6 +25,21 @@ const ALLOWED_FLAGS = [/--inspect/, /--coverage/, /--debug/];
|
|
|
22
25
|
// Protocol content for injection (escaped for JS)
|
|
23
26
|
const INVAR_PROTOCOL = `{{ invar_protocol_escaped }}`;
|
|
24
27
|
|
|
28
|
+
// DX-79: Helper to read feedback configuration
|
|
29
|
+
function readFeedbackConfig() {
|
|
30
|
+
try {
|
|
31
|
+
const settingsPath = ".claude/settings.local.json";
|
|
32
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
33
|
+
return {
|
|
34
|
+
enabled: settings.feedback?.enabled ?? true,
|
|
35
|
+
min_messages: settings.feedback?.min_messages ?? 30,
|
|
36
|
+
};
|
|
37
|
+
} catch {
|
|
38
|
+
// File doesn't exist or other error, use defaults
|
|
39
|
+
}
|
|
40
|
+
return { enabled: true, min_messages: 30 };
|
|
41
|
+
}
|
|
42
|
+
|
|
25
43
|
export default function (pi: HookAPI) {
|
|
26
44
|
let msgCount = 0;
|
|
27
45
|
|
|
@@ -53,6 +71,21 @@ export default function (pi: HookAPI) {
|
|
|
53
71
|
pi.send(`<system-reminder>
|
|
54
72
|
=== Protocol Refresh (message ${msgCount}) ===
|
|
55
73
|
${INVAR_PROTOCOL}
|
|
74
|
+
</system-reminder>`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// DX-79: Feedback trigger at threshold
|
|
78
|
+
const feedbackConfig = readFeedbackConfig();
|
|
79
|
+
if (msgCount === feedbackConfig.min_messages && feedbackConfig.enabled) {
|
|
80
|
+
pi.send(`<system-reminder>
|
|
81
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
82
|
+
📊 Invar: Auto-triggering usage feedback (${msgCount} messages)
|
|
83
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
84
|
+
|
|
85
|
+
Session has reached ${msgCount} messages. Consider running /invar-reflect
|
|
86
|
+
to generate usage feedback.
|
|
87
|
+
|
|
88
|
+
To disable: Set feedback.enabled=false in .claude/settings.local.json
|
|
56
89
|
</system-reminder>`);
|
|
57
90
|
}
|
|
58
91
|
});
|
|
@@ -62,7 +95,8 @@ ${INVAR_PROTOCOL}
|
|
|
62
95
|
// ============================================
|
|
63
96
|
pi.on("tool_call", async (event) => {
|
|
64
97
|
if (event.toolName !== "bash") return;
|
|
65
|
-
const
|
|
98
|
+
const input = event.input as Record<string, unknown>;
|
|
99
|
+
const cmd = (typeof input?.command === "string" ? input.command : "").trim();
|
|
66
100
|
|
|
67
101
|
// Skip if not a blocked command
|
|
68
102
|
if (!BLOCKED_CMDS.some((p) => p.test(cmd))) return;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invar Custom Tools for Pi Coding Agent
|
|
3
|
+
*
|
|
4
|
+
* Wraps Invar CLI commands as Pi tools for better LLM integration.
|
|
5
|
+
* Installed via: invar init --pi
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Type } from "@sinclair/typebox";
|
|
9
|
+
import type { CustomToolFactory } from "@mariozechner/pi-coding-agent";
|
|
10
|
+
|
|
11
|
+
const factory: CustomToolFactory = (pi) => {
|
|
12
|
+
// Helper to check if invar is available
|
|
13
|
+
async function checkInvarInstalled(): Promise<boolean> {
|
|
14
|
+
try {
|
|
15
|
+
const result = await pi.exec("which", ["invar"]);
|
|
16
|
+
return result.exitCode === 0;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Helper to validate path/target parameters (defense-in-depth)
|
|
23
|
+
function isValidPath(p: string): boolean {
|
|
24
|
+
// Reject shell metacharacters (including newline injection) and path traversal
|
|
25
|
+
if (/[;&|`$"'\\<>\n\r\0]/.test(p)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (p.includes('..')) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return [
|
|
35
|
+
// =========================================================================
|
|
36
|
+
// invar_guard - Smart verification (static + doctests + symbolic)
|
|
37
|
+
// =========================================================================
|
|
38
|
+
{
|
|
39
|
+
name: "invar_guard",
|
|
40
|
+
label: "Invar Guard",
|
|
41
|
+
description: "Verify code quality with static analysis, doctests, CrossHair symbolic execution, and Hypothesis testing. Use this instead of pytest/crosshair. By default checks git-modified files; use --all for full project check.",
|
|
42
|
+
parameters: Type.Object({
|
|
43
|
+
changed: Type.Optional(Type.Boolean({
|
|
44
|
+
description: "Check only git-modified files (default: true)",
|
|
45
|
+
default: true,
|
|
46
|
+
})),
|
|
47
|
+
contracts_only: Type.Optional(Type.Boolean({
|
|
48
|
+
description: "Contract coverage check only (skip tests)",
|
|
49
|
+
default: false,
|
|
50
|
+
})),
|
|
51
|
+
coverage: Type.Optional(Type.Boolean({
|
|
52
|
+
description: "Collect branch coverage from doctest + hypothesis",
|
|
53
|
+
default: false,
|
|
54
|
+
})),
|
|
55
|
+
strict: Type.Optional(Type.Boolean({
|
|
56
|
+
description: "Treat warnings as errors",
|
|
57
|
+
default: false,
|
|
58
|
+
})),
|
|
59
|
+
}),
|
|
60
|
+
async execute(toolCallId, params, onUpdate, ctx, signal) {
|
|
61
|
+
const installed = await checkInvarInstalled();
|
|
62
|
+
if (!installed) {
|
|
63
|
+
throw new Error("Invar not installed. Run: pip install invar-tools");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const args = ["guard"];
|
|
67
|
+
|
|
68
|
+
// Default is --changed (check modified files)
|
|
69
|
+
if (params.changed === false) {
|
|
70
|
+
args.push("--all");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (params.contracts_only) {
|
|
74
|
+
args.push("-c");
|
|
75
|
+
}
|
|
76
|
+
if (params.coverage) {
|
|
77
|
+
args.push("--coverage");
|
|
78
|
+
}
|
|
79
|
+
if (params.strict) {
|
|
80
|
+
args.push("--strict");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const result = await pi.exec("invar", args, { cwd: pi.cwd, signal });
|
|
84
|
+
|
|
85
|
+
if (result.killed) {
|
|
86
|
+
throw new Error("Guard verification was cancelled");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const output = result.stdout + result.stderr;
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
content: [{ type: "text", text: output || "Guard completed" }],
|
|
93
|
+
details: {
|
|
94
|
+
exitCode: result.exitCode,
|
|
95
|
+
passed: result.exitCode === 0,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
// =========================================================================
|
|
102
|
+
// invar_sig - Show function signatures and contracts
|
|
103
|
+
// =========================================================================
|
|
104
|
+
{
|
|
105
|
+
name: "invar_sig",
|
|
106
|
+
label: "Invar Sig",
|
|
107
|
+
description: "Show function signatures and contracts (@pre/@post). Use this INSTEAD of Read() when you want to understand file structure without reading full implementation.",
|
|
108
|
+
parameters: Type.Object({
|
|
109
|
+
target: Type.String({
|
|
110
|
+
description: "File path or file::symbol path (e.g., 'src/foo.py' or 'src/foo.py::MyClass')",
|
|
111
|
+
}),
|
|
112
|
+
}),
|
|
113
|
+
async execute(toolCallId, params, onUpdate, ctx, signal) {
|
|
114
|
+
const installed = await checkInvarInstalled();
|
|
115
|
+
if (!installed) {
|
|
116
|
+
throw new Error("Invar not installed. Run: pip install invar-tools");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!isValidPath(params.target)) {
|
|
120
|
+
throw new Error("Invalid target path: contains unsafe characters or path traversal");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const result = await pi.exec("invar", ["sig", params.target], {
|
|
124
|
+
cwd: pi.cwd,
|
|
125
|
+
signal,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (result.killed) {
|
|
129
|
+
throw new Error("Sig command was cancelled");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (result.exitCode !== 0) {
|
|
133
|
+
throw new Error(`Failed to get signatures: ${result.stderr}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: "text", text: result.stdout }],
|
|
138
|
+
details: {
|
|
139
|
+
target: params.target,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// =========================================================================
|
|
146
|
+
// invar_map - Symbol map with reference counts
|
|
147
|
+
// =========================================================================
|
|
148
|
+
{
|
|
149
|
+
name: "invar_map",
|
|
150
|
+
label: "Invar Map",
|
|
151
|
+
description: "Symbol map with reference counts. Use this INSTEAD of Grep for 'def ' to find entry points and most-referenced symbols.",
|
|
152
|
+
parameters: Type.Object({
|
|
153
|
+
path: Type.Optional(Type.String({
|
|
154
|
+
description: "Project path (default: current directory)",
|
|
155
|
+
default: ".",
|
|
156
|
+
})),
|
|
157
|
+
top: Type.Optional(Type.Number({
|
|
158
|
+
description: "Show top N symbols by reference count",
|
|
159
|
+
default: 10,
|
|
160
|
+
})),
|
|
161
|
+
}),
|
|
162
|
+
async execute(toolCallId, params, onUpdate, ctx, signal) {
|
|
163
|
+
const installed = await checkInvarInstalled();
|
|
164
|
+
if (!installed) {
|
|
165
|
+
throw new Error("Invar not installed. Run: pip install invar-tools");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (params.path && params.path !== "." && !isValidPath(params.path)) {
|
|
169
|
+
throw new Error("Invalid path: contains unsafe characters or path traversal");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const args = ["map"];
|
|
173
|
+
|
|
174
|
+
if (params.path && params.path !== ".") {
|
|
175
|
+
args.push(params.path);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (params.top) {
|
|
179
|
+
args.push("--top", params.top.toString());
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const result = await pi.exec("invar", args, {
|
|
183
|
+
cwd: pi.cwd,
|
|
184
|
+
signal,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (result.killed) {
|
|
188
|
+
throw new Error("Map command was cancelled");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (result.exitCode !== 0) {
|
|
192
|
+
throw new Error(`Failed to generate map: ${result.stderr}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
content: [{ type: "text", text: result.stdout }],
|
|
197
|
+
details: {
|
|
198
|
+
path: params.path || ".",
|
|
199
|
+
top: params.top || 10,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
];
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export default factory;
|
|
@@ -16,6 +16,46 @@ The `/invar-reflect` skill can be triggered:
|
|
|
16
16
|
|
|
17
17
|
## Proposed Hook Schema
|
|
18
18
|
|
|
19
|
+
|
|
20
|
+
### Message Count Trigger (Implemented - DX-79)
|
|
21
|
+
|
|
22
|
+
**Status**: ✅ Implemented in v1.15.0
|
|
23
|
+
|
|
24
|
+
Both Claude Code and Pi now support automatic feedback triggering via **message count threshold**.
|
|
25
|
+
|
|
26
|
+
**Configuration in `.claude/settings.local.json`**:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"feedback": {
|
|
31
|
+
"enabled": true,
|
|
32
|
+
"min_messages": 30
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Hook Parameters**:
|
|
38
|
+
|
|
39
|
+
| Parameter | Type | Default | Description |
|
|
40
|
+
|-----------|------|---------|-------------|
|
|
41
|
+
| `enabled` | boolean | true | Enable feedback collection |
|
|
42
|
+
| `min_messages` | number | 30 | Minimum messages before trigger |
|
|
43
|
+
|
|
44
|
+
**How it works**:
|
|
45
|
+
|
|
46
|
+
1. **Message counting**: Both hooks track message count per session
|
|
47
|
+
2. **Threshold trigger**: At `min_messages`, hook displays reminder
|
|
48
|
+
3. **User action**: Agent sees reminder and can run `/invar-reflect`
|
|
49
|
+
|
|
50
|
+
**Cross-platform implementation**:
|
|
51
|
+
|
|
52
|
+
| Platform | Hook File | Mechanism |
|
|
53
|
+
|----------|-----------|-----------|
|
|
54
|
+
| **Claude Code** | `.claude/hooks/invar.UserPromptSubmit.sh` | Bash script with jq config parsing |
|
|
55
|
+
| **Pi** | `.pi/hooks/invar.ts` | TypeScript with fs config reading |
|
|
56
|
+
|
|
57
|
+
Both read the same `.claude/settings.local.json` configuration file.
|
|
58
|
+
|
|
19
59
|
### PostTaskCompletion Hook (Waiting for Claude Code Support)
|
|
20
60
|
|
|
21
61
|
**Proposed configuration in `.claude/settings.json`**:
|
|
@@ -69,24 +109,46 @@ The `/invar-reflect` skill can be triggered:
|
|
|
69
109
|
|
|
70
110
|
## Triggering Conditions
|
|
71
111
|
|
|
72
|
-
The hook triggers when **ALL** conditions are met:
|
|
73
112
|
|
|
74
|
-
|
|
75
|
-
2. ✅ **Message count >= 30** - Sufficient context for meaningful feedback
|
|
76
|
-
3. ✅ **Duration >= 2 hours** - Non-trivial session (avoids quick fixes)
|
|
113
|
+
The hook triggers when the message count reaches the configured threshold (default: 30).
|
|
77
114
|
|
|
78
|
-
**
|
|
115
|
+
**Conditions**:
|
|
79
116
|
|
|
80
|
-
|
|
117
|
+
1. ✅ **Message count >= min_messages** (default: 30)
|
|
118
|
+
2. ✅ **Feedback enabled** (`feedback.enabled = true`)
|
|
119
|
+
|
|
120
|
+
**No hard frequency cap**: Users can run `/invar-reflect` manually at any time.
|
|
121
|
+
|
|
122
|
+
**Customizing threshold**:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"feedback": {
|
|
127
|
+
"enabled": true,
|
|
128
|
+
"min_messages": 50 // Trigger at 50 messages instead of 30
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
81
132
|
|
|
82
133
|
## Silent Mode
|
|
83
134
|
|
|
84
|
-
When `mode: "silent"`:
|
|
85
|
-
- Feedback generation runs in background
|
|
86
|
-
- No interruption to current conversation
|
|
87
|
-
- User sees notification only: `✓ Feedback saved to .invar/feedback/feedback-{date}.md`
|
|
88
135
|
|
|
89
|
-
|
|
136
|
+
The hook displays a **reminder** when the threshold is reached:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
<system-reminder>
|
|
140
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
141
|
+
📊 Invar: Auto-triggering usage feedback (30 messages)
|
|
142
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
143
|
+
|
|
144
|
+
Session has reached 30 messages. Consider running /invar-reflect
|
|
145
|
+
to generate usage feedback.
|
|
146
|
+
|
|
147
|
+
To disable: Set feedback.enabled=false in .claude/settings.local.json
|
|
148
|
+
</system-reminder>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Note**: The agent sees this reminder and can choose to invoke `/invar-reflect` or continue working.
|
|
90
152
|
|
|
91
153
|
## User Control
|
|
92
154
|
|
|
@@ -144,7 +206,68 @@ rm .invar/feedback/feedback-2026-01-03.md
|
|
|
144
206
|
|
|
145
207
|
## Workaround: Using Stop Hook (Until PostTaskCompletion is Available)
|
|
146
208
|
|
|
147
|
-
|
|
209
|
+
|
|
210
|
+
### Implementation Details
|
|
211
|
+
|
|
212
|
+
**Claude Code Hook** (`.claude/hooks/invar.UserPromptSubmit.sh`):
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# DX-79: Feedback trigger at threshold
|
|
216
|
+
FEEDBACK_ENABLED=true
|
|
217
|
+
MIN_MESSAGES=30
|
|
218
|
+
|
|
219
|
+
if [[ -f ".claude/settings.local.json" ]]; then
|
|
220
|
+
if command -v jq &> /dev/null; then
|
|
221
|
+
FEEDBACK_ENABLED=$(jq -r '.feedback.enabled // true' .claude/settings.local.json)
|
|
222
|
+
MIN_MESSAGES=$(jq -r '.feedback.min_messages // 30' .claude/settings.local.json)
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
if [[ "$FEEDBACK_ENABLED" == "true" && $COUNT -eq $MIN_MESSAGES ]]; then
|
|
227
|
+
echo "<system-reminder>"
|
|
228
|
+
echo "📊 Invar: Auto-triggering usage feedback ($COUNT messages)"
|
|
229
|
+
echo "Consider running /invar-reflect to generate usage feedback."
|
|
230
|
+
echo "</system-reminder>"
|
|
231
|
+
fi
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Pi Hook** (`.pi/hooks/invar.ts`):
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// DX-79: Helper to read feedback configuration
|
|
238
|
+
function readFeedbackConfig() {
|
|
239
|
+
try {
|
|
240
|
+
const fs = require("fs");
|
|
241
|
+
const settingsPath = ".claude/settings.local.json";
|
|
242
|
+
if (fs.existsSync(settingsPath)) {
|
|
243
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
244
|
+
return {
|
|
245
|
+
enabled: settings.feedback?.enabled ?? true,
|
|
246
|
+
min_messages: settings.feedback?.min_messages ?? 30,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
// Ignore errors, use defaults
|
|
251
|
+
}
|
|
252
|
+
return { enabled: true, min_messages: 30 };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
pi.on("agent_start", async () => {
|
|
256
|
+
msgCount++;
|
|
257
|
+
|
|
258
|
+
// ... protocol refresh logic ...
|
|
259
|
+
|
|
260
|
+
const feedbackConfig = readFeedbackConfig();
|
|
261
|
+
if (msgCount === feedbackConfig.min_messages && feedbackConfig.enabled) {
|
|
262
|
+
pi.send(`<system-reminder>
|
|
263
|
+
📊 Invar: Auto-triggering usage feedback (${msgCount} messages)
|
|
264
|
+
Consider running /invar-reflect to generate usage feedback.
|
|
265
|
+
</system-reminder>`);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Installation**: Hooks are automatically installed via `invar init --claude` or `invar init --pi`.
|
|
148
271
|
|
|
149
272
|
### Stop Hook Implementation
|
|
150
273
|
|
|
@@ -332,24 +455,26 @@ Enable automatic feedback collection? [Y/n]:
|
|
|
332
455
|
|
|
333
456
|
## Phase B Status
|
|
334
457
|
|
|
335
|
-
**Completed**:
|
|
336
|
-
- ✅ Hook schema designed
|
|
337
|
-
- ✅ Configuration structure defined
|
|
338
|
-
- ✅ User control mechanism specified
|
|
339
|
-
- ✅ Stop hook workaround documented
|
|
340
458
|
|
|
341
|
-
**
|
|
342
|
-
-
|
|
343
|
-
-
|
|
344
|
-
-
|
|
459
|
+
**Completed** (v1.15.0):
|
|
460
|
+
- ✅ Message Count trigger strategy designed
|
|
461
|
+
- ✅ Cross-platform implementation (Claude Code + Pi)
|
|
462
|
+
- ✅ Shared configuration structure
|
|
463
|
+
- ✅ Hook templates updated
|
|
464
|
+
- ✅ Installation via `invar init`
|
|
345
465
|
|
|
346
|
-
**
|
|
347
|
-
-
|
|
348
|
-
-
|
|
349
|
-
-
|
|
466
|
+
**Replaced PostTaskCompletion with Message Count** because:
|
|
467
|
+
- PostTaskCompletion hook not supported by Claude Code or Pi
|
|
468
|
+
- Message count is universally implementable
|
|
469
|
+
- Simpler, more predictable trigger mechanism
|
|
470
|
+
- User has full control via config
|
|
350
471
|
|
|
351
|
-
|
|
472
|
+
**Testing**:
|
|
473
|
+
- Manual `/invar-reflect` invocation: Works
|
|
474
|
+
- Hook trigger at threshold: Implemented
|
|
475
|
+
- Config disable: Honored by both hooks
|
|
476
|
+
- Multi-agent setup: Both hooks installed
|
|
352
477
|
|
|
353
|
-
**
|
|
354
|
-
|
|
355
|
-
|
|
478
|
+
**Next Steps**:
|
|
479
|
+
- Monitor user feedback on threshold defaults
|
|
480
|
+
- Consider adding reminder messages at other checkpoints (e.g., 60, 90 messages)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: invar-tools
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.15.0
|
|
4
4
|
Summary: AI-native software engineering tools with design-by-contract verification
|
|
5
5
|
Project-URL: Homepage, https://github.com/tefx/invar
|
|
6
6
|
Project-URL: Documentation, https://github.com/tefx/invar#readme
|
|
@@ -190,8 +190,9 @@ cd your-project
|
|
|
190
190
|
uvx invar-tools init
|
|
191
191
|
|
|
192
192
|
# Or quick setup (skip prompts)
|
|
193
|
-
uvx invar-tools init --claude # Claude Code
|
|
194
|
-
uvx invar-tools init --pi # Pi
|
|
193
|
+
uvx invar-tools init --claude # Claude Code only
|
|
194
|
+
uvx invar-tools init --pi # Pi only
|
|
195
|
+
uvx invar-tools init --claude --pi # Both agents (DX-81)
|
|
195
196
|
uvx invar-tools init --mcp-only # MCP tools only (legacy projects)
|
|
196
197
|
|
|
197
198
|
# Add runtime contracts to your project
|
|
@@ -492,6 +493,7 @@ AlphaCodium · Parsel · Reflexion · Clover
|
|
|
492
493
|
|-------|--------|-------|
|
|
493
494
|
| **Claude Code** | ✅ Full | `invar init --claude` |
|
|
494
495
|
| **[Pi](https://shittycodingagent.ai/)** | ✅ Full | `invar init --pi` |
|
|
496
|
+
| **Multi-Agent** | ✅ Full | `invar init --claude --pi` (DX-81) |
|
|
495
497
|
| **Cursor** | ✅ MCP | `invar init` → select Other, add MCP config |
|
|
496
498
|
| **Other** | 📝 Manual | `invar init` → select Other, include `AGENT.md` in prompt |
|
|
497
499
|
|
|
@@ -821,6 +823,7 @@ rules = ["missing_contract", "shell_result"]
|
|
|
821
823
|
| `invar init` | Initialize or update project (interactive) |
|
|
822
824
|
| `invar init --claude` | Quick setup for Claude Code |
|
|
823
825
|
| `invar init --pi` | Quick setup for Pi agent |
|
|
826
|
+
| `invar init --claude --pi` | Setup for both agents (DX-81) |
|
|
824
827
|
| `invar init --mcp-only` | MCP tools only (no framework files) |
|
|
825
828
|
| `invar uninstall` | Remove Invar from project (preserves user content) |
|
|
826
829
|
| `invar sig <file>` | Show signatures and contracts |
|
|
@@ -72,6 +72,7 @@ invar/shell/mcp_config.py,sha256=-hC7Y5BGuVs285b6gBARk7ZyzVxHwPgXSyt_GoN0jfs,458
|
|
|
72
72
|
invar/shell/mutation.py,sha256=Lfyk2b8j8-hxAq-iwAgQeOhr7Ci6c5tRF1TXe3CxQCs,8914
|
|
73
73
|
invar/shell/pattern_integration.py,sha256=pRcjfq3NvMW_tvQCnaXZnD1k5AVEWK8CYOE2jN6VTro,7842
|
|
74
74
|
invar/shell/pi_hooks.py,sha256=ulZc1sP8mTRJTBsjwFHQzUgg-h8ajRIMp7iF1Y4UUtw,6885
|
|
75
|
+
invar/shell/pi_tools.py,sha256=_xTxE3zeEWSUm3IuuMziglkB_nL8NIco7kQ2nZkCMLU,3668
|
|
75
76
|
invar/shell/property_tests.py,sha256=N9JreyH5PqR89oF5yLcX7ZAV-Koyg5BKo-J05-GUPsA,9109
|
|
76
77
|
invar/shell/py_refs.py,sha256=Vjz50lmt9prDBcBv4nkkODdiJ7_DKu5zO4UPZBjAfmM,4638
|
|
77
78
|
invar/shell/skill_manager.py,sha256=Mr7Mh9rxPSKSAOTJCAM5ZHiG5nfUf6KQVCuD4LBNHSI,12440
|
|
@@ -85,7 +86,7 @@ invar/shell/commands/doc.py,sha256=SOLDoCXXGxx_JU0PKXlAIGEF36PzconHmmAtL-rM6D4,1
|
|
|
85
86
|
invar/shell/commands/feedback.py,sha256=lLxEeWW_71US_vlmorFrGXS8IARB9nbV6D0zruLs660,7640
|
|
86
87
|
invar/shell/commands/guard.py,sha256=xTQ8cPp-x1xMCtufKxmMNUSpIpH31uUjziAB8ifCnC0,24837
|
|
87
88
|
invar/shell/commands/hooks.py,sha256=W-SOnT4VQyUvXwipozkJwgEYfiOJGz7wksrbcdWegUg,2356
|
|
88
|
-
invar/shell/commands/init.py,sha256=
|
|
89
|
+
invar/shell/commands/init.py,sha256=vaPo0p7xBm3Nfgu9ytcvAjgk4dQBKvyEhrz_Cg1URMQ,23557
|
|
89
90
|
invar/shell/commands/merge.py,sha256=nuvKo8m32-OL-SCQlS4SLKmOZxQ3qj-1nGCx1Pgzifw,8183
|
|
90
91
|
invar/shell/commands/mutate.py,sha256=GwemiO6LlbGCBEQsBFnzZuKhF-wIMEl79GAMnKUWc8U,5765
|
|
91
92
|
invar/shell/commands/perception.py,sha256=HewSv6Kv8Gw2UQqkGY2rP5YKlnwyC3LBrQ2hFVXXw30,19304
|
|
@@ -135,13 +136,14 @@ invar/templates/examples/typescript/workflow.md,sha256=5byADjA3WgOgiDbkEtVRKKGvl
|
|
|
135
136
|
invar/templates/hooks/PostToolUse.sh.jinja,sha256=JHJGMdF3xp2qEqkPC9GaLp0NCa5gdRzqAmgRy4IldBg,3428
|
|
136
137
|
invar/templates/hooks/PreToolUse.sh.jinja,sha256=tZb-FGFxOBtTprUfeChau7rZOMPII69_5HSF-i_WD4Q,3558
|
|
137
138
|
invar/templates/hooks/Stop.sh.jinja,sha256=SD0PhBPeun7DTvn8Erbz11PBGAwGby4tMTd97yOJuTQ,981
|
|
138
|
-
invar/templates/hooks/UserPromptSubmit.sh.jinja,sha256=
|
|
139
|
+
invar/templates/hooks/UserPromptSubmit.sh.jinja,sha256=5xs-ASw8s_tHLvtA30nm5J7PhbpIML-8Hhv-QTTuhKY,3981
|
|
139
140
|
invar/templates/hooks/__init__.py,sha256=RnnMoQA-8eqbr8Y_1Vu9B8h5vAz4C-vmo8wgdcGYrz0,43
|
|
140
|
-
invar/templates/hooks/pi/invar.ts.jinja,sha256=
|
|
141
|
+
invar/templates/hooks/pi/invar.ts.jinja,sha256=WLEYwO8tvdu9RakxFtcVRuBmaE3HgRAUFf9T5OmgP0s,4027
|
|
141
142
|
invar/templates/onboard/assessment.md.jinja,sha256=EzqF0VUcxJZG2bVJLxTOyQlAERRbh9v9hXKVt6vcbxY,5850
|
|
142
143
|
invar/templates/onboard/roadmap.md.jinja,sha256=gmvZk4Hdwe0l3qSFV15QGcsr-OPMhsc6-1K9F2SFSIQ,3939
|
|
143
144
|
invar/templates/onboard/patterns/python.md,sha256=3wwucAcQz0DlggtpqYo-ZCnmrXgBQ0aBgUHN_EZ1VW0,8681
|
|
144
145
|
invar/templates/onboard/patterns/typescript.md,sha256=yOVfHtdAdjKkWNh66_dR7z2xEA4sggbIcCKthW-fqac,11983
|
|
146
|
+
invar/templates/pi-tools/invar/index.ts,sha256=fhE3aGKk5VYcUdbeVMv-x3tqWHA0eVU641pytY06Bvg,6694
|
|
145
147
|
invar/templates/protocol/INVAR.md.jinja,sha256=t2ZIQZJvzDTJMrRw_ijUo6ScZmeNK0-nV-H7ztTIyQQ,1464
|
|
146
148
|
invar/templates/protocol/python/architecture-examples.md,sha256=O96LH9WFpk7G9MrhSbifLS5pyibTIDG-_EGFF7g3V4M,1175
|
|
147
149
|
invar/templates/protocol/python/contracts-syntax.md,sha256=Q6supTQ3tChVrlN7xhcdb3Q8VGIESxQLA-mQvrNIZmo,1162
|
|
@@ -173,16 +175,16 @@ invar/templates/skills/extensions/security/SKILL.md,sha256=5mLwf4JP82Wq1vKkDIJwi
|
|
|
173
175
|
invar/templates/skills/extensions/security/patterns/_common.yaml,sha256=75BvSABWUtO1VXFvdsMgqi86J1759T4ROhYYcizSygQ,3680
|
|
174
176
|
invar/templates/skills/extensions/security/patterns/python.yaml,sha256=osyR8mWiyjW6tWjZA7QZfBIiim7XqgBYnrE45ktDx50,4658
|
|
175
177
|
invar/templates/skills/extensions/security/patterns/typescript.yaml,sha256=qDEg-sxSE63Bis2IZG1y4L8m8g2ZYkC29o6t-J_LmUo,5508
|
|
176
|
-
invar/templates/skills/invar-reflect/CONFIG.md,sha256=
|
|
178
|
+
invar/templates/skills/invar-reflect/CONFIG.md,sha256=G8KVNROQIm7CvjrB7cwzOANQOfJg3-1_0zRVZR1sX44,12852
|
|
177
179
|
invar/templates/skills/invar-reflect/SKILL.md,sha256=z0Nyh9JasZr70XhE9Es7IslxL3C8wH2lf8RewQi4Lbs,11285
|
|
178
180
|
invar/templates/skills/invar-reflect/template.md,sha256=Rr5hvbllvmd8jSLf_0ZjyKt6KOod0RlNdCtZJ3lYjiM,10470
|
|
179
181
|
invar/templates/skills/investigate/SKILL.md.jinja,sha256=cp6TBEixBYh1rLeeHOR1yqEnFqv1NZYePORMnavLkQI,3231
|
|
180
182
|
invar/templates/skills/propose/SKILL.md.jinja,sha256=6BuKiCqO1AEu3VtzMHy1QWGqr_xqG9eJlhbsKT4jev4,3463
|
|
181
183
|
invar/templates/skills/review/SKILL.md.jinja,sha256=ET5mbdSe_eKgJbi2LbgFC-z1aviKcHOBw7J5Q28fr4U,14105
|
|
182
|
-
invar_tools-1.
|
|
183
|
-
invar_tools-1.
|
|
184
|
-
invar_tools-1.
|
|
185
|
-
invar_tools-1.
|
|
186
|
-
invar_tools-1.
|
|
187
|
-
invar_tools-1.
|
|
188
|
-
invar_tools-1.
|
|
184
|
+
invar_tools-1.15.0.dist-info/METADATA,sha256=Xm0Suh3a3jfCfDBqzsuYpuVRwT4dWxRF5-PnZDMgYas,28595
|
|
185
|
+
invar_tools-1.15.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
186
|
+
invar_tools-1.15.0.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
|
|
187
|
+
invar_tools-1.15.0.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
|
|
188
|
+
invar_tools-1.15.0.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
|
|
189
|
+
invar_tools-1.15.0.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
|
|
190
|
+
invar_tools-1.15.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|