invar-tools 1.6.0__py3-none-any.whl → 1.7.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.
@@ -521,9 +521,11 @@ from invar.shell.commands.init import init
521
521
  from invar.shell.commands.mutate import mutate # DX-28
522
522
  from invar.shell.commands.sync_self import sync_self # DX-49
523
523
  from invar.shell.commands.test import test, verify
524
+ from invar.shell.commands.uninstall import uninstall # DX-69
524
525
  from invar.shell.commands.update import update
525
526
 
526
527
  app.command()(init)
528
+ app.command()(uninstall) # DX-69: Remove Invar from project
527
529
  app.command()(update)
528
530
  app.command()(test)
529
531
  app.command()(verify)
@@ -38,8 +38,6 @@ from invar.shell.mcp_config import (
38
38
  from invar.shell.template_engine import generate_from_manifest
39
39
  from invar.shell.templates import (
40
40
  add_config,
41
- add_invar_reference,
42
- create_agent_config,
43
41
  create_directories,
44
42
  detect_agent_configs,
45
43
  install_hooks,
@@ -65,18 +63,17 @@ def run_claude_init(path: Path) -> bool:
65
63
 
66
64
  console.print("\n[bold]Running claude /init...[/bold]")
67
65
  try:
66
+ # Don't capture output - claude /init is interactive and needs user input
68
67
  result = subprocess.run(
69
68
  ["claude", "/init"],
70
69
  cwd=path,
71
- capture_output=True,
72
- text=True,
73
70
  timeout=120,
74
71
  )
75
72
  if result.returncode == 0:
76
73
  console.print("[green]claude /init completed successfully[/green]")
77
74
  return True
78
75
  else:
79
- console.print(f"[yellow]Warning:[/yellow] claude /init failed: {result.stderr}")
76
+ console.print("[yellow]Warning:[/yellow] claude /init failed")
80
77
  return False
81
78
  except subprocess.TimeoutExpired:
82
79
  console.print("[yellow]Warning:[/yellow] claude /init timed out")
@@ -86,57 +83,6 @@ def run_claude_init(path: Path) -> bool:
86
83
  return False
87
84
 
88
85
 
89
- def append_invar_reference_to_claude_md(path: Path) -> bool:
90
- """
91
- Append Invar reference to existing CLAUDE.md.
92
-
93
- Preserves content generated by 'claude /init'.
94
- Returns True if modified, False otherwise.
95
- """
96
- claude_md = path / "CLAUDE.md"
97
- if not claude_md.exists():
98
- return False
99
-
100
- content = claude_md.read_text()
101
- if "INVAR.md" in content:
102
- console.print("[dim]CLAUDE.md already references INVAR.md[/dim]")
103
- return False
104
-
105
- # Append reference at the end
106
- invar_reference = """
107
-
108
- ---
109
-
110
- ## Invar Protocol
111
-
112
- > **Protocol:** Follow [INVAR.md](./INVAR.md) — includes Check-In, USBV workflow, and Task Completion.
113
-
114
- ### Check-In
115
-
116
- Your first message MUST display:
117
-
118
- ```
119
- ✓ Check-In: [project] | [branch] | [clean/dirty]
120
- ```
121
-
122
- Read `.invar/context.md` first. Do NOT run guard/map at Check-In.
123
-
124
- ### Final
125
-
126
- Your last message MUST display:
127
-
128
- ```
129
- ✓ Final: guard PASS | 0 errors, 2 warnings
130
- ```
131
-
132
- Execute `invar guard` and show this one-line summary.
133
- """
134
-
135
- claude_md.write_text(content + invar_reference)
136
- console.print("[green]Updated[/green] CLAUDE.md (added Invar reference)")
137
- return True
138
-
139
-
140
86
  # @shell_complexity: MCP config with method selection and validation
141
87
  def configure_mcp_with_method(
142
88
  path: Path, mcp_method: str | None
@@ -312,11 +258,9 @@ def init(
312
258
  return
313
259
 
314
260
  # DX-21B: Run claude /init if requested (before sync)
261
+ # DX-69: sync_templates() will merge claude's CLAUDE.md with invar template
315
262
  if claude:
316
- claude_success = run_claude_init(path)
317
- if claude_success:
318
- # Append Invar reference to generated CLAUDE.md
319
- append_invar_reference_to_claude_md(path)
263
+ run_claude_init(path)
320
264
 
321
265
  config_result = add_config(path, console)
322
266
  if isinstance(config_result, Failure):
@@ -371,28 +315,13 @@ def init(
371
315
  if isinstance(result, Success) and result.unwrap():
372
316
  console.print("[green]Created[/green] .invar/proposals/TEMPLATE.md")
373
317
 
374
- # Agent detection and configuration (DX-11)
318
+ # Agent detection (DX-69: simplified, only Claude Code supported)
375
319
  console.print("\n[bold]Checking for agent configurations...[/bold]")
376
320
  agent_result = detect_agent_configs(path)
377
- if isinstance(agent_result, Failure):
378
- console.print(f"[yellow]Warning:[/yellow] {agent_result.failure()}")
379
- agent_status: dict[str, str] = {}
380
- else:
321
+ if isinstance(agent_result, Success):
381
322
  agent_status = agent_result.unwrap()
382
-
383
- # Handle agent configs (DX-11, DX-17)
384
- for agent, status in agent_status.items():
385
- if status == "configured":
386
- console.print(f" [green]✓[/green] {agent}: already configured")
387
- elif status == "found":
388
- # Existing file without Invar reference - ask before modifying
389
- if yes or typer.confirm(f" Add Invar reference to {agent} config?", default=True):
390
- add_invar_reference(path, agent, console)
391
- else:
392
- console.print(f" [yellow]○[/yellow] {agent}: skipped")
393
- elif status == "not_found":
394
- # Create full template with workflow enforcement (DX-17)
395
- create_agent_config(path, agent, console)
323
+ if agent_status.get("claude") == "configured":
324
+ console.print(" [green]✓[/green] claude: already configured")
396
325
 
397
326
  # Configure MCP server (DX-16, DX-21B)
398
327
  configure_mcp_with_method(path, mcp_method)
@@ -0,0 +1,341 @@
1
+ """
2
+ DX-69: Uninstall Invar from a project.
3
+
4
+ Safely removes Invar files and configurations while preserving user content.
5
+ Uses marker-based detection to identify Invar-generated content.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import re
12
+ import shutil
13
+ from pathlib import Path
14
+
15
+ import typer
16
+ from rich.console import Console
17
+
18
+ console = Console()
19
+
20
+
21
+ def has_invar_marker(path: Path) -> bool:
22
+ """Check if a file has Invar markers (_invar: or <!--invar:)."""
23
+ try:
24
+ content = path.read_text()
25
+ return "_invar:" in content or "<!--invar:" in content
26
+ except (OSError, UnicodeDecodeError):
27
+ return False
28
+
29
+
30
+ def has_invar_region_marker(path: Path) -> bool:
31
+ """Check if a file has # invar:begin marker."""
32
+ try:
33
+ content = path.read_text()
34
+ return "# invar:begin" in content
35
+ except (OSError, UnicodeDecodeError):
36
+ return False
37
+
38
+
39
+ def has_invar_hook_marker(path: Path) -> bool:
40
+ """Check if a hook file has invar marker."""
41
+ try:
42
+ content = path.read_text()
43
+ # Invar hooks have specific patterns
44
+ return "invar" in content.lower() and (
45
+ "INVAR_" in content
46
+ or "invar guard" in content
47
+ or "invar_guard" in content
48
+ or "invar." in content.lower() # wrapper files: source invar.PreToolUse.sh
49
+ or "invar hook" in content.lower() # comment: # Invar hook wrapper
50
+ )
51
+ except (OSError, UnicodeDecodeError):
52
+ return False
53
+
54
+
55
+ # @shell_orchestration: Regex patterns tightly coupled to file removal logic
56
+ def remove_invar_regions(content: str) -> str:
57
+ """Remove <!--invar:xxx-->...<!--/invar:xxx--> regions except user region."""
58
+ patterns = [
59
+ # HTML-style regions (CLAUDE.md)
60
+ (r"<!--invar:critical-->.*?<!--/invar:critical-->\n?", ""),
61
+ (r"<!--invar:managed[^>]*-->.*?<!--/invar:managed-->\n?", ""),
62
+ (r"<!--invar:project-->.*?<!--/invar:project-->\n?", ""),
63
+ # Comment-style regions (.pre-commit-config.yaml)
64
+ (r"# invar:begin\n.*?# invar:end\n?", ""),
65
+ ]
66
+ for pattern, replacement in patterns:
67
+ content = re.sub(pattern, replacement, content, flags=re.DOTALL)
68
+ return content.strip()
69
+
70
+
71
+ def remove_mcp_invar_entry(path: Path) -> tuple[bool, str]:
72
+ """Remove invar entry from .mcp.json, return (modified, new_content)."""
73
+ try:
74
+ content = path.read_text()
75
+ data = json.loads(content)
76
+ if "mcpServers" in data and "invar" in data["mcpServers"]:
77
+ del data["mcpServers"]["invar"]
78
+ # If no servers left, indicate file can be deleted
79
+ if not data["mcpServers"]:
80
+ return True, ""
81
+ return True, json.dumps(data, indent=2)
82
+ return False, content
83
+ except (OSError, json.JSONDecodeError):
84
+ return False, ""
85
+
86
+
87
+ # @shell_complexity: Multi-file type detection requires comprehensive branching
88
+ def collect_removal_targets(path: Path) -> dict:
89
+ """Collect files and directories to remove/modify."""
90
+ targets = {
91
+ "delete_dirs": [],
92
+ "delete_files": [],
93
+ "modify_files": [],
94
+ "skip": [],
95
+ }
96
+
97
+ # Directories to delete entirely
98
+ invar_dir = path / ".invar"
99
+ if invar_dir.exists():
100
+ targets["delete_dirs"].append((".invar/", "directory"))
101
+
102
+ # Files to delete entirely
103
+ for file_name, description in [
104
+ ("invar.toml", "config"),
105
+ ("INVAR.md", "protocol"),
106
+ ]:
107
+ file_path = path / file_name
108
+ if file_path.exists():
109
+ targets["delete_files"].append((file_name, description))
110
+
111
+ # Skills with _invar marker
112
+ skills_dir = path / ".claude" / "skills"
113
+ if skills_dir.exists():
114
+ for skill_dir in skills_dir.iterdir():
115
+ if skill_dir.is_dir():
116
+ skill_file = skill_dir / "SKILL.md"
117
+ if skill_file.exists():
118
+ if has_invar_marker(skill_file):
119
+ targets["delete_dirs"].append(
120
+ (f".claude/skills/{skill_dir.name}/", "skill, has _invar marker")
121
+ )
122
+ else:
123
+ targets["skip"].append(
124
+ (f".claude/skills/{skill_dir.name}/", "no _invar marker")
125
+ )
126
+
127
+ # Commands with _invar marker
128
+ commands_dir = path / ".claude" / "commands"
129
+ if commands_dir.exists():
130
+ for cmd_file in commands_dir.glob("*.md"):
131
+ if has_invar_marker(cmd_file):
132
+ targets["delete_files"].append(
133
+ (f".claude/commands/{cmd_file.name}", "command, has _invar marker")
134
+ )
135
+ else:
136
+ targets["skip"].append(
137
+ (f".claude/commands/{cmd_file.name}", "no _invar marker")
138
+ )
139
+
140
+ # Hooks with invar marker
141
+ hooks_dir = path / ".claude" / "hooks"
142
+ if hooks_dir.exists():
143
+ for hook_file in hooks_dir.glob("*.sh"):
144
+ if has_invar_hook_marker(hook_file):
145
+ targets["delete_files"].append(
146
+ (f".claude/hooks/{hook_file.name}", "hook, has invar marker")
147
+ )
148
+
149
+ # CLAUDE.md - modify, not delete
150
+ claude_md = path / "CLAUDE.md"
151
+ if claude_md.exists():
152
+ content = claude_md.read_text()
153
+ if "<!--invar:" in content:
154
+ # Check if there's user content
155
+ has_user_region = "<!--invar:user-->" in content
156
+ targets["modify_files"].append(
157
+ ("CLAUDE.md", f"remove invar regions{', keep user region' if has_user_region else ''}")
158
+ )
159
+
160
+ # .mcp.json - modify or delete
161
+ mcp_json = path / ".mcp.json"
162
+ if mcp_json.exists():
163
+ modified, new_content = remove_mcp_invar_entry(mcp_json)
164
+ if modified:
165
+ if new_content:
166
+ targets["modify_files"].append((".mcp.json", "remove mcpServers.invar"))
167
+ else:
168
+ targets["delete_files"].append((".mcp.json", "only had invar config"))
169
+
170
+ # Config files with region markers (DX-69: cursor/aider removed)
171
+ for file_name in [".pre-commit-config.yaml"]:
172
+ file_path = path / file_name
173
+ if file_path.exists():
174
+ if has_invar_region_marker(file_path):
175
+ content = file_path.read_text()
176
+ cleaned = remove_invar_regions(content)
177
+ if cleaned:
178
+ targets["modify_files"].append((file_name, "remove invar:begin..end block"))
179
+ else:
180
+ targets["delete_files"].append((file_name, "only had invar content"))
181
+ else:
182
+ targets["skip"].append((file_name, "no invar:begin marker"))
183
+
184
+ # Empty directories to clean up
185
+ for dir_name in ["src/core", "src/shell"]:
186
+ dir_path = path / dir_name
187
+ if dir_path.exists() and dir_path.is_dir():
188
+ if not any(dir_path.iterdir()):
189
+ targets["delete_dirs"].append((dir_name, "empty directory"))
190
+
191
+ return targets
192
+
193
+
194
+ # @shell_complexity: Rich output formatting for different target categories
195
+ def show_preview(targets: dict) -> None:
196
+ """Display what would be removed/modified."""
197
+ console.print("\n[bold]Invar Uninstall Preview[/bold]")
198
+ console.print("=" * 40)
199
+
200
+ if targets["delete_dirs"] or targets["delete_files"]:
201
+ console.print("\n[red]Will DELETE:[/red]")
202
+ for item, desc in targets["delete_dirs"]:
203
+ console.print(f" {item:40} ({desc})")
204
+ for item, desc in targets["delete_files"]:
205
+ console.print(f" {item:40} ({desc})")
206
+
207
+ if targets["modify_files"]:
208
+ console.print("\n[yellow]Will MODIFY:[/yellow]")
209
+ for item, desc in targets["modify_files"]:
210
+ console.print(f" {item:40} ({desc})")
211
+
212
+ if targets["skip"]:
213
+ console.print("\n[dim]Will SKIP:[/dim]")
214
+ for item, desc in targets["skip"]:
215
+ console.print(f" {item:40} ({desc})")
216
+
217
+ console.print()
218
+
219
+
220
+ # @shell_complexity: Different file types require different removal strategies
221
+ def execute_removal(path: Path, targets: dict) -> None:
222
+ """Execute the removal/modification operations."""
223
+ # Delete directories
224
+ for dir_name, _ in targets["delete_dirs"]:
225
+ dir_path = path / dir_name.rstrip("/")
226
+ if dir_path.exists():
227
+ shutil.rmtree(dir_path)
228
+ console.print(f"[red]Deleted[/red] {dir_name}")
229
+
230
+ # Delete files
231
+ for file_name, _ in targets["delete_files"]:
232
+ file_path = path / file_name
233
+ if file_path.exists():
234
+ file_path.unlink()
235
+ console.print(f"[red]Deleted[/red] {file_name}")
236
+
237
+ # Modify files
238
+ for file_name, _desc in targets["modify_files"]:
239
+ file_path = path / file_name
240
+ if not file_path.exists():
241
+ continue
242
+
243
+ if file_name == ".mcp.json":
244
+ modified, new_content = remove_mcp_invar_entry(file_path)
245
+ if modified and new_content:
246
+ file_path.write_text(new_content)
247
+ console.print(f"[yellow]Modified[/yellow] {file_name}")
248
+ else:
249
+ content = file_path.read_text()
250
+ cleaned = remove_invar_regions(content)
251
+ if cleaned:
252
+ file_path.write_text(cleaned + "\n")
253
+ console.print(f"[yellow]Modified[/yellow] {file_name}")
254
+ else:
255
+ file_path.unlink()
256
+ console.print(f"[red]Deleted[/red] {file_name} (empty after cleanup)")
257
+
258
+ # Clean up empty .claude directory if it exists and is empty
259
+ claude_dir = path / ".claude"
260
+ if claude_dir.exists():
261
+ # Check subdirectories
262
+ for subdir in ["skills", "commands", "hooks"]:
263
+ subdir_path = claude_dir / subdir
264
+ if subdir_path.exists() and not any(subdir_path.iterdir()):
265
+ subdir_path.rmdir()
266
+ console.print(f"[dim]Removed empty[/dim] .claude/{subdir}/")
267
+ # Check if .claude itself is empty
268
+ if not any(claude_dir.iterdir()):
269
+ claude_dir.rmdir()
270
+ console.print("[dim]Removed empty[/dim] .claude/")
271
+
272
+
273
+ def uninstall(
274
+ path: Path = typer.Argument(
275
+ Path(),
276
+ help="Project path",
277
+ exists=True,
278
+ file_okay=False,
279
+ dir_okay=True,
280
+ resolve_path=True,
281
+ ),
282
+ dry_run: bool = typer.Option(
283
+ False,
284
+ "--dry-run",
285
+ "-n",
286
+ help="Show what would be removed without removing",
287
+ ),
288
+ force: bool = typer.Option(
289
+ False,
290
+ "--force",
291
+ "-f",
292
+ help="Skip confirmation prompt",
293
+ ),
294
+ ) -> None:
295
+ """Remove Invar from a project.
296
+
297
+ Safely removes Invar-generated files and configurations while
298
+ preserving user content. Uses marker-based detection.
299
+
300
+ Examples:
301
+ invar uninstall --dry-run # Preview changes
302
+ invar uninstall # Remove with confirmation
303
+ invar uninstall --force # Remove without confirmation
304
+ """
305
+ # Check if this is an Invar project
306
+ invar_toml = path / "invar.toml"
307
+ invar_md = path / "INVAR.md"
308
+ invar_dir = path / ".invar"
309
+
310
+ if not (invar_toml.exists() or invar_md.exists() or invar_dir.exists()):
311
+ console.print("[yellow]Warning:[/yellow] This doesn't appear to be an Invar project.")
312
+ console.print("No invar.toml, INVAR.md, or .invar/ directory found.")
313
+ raise typer.Exit(1)
314
+
315
+ # Collect targets
316
+ targets = collect_removal_targets(path)
317
+
318
+ # Check if there's anything to do
319
+ if not any([targets["delete_dirs"], targets["delete_files"], targets["modify_files"]]):
320
+ console.print("[green]Nothing to remove.[/green] Project is clean.")
321
+ raise typer.Exit(0)
322
+
323
+ # Show preview
324
+ show_preview(targets)
325
+
326
+ # Dry run exits here
327
+ if dry_run:
328
+ console.print("[dim]Dry run complete. No changes made.[/dim]")
329
+ raise typer.Exit(0)
330
+
331
+ # Confirmation
332
+ if not force:
333
+ confirm = typer.confirm("Proceed with uninstall?", default=False)
334
+ if not confirm:
335
+ console.print("[dim]Cancelled.[/dim]")
336
+ raise typer.Exit(0)
337
+
338
+ # Execute
339
+ execute_removal(path, targets)
340
+
341
+ console.print("\n[green]✓ Invar has been removed from the project.[/green]")
invar/shell/templates.py CHANGED
@@ -187,30 +187,16 @@ def copy_skills_directory(dest: Path, console) -> Result[bool, str]:
187
187
  return Failure(f"Failed to copy skills: {e}")
188
188
 
189
189
 
190
- # Agent configuration for multi-agent support (DX-11, DX-17)
190
+ # Agent configuration - Claude Code only (DX-69: simplified, cursor/aider removed)
191
191
  AGENT_CONFIGS = {
192
192
  "claude": {
193
193
  "file": "CLAUDE.md",
194
194
  "template": "CLAUDE.md.template",
195
- "reference": '> **Protocol:** Follow [INVAR.md](./INVAR.md) for the Invar development methodology.\n',
196
- "check_pattern": "INVAR.md",
197
- },
198
- "cursor": {
199
- "file": ".cursorrules",
200
- "template": "cursorrules.template",
201
- "reference": "Follow the Invar Protocol in INVAR.md.\n\n",
202
- "check_pattern": "INVAR.md",
203
- },
204
- "aider": {
205
- "file": ".aider.conf.yml",
206
- "template": "aider.conf.yml.template",
207
- "reference": "# Follow the Invar Protocol in INVAR.md\nread:\n - INVAR.md\n",
208
195
  "check_pattern": "INVAR.md",
209
196
  },
210
197
  }
211
198
 
212
199
 
213
- # @shell_complexity: Agent config detection across multiple locations
214
200
  def detect_agent_configs(path: Path) -> Result[dict[str, str], str]:
215
201
  """
216
202
  Detect existing agent configuration files.
@@ -244,61 +230,6 @@ def detect_agent_configs(path: Path) -> Result[dict[str, str], str]:
244
230
  return Failure(f"Failed to detect agent configs: {e}")
245
231
 
246
232
 
247
- # @shell_complexity: Reference addition with existing check
248
- def add_invar_reference(path: Path, agent: str, console) -> Result[bool, str]:
249
- """Add Invar reference to an existing agent config file."""
250
- if agent not in AGENT_CONFIGS:
251
- return Failure(f"Unknown agent: {agent}")
252
-
253
- config = AGENT_CONFIGS[agent]
254
- config_path = path / config["file"]
255
-
256
- if not config_path.exists():
257
- return Failure(f"Config file not found: {config['file']}")
258
-
259
- try:
260
- content = config_path.read_text()
261
- if config["check_pattern"] in content:
262
- return Success(False) # Already configured
263
-
264
- # Prepend reference
265
- new_content = config["reference"] + content
266
- config_path.write_text(new_content)
267
- console.print(f"[green]Updated[/green] {config['file']} (added Invar reference)")
268
- return Success(True)
269
- except OSError as e:
270
- return Failure(f"Failed to update {config['file']}: {e}")
271
-
272
-
273
- # @shell_complexity: Config creation with template selection
274
- def create_agent_config(path: Path, agent: str, console) -> Result[bool, str]:
275
- """
276
- Create agent config from template (DX-17).
277
-
278
- Creates full template file for agents that don't have an existing config.
279
- """
280
- if agent not in AGENT_CONFIGS:
281
- return Failure(f"Unknown agent: {agent}")
282
-
283
- config = AGENT_CONFIGS[agent]
284
- config_path = path / config["file"]
285
-
286
- if config_path.exists():
287
- return Success(False) # Already exists
288
-
289
- # Use template if available
290
- template_name = config.get("template")
291
- if template_name:
292
- result = copy_template(template_name, path, config["file"])
293
- if isinstance(result, Success) and result.unwrap():
294
- console.print(f"[green]Created[/green] {config['file']} (Invar workflow enforcement)")
295
- return Success(True)
296
- elif isinstance(result, Failure):
297
- return result
298
-
299
- return Success(False)
300
-
301
-
302
233
  # @shell_complexity: MCP server config with JSON manipulation
303
234
  def configure_mcp_server(path: Path, console) -> Result[list[str], str]:
304
235
  """
@@ -1,3 +1,9 @@
1
+ ---
2
+ _invar:
3
+ version: "5.0"
4
+ type: command
5
+ ---
6
+
1
7
  # Audit
2
8
 
3
9
  Read-only code review. Reports issues without fixing them.
@@ -1,3 +1,9 @@
1
+ ---
2
+ _invar:
3
+ version: "5.0"
4
+ type: command
5
+ ---
6
+
1
7
  # Guard
2
8
 
3
9
  Run Invar verification on the project and report results.
@@ -1,3 +1,4 @@
1
+ # invar:begin
1
2
  # Invar Pre-commit Hooks
2
3
  # Install: pre-commit install
3
4
  #
@@ -42,3 +43,4 @@ repos:
42
43
  pass_filenames: false
43
44
  stages: [pre-commit]
44
45
  types: [python]
46
+ # invar:end
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: invar-tools
3
- Version: 1.6.0
3
+ Version: 1.7.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
@@ -59,18 +59,19 @@ invar/shell/pattern_integration.py,sha256=pRcjfq3NvMW_tvQCnaXZnD1k5AVEWK8CYOE2jN
59
59
  invar/shell/property_tests.py,sha256=N9JreyH5PqR89oF5yLcX7ZAV-Koyg5BKo-J05-GUPsA,9109
60
60
  invar/shell/subprocess_env.py,sha256=9oXl3eMEbzLsFEgMHqobEw6oW_wV0qMEP7pklwm58Pw,11453
61
61
  invar/shell/template_engine.py,sha256=IzOiGsKVFo0lDUdtg27wMzIJJKToclv151RDZuDnHHo,11027
62
- invar/shell/templates.py,sha256=l2En95E8jRVlojdQIqdZgRLVB43f_b1d_AJapKkozwA,15908
62
+ invar/shell/templates.py,sha256=wVG78wBoVB-SOugfqaKj6xklwSDpEyjeEOGE6EKcS0s,13387
63
63
  invar/shell/testing.py,sha256=rTNBH0Okh2qtG9ohSXOz487baQ2gXrWT3s_WECW3HJs,11143
64
64
  invar/shell/commands/__init__.py,sha256=MEkKwVyjI9DmkvBpJcuumXo2Pg_FFkfEr-Rr3nrAt7A,284
65
- invar/shell/commands/guard.py,sha256=QJhL3vI_PzAPuGbi2GhxOdmHSIXtaoNuJxjgXTW3JRQ,21595
65
+ invar/shell/commands/guard.py,sha256=vDBGOFb9mQ1D8eXrMvQB505GpjO1XLeCLrv2ig9-6dU,21718
66
66
  invar/shell/commands/hooks.py,sha256=W-SOnT4VQyUvXwipozkJwgEYfiOJGz7wksrbcdWegUg,2356
67
- invar/shell/commands/init.py,sha256=UzHLfAP9ddIY32HrLd32nUveFcHnB0HlbMcIgtfob9Y,18297
67
+ invar/shell/commands/init.py,sha256=7PhXVjnhsY-1o2kBWRfoUPvSjopB0jRjazLe4P_FyZQ,16339
68
68
  invar/shell/commands/merge.py,sha256=nuvKo8m32-OL-SCQlS4SLKmOZxQ3qj-1nGCx1Pgzifw,8183
69
69
  invar/shell/commands/mutate.py,sha256=GwemiO6LlbGCBEQsBFnzZuKhF-wIMEl79GAMnKUWc8U,5765
70
70
  invar/shell/commands/perception.py,sha256=TyH_HpqyKkmE3-zcU4YyBG8ghwJaSFeRC-OQMVBDTbQ,3837
71
71
  invar/shell/commands/sync_self.py,sha256=nmqBry7V2_enKwy2zzHg8UoedZNicLe3yKDhjmBeZ68,3880
72
72
  invar/shell/commands/template_sync.py,sha256=wVZ-UvJ1wpN2UBcWMfbei0n46XHYx-zRbMA2oX6FSi4,13723
73
73
  invar/shell/commands/test.py,sha256=goMf-ovvzEyWQMheq4YlJ-mwK5-w3lDj0cq0IA_1-_c,4205
74
+ invar/shell/commands/uninstall.py,sha256=f8kGBwkdYycQ5eIdfveIefTw9ZSU6D4OyUuHt8ScUTY,12166
74
75
  invar/shell/commands/update.py,sha256=0V5F8vxQ6PHPHPVYDmxdRD7xXeQEFypiJMYpY5ryiek,1349
75
76
  invar/shell/prove/__init__.py,sha256=ZqlbmyMFJf6yAle8634jFuPRv8wNvHps8loMlOJyf8A,240
76
77
  invar/shell/prove/accept.py,sha256=cnY_6jzU1EBnpLF8-zWUWcXiSXtCwxPsXEYXsSVPG38,3717
@@ -79,14 +80,12 @@ invar/shell/prove/crosshair.py,sha256=4Z_iIYBlkp-I6FqSYZa89wWB09V4Ouw2PduYhTn6rf
79
80
  invar/shell/prove/hypothesis.py,sha256=QUclOOUg_VB6wbmHw8O2EPiL5qBOeBRqQeM04AVuLw0,9880
80
81
  invar/templates/CLAUDE.md.template,sha256=eaGU3SyRO_NEifw5b26k3srgQH4jyeujjCJ-HbM36_w,4913
81
82
  invar/templates/__init__.py,sha256=cb3ht8KPK5oBn5oG6HsTznujmo9WriJ_P--fVxJwycc,45
82
- invar/templates/aider.conf.yml.template,sha256=4xzSs3BXzFJvwdhnWbmzSY0yCbfx5oxqnV8ZjehqHBg,853
83
83
  invar/templates/context.md.template,sha256=FKyI1ghpqcf4wftyv9-auIFHor8Nm8lETN45Ja-L8Og,2386
84
- invar/templates/cursorrules.template,sha256=N6AiEJRJHGkHm2tswh3PnZ_07ozeyQQI8iEOGK5Aqoc,1023
85
84
  invar/templates/manifest.toml,sha256=cEe7yEOOeaLmOF-PrwZXxiPGjHhsSJYkWBKRHDmSbac,4268
86
- invar/templates/pre-commit-config.yaml.template,sha256=2qWY3E8iDUqi85jE_X7y0atE88YOlL5IZ93wkjCgQGo,1737
85
+ invar/templates/pre-commit-config.yaml.template,sha256=ZMqiStLCf9cC4uL2JoF59aYv_G4AV-roGdkj1tWHCBc,1763
87
86
  invar/templates/proposal.md.template,sha256=UP7SpQ7gk8jVlHGLQCSQ5c-kCj1DBQEz8M-vEStK77I,1573
88
- invar/templates/commands/audit.md,sha256=eXBySlQrVyk054vYQWAZYzj-HgT2QXhpzziw6GlIeGM,4112
89
- invar/templates/commands/guard.md,sha256=PyeAKfrmlXsgbrTDypQqXmTDKK1JHKhHEQrHqftA7X0,1177
87
+ invar/templates/commands/audit.md,sha256=OrotO8420zTKnlNyAyL1Eos0VIaihzEU4AHdfDv68Oc,4162
88
+ invar/templates/commands/guard.md,sha256=N_C_AXd9kI85W1B0aTEycjiDp_jdaP8eeq8O0FQ_WQ8,1227
90
89
  invar/templates/config/CLAUDE.md.jinja,sha256=VbtDWxn3H8qiE9-DV1hlG3DJ-GcBQU4ZiUHbFh6Bxxk,7814
91
90
  invar/templates/config/context.md.jinja,sha256=_kJ8erEQNJMLDCKrv4BXWkO6OaGzE-zW9biCf7144aY,3103
92
91
  invar/templates/config/pre-commit.yaml.jinja,sha256=Qflmii8hngHciSgfa8mIlg3-E3D4b0xflm0-Q-cWcCc,1752
@@ -105,10 +104,10 @@ invar/templates/skills/develop/SKILL.md.jinja,sha256=3coPSZGh1-YKN9Xc_xcEkfEP3S0
105
104
  invar/templates/skills/investigate/SKILL.md.jinja,sha256=bOLdLMH5WUVBYOo4NpsfyvI6xx7I1lCNr_X-8bMe_kg,2744
106
105
  invar/templates/skills/propose/SKILL.md.jinja,sha256=_iDLYN6-cfzA8n0_8sv-Dnpm1xq9IIpcDyM10mU2WUA,2420
107
106
  invar/templates/skills/review/SKILL.md.jinja,sha256=e7HULz1jjLOlk2LYejQMk2F-cu7dDIwvh6lWNjx3j-Q,14123
108
- invar_tools-1.6.0.dist-info/METADATA,sha256=8_ISE8BAkc9wl7CmsPffsLT2mulMUhDAXF78v08ySPk,16964
109
- invar_tools-1.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
110
- invar_tools-1.6.0.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
111
- invar_tools-1.6.0.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
112
- invar_tools-1.6.0.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
113
- invar_tools-1.6.0.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
114
- invar_tools-1.6.0.dist-info/RECORD,,
107
+ invar_tools-1.7.0.dist-info/METADATA,sha256=JKBCWf7HkEIrD3m3VC7fHLA7REki-dlk1l0HxsgPFW4,16964
108
+ invar_tools-1.7.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
109
+ invar_tools-1.7.0.dist-info/entry_points.txt,sha256=RwH_EhqgtFPsnO6RcrwrAb70Zyfb8Mh6uUtztWnUxGk,102
110
+ invar_tools-1.7.0.dist-info/licenses/LICENSE,sha256=qeFksp4H4kfTgQxPCIu3OdagXyiZcgBlVfsQ6M5oFyk,10767
111
+ invar_tools-1.7.0.dist-info/licenses/LICENSE-GPL,sha256=IvZfC6ZbP7CLjytoHVzvpDZpD-Z3R_qa1GdMdWlWQ6Q,35157
112
+ invar_tools-1.7.0.dist-info/licenses/NOTICE,sha256=joEyMyFhFY8Vd8tTJ-a3SirI0m2Sd0WjzqYt3sdcglc,2561
113
+ invar_tools-1.7.0.dist-info/RECORD,,
@@ -1,31 +0,0 @@
1
- # Invar Protocol Configuration for Aider
2
- # Follow the Invar Protocol in INVAR.md
3
-
4
- # Auto-read protocol files at session start
5
- read:
6
- - INVAR.md
7
- - .invar/examples/contracts.py
8
- - .invar/examples/core_shell.py
9
- - .invar/context.md
10
-
11
- # System prompt addition
12
- system-prompt: |
13
- Follow the Invar Protocol in INVAR.md.
14
-
15
- ## Check-In
16
- Your first message MUST display:
17
- ✓ Check-In: [project] | [branch] | [clean/dirty]
18
-
19
- Read .invar/context.md first. Do NOT run guard/map at Check-In.
20
- This is your sign-in. No visible check-in = Session not started.
21
-
22
- ## Final
23
- Your last message MUST display:
24
- ✓ Final: guard PASS | 0 errors, 2 warnings
25
-
26
- Execute: invar guard
27
- This is your sign-out. Completes the Check-In/Final pair.
28
-
29
- ## Workflow (USBV)
30
- Understand → Specify → Build → Validate
31
- Inspect before Contract. Depth varies naturally.
@@ -1,40 +0,0 @@
1
- # Invar Protocol
2
-
3
- Follow the Invar Protocol in INVAR.md — includes Check-In, USBV workflow, and Task Completion requirements.
4
-
5
- ## Check-In
6
-
7
- Your first message MUST display:
8
-
9
- ```
10
- ✓ Check-In: [project] | [branch] | [clean/dirty]
11
- ```
12
-
13
- Actions:
14
- 1. Read `.invar/context.md` (Key Rules + Current State + Lessons Learned)
15
- 2. Show one-line status
16
-
17
- **Do NOT execute guard or map at Check-In.**
18
- Guard is for VALIDATE phase and Final only.
19
-
20
- This is your sign-in. The user sees it immediately.
21
- No visible check-in = Session not started.
22
-
23
- ## Final
24
-
25
- Your last message for an implementation task MUST display:
26
-
27
- ```
28
- ✓ Final: guard PASS | 0 errors, 2 warnings
29
- ```
30
-
31
- Execute `invar guard` and show this one-line summary.
32
-
33
- This is your sign-out. Completes the Check-In/Final pair.
34
-
35
- ## Quick Reference
36
-
37
- - Core (`**/core/**`): @pre/@post contracts, doctests, pure (no I/O)
38
- - Shell (`**/shell/**`): Result[T, E] return type
39
- - Workflow: Understand → Specify → Build → Validate (USBV)
40
- - Inspect before Contract. Depth varies naturally.