invar-tools 1.10.0__py3-none-any.whl → 1.12.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/core/doc_edit.py +187 -0
- invar/core/doc_parser.py +563 -0
- invar/core/ts_sig_parser.py +6 -3
- invar/mcp/handlers.py +436 -0
- invar/mcp/server.py +351 -156
- invar/node_tools/ts-query.js +396 -0
- invar/shell/commands/doc.py +409 -0
- invar/shell/commands/guard.py +29 -0
- invar/shell/commands/init.py +72 -13
- invar/shell/commands/perception.py +302 -6
- invar/shell/doc_tools.py +459 -0
- invar/shell/fs.py +15 -14
- invar/shell/prove/crosshair.py +3 -0
- invar/shell/prove/guard_ts.py +13 -10
- invar/shell/py_refs.py +156 -0
- invar/shell/skill_manager.py +17 -15
- invar/shell/ts_compiler.py +238 -0
- invar/templates/examples/typescript/patterns.md +193 -0
- invar/templates/skills/develop/SKILL.md.jinja +46 -0
- invar/templates/skills/review/SKILL.md.jinja +205 -493
- {invar_tools-1.10.0.dist-info → invar_tools-1.12.0.dist-info}/METADATA +58 -8
- {invar_tools-1.10.0.dist-info → invar_tools-1.12.0.dist-info}/RECORD +27 -18
- {invar_tools-1.10.0.dist-info → invar_tools-1.12.0.dist-info}/WHEEL +0 -0
- {invar_tools-1.10.0.dist-info → invar_tools-1.12.0.dist-info}/entry_points.txt +0 -0
- {invar_tools-1.10.0.dist-info → invar_tools-1.12.0.dist-info}/licenses/LICENSE +0 -0
- {invar_tools-1.10.0.dist-info → invar_tools-1.12.0.dist-info}/licenses/LICENSE-GPL +0 -0
- {invar_tools-1.10.0.dist-info → invar_tools-1.12.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -7,6 +7,7 @@ Shell module: handles file I/O for map and sig commands.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import json
|
|
10
|
+
from dataclasses import dataclass
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import TYPE_CHECKING
|
|
12
13
|
|
|
@@ -70,7 +71,11 @@ def run_sig(target: str, json_output: bool) -> Result[None, str]:
|
|
|
70
71
|
|
|
71
72
|
file_path = Path(file_path_str)
|
|
72
73
|
if not file_path.exists():
|
|
73
|
-
|
|
74
|
+
# DX-78 Phase B: Suggest alternative tools
|
|
75
|
+
return Failure(
|
|
76
|
+
f"File not found: {file_path}\n\n"
|
|
77
|
+
"💡 Try using Grep to search for the symbol across the codebase."
|
|
78
|
+
)
|
|
74
79
|
|
|
75
80
|
# Read file content
|
|
76
81
|
try:
|
|
@@ -122,7 +127,59 @@ def _run_sig_python(
|
|
|
122
127
|
def _run_sig_typescript(
|
|
123
128
|
content: str, file_path: Path, symbol_name: str | None, json_output: bool
|
|
124
129
|
) -> Result[None, str]:
|
|
125
|
-
"""Run sig for TypeScript files
|
|
130
|
+
"""Run sig for TypeScript files.
|
|
131
|
+
|
|
132
|
+
DX-78: Uses TS Compiler API when available, falls back to regex parser.
|
|
133
|
+
"""
|
|
134
|
+
from invar.shell.ts_compiler import is_typescript_available, run_sig_typescript
|
|
135
|
+
|
|
136
|
+
# Try TS Compiler API first (DX-78)
|
|
137
|
+
if is_typescript_available():
|
|
138
|
+
sig_result = run_sig_typescript(file_path)
|
|
139
|
+
if isinstance(sig_result, Success):
|
|
140
|
+
symbols = sig_result.unwrap()
|
|
141
|
+
|
|
142
|
+
# Filter by symbol name if specified
|
|
143
|
+
if symbol_name:
|
|
144
|
+
symbols = [s for s in symbols if s.name == symbol_name]
|
|
145
|
+
if not symbols:
|
|
146
|
+
return Failure(f"Symbol '{symbol_name}' not found in {file_path}")
|
|
147
|
+
|
|
148
|
+
# Output using TS Compiler API format
|
|
149
|
+
if json_output:
|
|
150
|
+
output = {
|
|
151
|
+
"file": str(file_path),
|
|
152
|
+
"symbols": [
|
|
153
|
+
{
|
|
154
|
+
"name": s.name,
|
|
155
|
+
"kind": s.kind,
|
|
156
|
+
"signature": s.signature,
|
|
157
|
+
"line": s.line,
|
|
158
|
+
"contracts": s.contracts,
|
|
159
|
+
"members": s.members,
|
|
160
|
+
}
|
|
161
|
+
for s in symbols
|
|
162
|
+
],
|
|
163
|
+
}
|
|
164
|
+
console.print(json.dumps(output, indent=2))
|
|
165
|
+
else:
|
|
166
|
+
console.print(f"[bold]{file_path}[/bold]")
|
|
167
|
+
for s in symbols:
|
|
168
|
+
console.print(f" [{s.kind}] {s.name}")
|
|
169
|
+
console.print(f" {s.signature}")
|
|
170
|
+
if s.contracts:
|
|
171
|
+
for pre in s.contracts.get("pre", []):
|
|
172
|
+
console.print(f" @pre {pre}")
|
|
173
|
+
for post in s.contracts.get("post", []):
|
|
174
|
+
console.print(f" @post {post}")
|
|
175
|
+
if s.members:
|
|
176
|
+
for m in s.members:
|
|
177
|
+
console.print(f" [{m['kind']}] {m['name']}: {m.get('signature', '')}")
|
|
178
|
+
console.print()
|
|
179
|
+
|
|
180
|
+
return Success(None)
|
|
181
|
+
|
|
182
|
+
# Fallback to regex parser (LX-06 legacy)
|
|
126
183
|
from invar.core.ts_sig_parser import (
|
|
127
184
|
extract_ts_signatures,
|
|
128
185
|
format_ts_signatures_json,
|
|
@@ -172,7 +229,14 @@ def _run_map_python(path: Path, top_n: int, json_output: bool) -> Result[None, s
|
|
|
172
229
|
continue
|
|
173
230
|
|
|
174
231
|
if not file_infos:
|
|
175
|
-
return Failure(
|
|
232
|
+
return Failure(
|
|
233
|
+
"No Python symbols found.\n\n"
|
|
234
|
+
"Available tools:\n"
|
|
235
|
+
"- invar sig <file.py> — Extract signatures\n"
|
|
236
|
+
"- invar refs <file.py>::Symbol — Find references\n"
|
|
237
|
+
"- invar_doc_* — Document navigation\n"
|
|
238
|
+
"- invar_guard — Static verification"
|
|
239
|
+
)
|
|
176
240
|
|
|
177
241
|
# Build perception map
|
|
178
242
|
perception_map = build_perception_map(file_infos, sources, str(path.absolute()))
|
|
@@ -190,10 +254,47 @@ def _run_map_python(path: Path, top_n: int, json_output: bool) -> Result[None, s
|
|
|
190
254
|
|
|
191
255
|
# @shell_complexity: TypeScript map with file discovery and symbol extraction
|
|
192
256
|
def _run_map_typescript(path: Path, top_n: int, json_output: bool) -> Result[None, str]:
|
|
193
|
-
"""Run map for TypeScript projects
|
|
257
|
+
"""Run map for TypeScript projects.
|
|
194
258
|
|
|
195
|
-
|
|
259
|
+
DX-78: Uses TS Compiler API when available, falls back to regex parser.
|
|
196
260
|
"""
|
|
261
|
+
from invar.shell.ts_compiler import is_typescript_available, run_map_typescript
|
|
262
|
+
|
|
263
|
+
# Try TS Compiler API first (DX-78)
|
|
264
|
+
if is_typescript_available():
|
|
265
|
+
map_result = run_map_typescript(path, top_n)
|
|
266
|
+
if isinstance(map_result, Success):
|
|
267
|
+
data = map_result.unwrap()
|
|
268
|
+
|
|
269
|
+
if not data.get("symbols"):
|
|
270
|
+
return Failure(
|
|
271
|
+
"No TypeScript symbols found.\n\n"
|
|
272
|
+
"Available tools:\n"
|
|
273
|
+
"- invar sig <file.ts> — Extract signatures\n"
|
|
274
|
+
"- invar refs <file.ts>::Symbol — Find references\n"
|
|
275
|
+
"- invar_doc_* — Document navigation\n"
|
|
276
|
+
"- invar_guard — Static verification"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Output using TS Compiler API format
|
|
280
|
+
if json_output:
|
|
281
|
+
output = {
|
|
282
|
+
"language": "typescript",
|
|
283
|
+
"total_symbols": data.get("total", len(data["symbols"])),
|
|
284
|
+
"symbols": data["symbols"],
|
|
285
|
+
}
|
|
286
|
+
console.print(json.dumps(output, indent=2))
|
|
287
|
+
else:
|
|
288
|
+
console.print("[bold]TypeScript Symbol Map[/bold]")
|
|
289
|
+
console.print(f"Total symbols: {data.get('total', len(data['symbols']))}\n")
|
|
290
|
+
for sym in data["symbols"]:
|
|
291
|
+
console.print(f"[{sym['kind']}] {sym['name']}")
|
|
292
|
+
console.print(f" {sym['file']}:{sym['line']}")
|
|
293
|
+
console.print()
|
|
294
|
+
|
|
295
|
+
return Success(None)
|
|
296
|
+
|
|
297
|
+
# Fallback to regex parser (LX-06 legacy)
|
|
197
298
|
from invar.core.ts_sig_parser import TSSymbol, extract_ts_signatures
|
|
198
299
|
from invar.shell.fs import discover_typescript_files
|
|
199
300
|
|
|
@@ -213,7 +314,14 @@ def _run_map_typescript(path: Path, top_n: int, json_output: bool) -> Result[Non
|
|
|
213
314
|
continue
|
|
214
315
|
|
|
215
316
|
if not all_symbols:
|
|
216
|
-
return Failure(
|
|
317
|
+
return Failure(
|
|
318
|
+
"No TypeScript symbols found.\n\n"
|
|
319
|
+
"Available tools:\n"
|
|
320
|
+
"- invar sig <file.ts> — Extract signatures\n"
|
|
321
|
+
"- invar refs <file.ts>::Symbol — Find references\n"
|
|
322
|
+
"- invar_doc_* — Document navigation\n"
|
|
323
|
+
"- invar_guard — Static verification"
|
|
324
|
+
)
|
|
217
325
|
|
|
218
326
|
# Sort by kind priority (function/class first), then by name
|
|
219
327
|
kind_order = {"function": 0, "class": 1, "interface": 2, "type": 3, "const": 4, "method": 5}
|
|
@@ -249,3 +357,191 @@ def _run_map_typescript(path: Path, top_n: int, json_output: bool) -> Result[Non
|
|
|
249
357
|
console.print()
|
|
250
358
|
|
|
251
359
|
return Success(None)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# @shell_complexity: Reference finding with multi-language support and output formatting
|
|
363
|
+
def run_refs(target: str, json_output: bool) -> Result[None, str]:
|
|
364
|
+
"""Find all references to a symbol.
|
|
365
|
+
|
|
366
|
+
Target format: "path/to/file.py::symbol_name" or "path/to/file.ts::symbol_name"
|
|
367
|
+
DX-78: Supports both Python (via jedi) and TypeScript (via TS Compiler API).
|
|
368
|
+
"""
|
|
369
|
+
# Parse target
|
|
370
|
+
if "::" not in target:
|
|
371
|
+
return Failure(
|
|
372
|
+
"Invalid target format.\n\n"
|
|
373
|
+
"Expected: path/to/file.py::symbol_name\n"
|
|
374
|
+
"Example: src/auth.py::validate_token"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
file_part, symbol_name = target.rsplit("::", 1)
|
|
378
|
+
file_path = Path(file_part)
|
|
379
|
+
|
|
380
|
+
if not file_path.exists():
|
|
381
|
+
return Failure(f"File not found: {file_path}")
|
|
382
|
+
|
|
383
|
+
suffix = file_path.suffix.lower()
|
|
384
|
+
|
|
385
|
+
# Route to language-specific implementation
|
|
386
|
+
if suffix in (".ts", ".tsx"):
|
|
387
|
+
return _run_refs_typescript(file_path, symbol_name, json_output)
|
|
388
|
+
elif suffix in (".py", ".pyi"):
|
|
389
|
+
return _run_refs_python(file_path, symbol_name, json_output)
|
|
390
|
+
else:
|
|
391
|
+
return Failure(
|
|
392
|
+
f"Unsupported file type: {suffix}\n\n"
|
|
393
|
+
"Supported: .py, .pyi, .ts, .tsx"
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
# @shell_complexity: Reference finding with output formatting and error handling
|
|
398
|
+
def _run_refs_python(
|
|
399
|
+
file_path: Path, symbol_name: str, json_output: bool
|
|
400
|
+
) -> Result[None, str]:
|
|
401
|
+
"""Find references in Python using jedi."""
|
|
402
|
+
from invar.shell.py_refs import find_all_references_to_symbol
|
|
403
|
+
|
|
404
|
+
# Find project root
|
|
405
|
+
project_root = file_path.parent
|
|
406
|
+
for parent in file_path.parents:
|
|
407
|
+
if (parent / "pyproject.toml").exists() or (parent / "setup.py").exists():
|
|
408
|
+
project_root = parent
|
|
409
|
+
break
|
|
410
|
+
|
|
411
|
+
refs = find_all_references_to_symbol(file_path, symbol_name, project_root)
|
|
412
|
+
|
|
413
|
+
if not refs:
|
|
414
|
+
return Failure(f"Symbol '{symbol_name}' not found in {file_path}")
|
|
415
|
+
|
|
416
|
+
# Output
|
|
417
|
+
if json_output:
|
|
418
|
+
output = {
|
|
419
|
+
"target": str(file_path) + "::" + symbol_name,
|
|
420
|
+
"total": len(refs),
|
|
421
|
+
"references": [
|
|
422
|
+
{
|
|
423
|
+
"file": str(ref.file.relative_to(project_root))
|
|
424
|
+
if ref.file.is_relative_to(project_root)
|
|
425
|
+
else str(ref.file),
|
|
426
|
+
"line": ref.line,
|
|
427
|
+
"column": ref.column,
|
|
428
|
+
"context": ref.context,
|
|
429
|
+
"is_definition": ref.is_definition,
|
|
430
|
+
}
|
|
431
|
+
for ref in refs
|
|
432
|
+
],
|
|
433
|
+
}
|
|
434
|
+
console.print(json.dumps(output, indent=2))
|
|
435
|
+
else:
|
|
436
|
+
console.print(f"[bold]References to {symbol_name}[/bold]")
|
|
437
|
+
console.print(f"Found {len(refs)} reference(s)\n")
|
|
438
|
+
|
|
439
|
+
for ref in refs:
|
|
440
|
+
rel_path = (
|
|
441
|
+
ref.file.relative_to(project_root)
|
|
442
|
+
if ref.file.is_relative_to(project_root)
|
|
443
|
+
else ref.file
|
|
444
|
+
)
|
|
445
|
+
marker = " [definition]" if ref.is_definition else ""
|
|
446
|
+
console.print(f"{rel_path}:{ref.line}{marker}")
|
|
447
|
+
if ref.context:
|
|
448
|
+
console.print(f" {ref.context}")
|
|
449
|
+
console.print()
|
|
450
|
+
|
|
451
|
+
return Success(None)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
@dataclass
|
|
455
|
+
class _SymbolPosition:
|
|
456
|
+
"""Temporary holder for symbol position during refs lookup."""
|
|
457
|
+
line: int
|
|
458
|
+
column: int
|
|
459
|
+
name: str
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
# @shell_complexity: TypeScript refs with symbol lookup and output formatting
|
|
463
|
+
def _run_refs_typescript(
|
|
464
|
+
file_path: Path, symbol_name: str, json_output: bool
|
|
465
|
+
) -> Result[None, str]:
|
|
466
|
+
"""Find references in TypeScript using TS Compiler API."""
|
|
467
|
+
from invar.shell.ts_compiler import is_typescript_available, run_refs_typescript
|
|
468
|
+
|
|
469
|
+
if not is_typescript_available():
|
|
470
|
+
return Failure(
|
|
471
|
+
"TypeScript tools not available.\n\n"
|
|
472
|
+
"Requirements:\n"
|
|
473
|
+
"- Node.js installed\n"
|
|
474
|
+
"- tsconfig.json in project root"
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
# First, find the symbol's position using sig command
|
|
478
|
+
from invar.shell.ts_compiler import run_sig_typescript
|
|
479
|
+
|
|
480
|
+
sig_result = run_sig_typescript(file_path)
|
|
481
|
+
if isinstance(sig_result, Failure):
|
|
482
|
+
return sig_result
|
|
483
|
+
|
|
484
|
+
symbols = sig_result.unwrap()
|
|
485
|
+
symbol = next((s for s in symbols if s.name == symbol_name), None)
|
|
486
|
+
|
|
487
|
+
if symbol is None:
|
|
488
|
+
# Check class members
|
|
489
|
+
for s in symbols:
|
|
490
|
+
if s.members:
|
|
491
|
+
for member in s.members:
|
|
492
|
+
if member.get("name") == symbol_name:
|
|
493
|
+
# Extract column if available, default to 0
|
|
494
|
+
column = member.get("column", 0)
|
|
495
|
+
symbol = _SymbolPosition(
|
|
496
|
+
line=member["line"],
|
|
497
|
+
column=column,
|
|
498
|
+
name=symbol_name
|
|
499
|
+
)
|
|
500
|
+
break
|
|
501
|
+
if symbol:
|
|
502
|
+
break
|
|
503
|
+
|
|
504
|
+
if symbol is None:
|
|
505
|
+
return Failure(f"Symbol '{symbol_name}' not found in {file_path}")
|
|
506
|
+
|
|
507
|
+
# Find references using position
|
|
508
|
+
# Use symbol.column if available (from member dict), defaults to 0
|
|
509
|
+
column = getattr(symbol, "column", 0)
|
|
510
|
+
refs_result = run_refs_typescript(file_path, symbol.line, column)
|
|
511
|
+
if isinstance(refs_result, Failure):
|
|
512
|
+
return refs_result
|
|
513
|
+
|
|
514
|
+
refs = refs_result.unwrap()
|
|
515
|
+
|
|
516
|
+
if not refs:
|
|
517
|
+
return Failure(f"No references found for '{symbol_name}'")
|
|
518
|
+
|
|
519
|
+
# Output (refs already have relative paths from ts-query.js)
|
|
520
|
+
if json_output:
|
|
521
|
+
output = {
|
|
522
|
+
"target": str(file_path) + "::" + symbol_name,
|
|
523
|
+
"total": len(refs),
|
|
524
|
+
"references": [
|
|
525
|
+
{
|
|
526
|
+
"file": ref.file,
|
|
527
|
+
"line": ref.line,
|
|
528
|
+
"column": ref.column,
|
|
529
|
+
"context": ref.context,
|
|
530
|
+
"is_definition": ref.is_definition,
|
|
531
|
+
}
|
|
532
|
+
for ref in refs
|
|
533
|
+
],
|
|
534
|
+
}
|
|
535
|
+
console.print(json.dumps(output, indent=2))
|
|
536
|
+
else:
|
|
537
|
+
console.print(f"[bold]References to {symbol_name}[/bold]")
|
|
538
|
+
console.print(f"Found {len(refs)} reference(s)\n")
|
|
539
|
+
|
|
540
|
+
for ref in refs:
|
|
541
|
+
marker = " [definition]" if ref.is_definition else ""
|
|
542
|
+
console.print(f"{ref.file}:{ref.line}{marker}")
|
|
543
|
+
if ref.context:
|
|
544
|
+
console.print(f" {ref.context}")
|
|
545
|
+
console.print()
|
|
546
|
+
|
|
547
|
+
return Success(None)
|