Ivy LSP - Task Breakdown¤
Branch: feature/ivy-lsp
Design Doc: docs/plans/2026-02-20-ivy-lsp-design.md
Each task has a checkbox, estimated effort, dependencies, and acceptance criteria. Tasks are ordered by implementation phase and dependency chain.
Phase 1: Core Parser Integration + Document Symbols¤
Task 1.1: Project Scaffolding¤
- [ ] Create
ivy_lsp/package structure - Effort: 1 hour
- Dependencies: None
- Files to create:
ivy_lsp/init.py- Package init, version stringivy_lsp/main.py- Entry point:python -m ivy_lspstarts stdio LSP serverivy_lsp/server.py-IvyLanguageServer(LanguageServer)pygls server class with lifecycle handlersivy_lsp/parsing/init.pyivy_lsp/indexer/init.pyivy_lsp/features/init.pyivy_lsp/utils/init.py- Code style note: All
ivy_lsp/*.pyfiles must follow pre-commit Tier 1 rules defined in.pre-commit-config.yaml: Black formatting (--target-version py310), isort imports (--profile black), line length 88. Runpre-commit run --all-filesto verify. - Acceptance criteria:
python -m ivy_lspstarts a server that accepts LSP initialization over stdio- Server responds to
initializewith capabilities (document symbols, definition, references, completion, diagnostics) - Server logs startup to stderr
Task 1.2: IvySymbol Dataclasses¤
- [ ] Create symbol representation (
ivy_lsp/parsing/symbols.py) - Effort: 2 hours
- Dependencies: None
- Details:
IvySymboldataclass:name,kind(LSP SymbolKind),range(start_line, end_line, start_col, end_col),children: List[IvySymbol],detail(signature string),file_pathIvyScopedataclass:name,symbols: Dict[str, IvySymbol],parent: Optional[IvyScope],children: List[IvyScope]SymbolTableclass:add_symbol(),lookup(name),lookup_qualified(path),all_symbols(),symbols_in_file(path)IncludeGraphclass:add_edge(from_file, to_file),get_includes(file),get_included_by(file),get_transitive_includes(file)- Acceptance criteria:
- Symbol hierarchy can represent nested Ivy constructs (object inside object)
- SymbolTable supports qualified name lookup (e.g.,
frame.ack.range) - IncludeGraph supports transitive dependency queries
Task 1.3: Parser State Isolation¤
- [ ] Create
ivy_lsp/parsing/parser_session.py - Effort: 4 hours
- Dependencies: Task 1.2
- Details:
ParserSessioncontext manager that saves/restores allivy_parserglobals:ivy_parser.error_list(line 100)ivy_parser.stack(line 102)ivy_parser.special_attribute(line 236)ivy_parser.parent_object(line 237)ivy_parser.global_attribute(line 238)ivy_parser.common_attribute(line 239)iu.filenameiu.ivy_language_version(set to[1,7])ivy_ast.label_counterivy_ast.lf_counter
IvyParserWrapperclass:parse(source: str, filename: str) -> ParseResultParseResult:ast: Optional[Ivy],errors: List[ParseError],success: bool- Uses
ParserSessioncontext manager - Catches all exceptions (including
ErrorList,IvyError, unexpected exceptions) - Returns partial results when possible
- Import path handling: ensure
ivypackage is importable fromivy_lsp/ - Production note: Z3 imports now route through
ivy/z3_shim.py, a compatibility shim that wrapsz3/z3_api. The shim is read-only (no mutable state to save/restore), but the import path must be accessible. Ensureivy.z3_shimis importable when configuring theivypackage path. - Production note:
ivy_parser.pynow includes anUNPROVABLEreserved word and grammar rules (p_optunprovable,p_top_unprovable_property_labeledfmla, etc.). Thecheck_unprovablecontext var fromivy_actionsis imported at line 6. These add parser globals to be aware of but do not require state save/restore (they are grammar rules, not mutable globals). Line number references for globals above are verified accurate on production. - Acceptance criteria:
- Can parse a valid
.ivyfile and get back anIvyAST object - Global state is fully restored after parsing (even on exception)
- Two sequential parses don't interfere with each other
- Parse errors are captured, not raised
Task 1.4: AST-to-Symbol Converter¤
- [ ] Create
ivy_lsp/parsing/ast_to_symbols.py - Effort: 6 hours
- Dependencies: Task 1.2, Task 1.3
- Details:
ast_to_symbols(ivy_ast: Ivy, filename: str) -> List[IvySymbol]- Walk
Ivy.declslist, map eachDeclsubclass toIvySymbol:TypeDecl->SymbolKind.Class(detect enum types from args)ObjectDecl->SymbolKind.Module(recurse into nested decls)ModuleDecl->SymbolKind.Module(extract parameters)ActionDecl->SymbolKind.Function(extract params, return type from AST)RelationDecl->SymbolKind.FunctionFunctionDecl->SymbolKind.Function(extract return type)PropertyDecl->SymbolKind.Property(note: may havelf.unprovable=Trueattribute; reflect indetailstring, e.g.,"unprovable property name")AxiomDecl->SymbolKind.PropertyIsolateDecl->SymbolKind.NamespaceInstanceDecl(InstantiateDecl) ->SymbolKind.VariableAliasDecl->SymbolKind.VariableDestructorDecl->SymbolKind.FieldVariantDecl->SymbolKind.ClassImportDecl/ExportDeccl-> track but don't emit as symbolsIncludeDecl-> track for include graphBeforeDecl/AfterDecl/AroundDecl->SymbolKind.Function(mixin)DefinitionDecl->SymbolKind.FunctionStructDecl(within TypeDecl) -> fields as children
- Extract
detailstring for each symbol (e.g., action signature:(pkt:quic_packet) returns (ok:bool)) - Handle
linenofromiu.Locationobjects -> LSP Range - Reference:
ivy/ivy_ast.pyfor all Decl subclasses and theirdefines()methods - Acceptance criteria:
- Parse
quic_types.ivyand produce correct symbol hierarchy - Parse
quic_frame.ivyand produce correct nested object structure - All 13+ Decl types are handled
- Symbol positions (line/column) are accurate
Task 1.5: Lexer Fallback Scanner¤
- [ ] Create
ivy_lsp/parsing/fallback_scanner.py - Effort: 4 hours
- Dependencies: Task 1.2
- Details:
fallback_scan(source: str, filename: str) -> List[IvySymbol]- Use
ivy_lexer.lexer(withLexerVersion([1,7])) to tokenize the source - Walk token stream, detect declaration patterns:
TYPE PRESYMBOL -> TypeSymbol OBJECT PRESYMBOL [EQ] LCB -> ObjectSymbol (push scope) MODULE PRESYMBOL LPAREN -> ModuleSymbol (push scope) ACTION PRESYMBOL -> ActionSymbol RELATION PRESYMBOL -> RelationSymbol FUNCTION PRESYMBOL -> FunctionSymbol INCLUDE PRESYMBOL -> IncludeDirective ISOLATE PRESYMBOL -> IsolateSymbol EXPORT [ACTION] PRESYMBOL -> ExportDirective IMPORT [ACTION] PRESYMBOL -> ImportDirective PROPERTY/AXIOM/CONJECTURE -> PropertySymbol UNPROVABLE PROPERTY/INVARIANT -> PropertySymbol (with unprovable flag) BEFORE/AFTER/AROUND PRESYMBOL -> MixinSymbol VARIANT PRESYMBOL OF PRESYMBOL -> VariantSymbol INSTANTIATE PRESYMBOL COLON PRESYMBOL -> InstanceSymbol ALIAS PRESYMBOL -> AliasSymbol INTERPRET PRESYMBOL -> InterpretSymbol VAR/INDIV PRESYMBOL -> VariableSymbol DESTRUCTOR PRESYMBOL -> DestructorSymbol DEFINITION PRESYMBOL -> DefinitionSymbol SPECIFICATION LCB -> Block scope IMPLEMENTATION LCB -> Block scope - Track brace depth (
LCB/RCB) for scope nesting - Recognize
GLOBALLY(U+25A1) andEVENTUALLY(U+25C7) as Unicode temporal operator tokens (defined inivy_lexer.pyviat_GLOBALLY/t_EVENTUALLYfunctions) - Note: lexer uses
re.UNICODE | re.VERBOSEflags (line 251 ofivy_lexer.py) - Produce hierarchical symbols respecting object/module nesting
- Acceptance criteria:
- Can produce partial symbols from files with syntax errors
- Handles nested
object { object { ... } }correctly via brace depth - Produces same top-level symbols as AST converter for valid files (degraded detail)
Task 1.6: Document Symbols Feature¤
- [ ] Create
ivy_lsp/features/document_symbols.py - Effort: 2 hours
- Dependencies: Task 1.3, Task 1.4, Task 1.5
- Details:
- Register
textDocument/documentSymbolhandler on the server - On request: parse the document (primary: parser, fallback: scanner)
- Convert
List[IvySymbol]toList[DocumentSymbol](LSP protocol type) - Handle hierarchical symbols (children nested inside parent DocumentSymbol)
- Acceptance criteria:
- VS Code / Serena can display symbol outline for
.ivyfiles - Hierarchy reflects
object/modulenesting - Falls back gracefully on invalid files
Task 1.7: Workspace Symbols Feature¤
- [ ] Create
ivy_lsp/features/workspace_symbols.py - Effort: 2 hours
- Dependencies: Task 1.6
- Details:
- Register
workspace/symbolhandler - On request with query string: search all parsed symbols matching query
- Support substring matching and qualified names
- Return
List[WorkspaceSymbol]with file locations - Acceptance criteria:
- Search for
pkt_numreturns all matching symbols across workspace - Supports partial matching (e.g.,
pktmatchespkt_num)
Task 1.8: Position Utilities¤
- [ ] Create
ivy_lsp/utils/position_utils.py - Effort: 1 hour
- Dependencies: None
- Details:
ivy_location_to_range(loc: iu.Location, source: str) -> Range- Convert Ivy'siu.Locationto LSP Rangeoffset_to_position(offset: int, source: str) -> Position- Convert byte offset to LSP Positionword_at_position(lines: List[str], position: Position) -> str- Extract word at cursor- Handle Ivy's 1-based line numbers vs LSP's 0-based
- Acceptance criteria:
- Line number conversion is correct (Ivy 1-based -> LSP 0-based)
- Word extraction handles
.separated qualified names
Task 1.9: Phase 1 Testing¤
- [ ] Test parser integration against real Ivy files
- Effort: 4 hours
- Dependencies: All Phase 1 tasks
- Details:
- Unit test: Parse each Decl type against small
.ivysnippets - Integration test: Parse
quic_types.ivy,quic_frame.ivy,quic_packet.ivy - Fallback test: Introduce syntax errors in test files, verify fallback produces symbols
- Corpus test: Run parser against sample of
.ivyfiles fromprotocol-testing/quic/ - Server test: Start LSP server, send
documentSymbolrequest, verify response - Acceptance criteria:
- All tests pass
- Parser handles at least 95% of
quic_stack/files without crashes - Fallback produces reasonable symbols for files that fail parsing
Phase 2: Cross-File Index + Navigation¤
Task 2.1: Include Resolver¤
- [ ] Create
ivy_lsp/indexer/include_resolver.py - Effort: 3 hours
- Dependencies: Phase 1 complete
- Details:
IncludeResolverclass:init(workspace_root: str, ivy_include_path: Optional[str] = None)resolve(include_name: str, from_file: str) -> Optional[str]- Resolveinclude Xto absolute file path- Search order (from
ivy_compiler.pyimport_modulelogic): - Same directory as the including file
- Workspace root directory
- Standard library:
ivy/include/1.7/(relative to ivy package) - Handle
.ivyextension appending
find_all_ivy_files(workspace_root: str) -> List[str]- Glob for all.ivyfiles- Reference:
ivy/ivy_compiler.pylines ~2268-2311 (read_module,import_module) - Acceptance criteria:
include quic_typesinquic_frame.ivyresolves toquic_stack/quic_types.ivy- Standard library includes (e.g.,
include collections) resolve toivy/include/1.7/collections.ivy - Returns
Nonefor unresolvable includes
Task 2.2: File Cache¤
- [ ] Create
ivy_lsp/indexer/file_cache.py - Effort: 2 hours
- Dependencies: Task 1.3
- Details:
FileCacheclass:get(filepath: str) -> Optional[CachedFile]- Returns cached parse result if mtime unchangedput(filepath: str, result: ParseResult, symbols: List[IvySymbol])- Cache parse resultinvalidate(filepath: str)- Remove from cacheinvalidate_dependents(filepath: str, include_graph: IncludeGraph)- Invalidate all files that include this one
CachedFiledataclass:filepath,mtime,parse_result,symbols,includes- LRU eviction when cache exceeds configurable size (default: 500 files)
- Acceptance criteria:
- Second parse of same (unmodified) file is served from cache
- File modification triggers cache invalidation
- Dependent files are invalidated when a dependency changes
Task 2.3: Workspace Indexer¤
- [ ] Create
ivy_lsp/indexer/workspace_indexer.py - Effort: 6 hours
- Dependencies: Task 2.1, Task 2.2, Task 1.2
- Details:
WorkspaceIndexerclass:init(workspace_root: str, parser: IvyParserWrapper, resolver: IncludeResolver)index_workspace() -> None- Scan all.ivyfiles, parse each, build symbol table + include graphreindex_file(filepath: str) -> None- Re-parse a single file, update symbol tableget_symbols(filepath: str) -> List[IvySymbol]- Get symbols for a filelookup_symbol(name: str) -> List[SymbolLocation]- Find all definitions of a symbollookup_references(name: str, definition_file: str) -> List[SymbolLocation]- Find all referencesget_symbols_in_scope(filepath: str) -> List[IvySymbol]- All symbols visible in a file (own + transitive includes)
- Background indexing with progress reporting (
window/workDoneProgress) - Debounced re-indexing on file changes (200ms debounce)
- Acceptance criteria:
- Full workspace index of
protocol-testing/quic/completes in <30 seconds - Symbol lookup across include boundaries works correctly
- File changes trigger targeted re-indexing (not full workspace)
Task 2.4: Go-to-Definition Feature¤
- [ ] Create
ivy_lsp/features/definition.py - Effort: 3 hours
- Dependencies: Task 2.3
- Details:
- Register
textDocument/definitionhandler - On request:
- Get word at cursor position
- Handle qualified names (e.g.,
frame.ack.largest_acked-> look uplargest_ackedinframe.ackscope) - Look up in workspace indexer
- Return
Location(file URI + range)
- Handle multiple definitions (return
List[Location]) - Acceptance criteria:
- Go-to-definition on
pkt_numinquic_frame.ivyjumps toquic_types.ivy - Go-to-definition on
quic_packet_type.initialjumps to the enum definition - Works across
includeboundaries
Task 2.5: Find References Feature¤
- [ ] Create
ivy_lsp/features/references.py - Effort: 3 hours
- Dependencies: Task 2.3
- Details:
- Register
textDocument/referenceshandler - On request:
- Get word at cursor position
- Search all files in workspace for token matching the symbol name
- Filter to files that are in the include graph (can actually see the symbol)
- Return
List[Location]
- For cross-file references: use lexer tokenization to find exact token matches (not substring grep)
- Acceptance criteria:
- Find references on
type pkt_numshows all files usingpkt_num - Does not show false positives from files that don't include the definition
Task 2.6: Phase 2 Testing¤
- [ ] Test cross-file navigation against QUIC protocol models
- Effort: 4 hours
- Dependencies: All Phase 2 tasks
- Details:
- Test include resolution for all files in
quic_stack/ - Test go-to-definition across include boundaries
- Test find references across the workspace
- Test workspace indexer performance on full
protocol-testing/(667 files) - Test cache invalidation on file changes
- Acceptance criteria:
- All
includedirectives inquic_stack/resolve correctly - Go-to-definition works for at least 90% of symbol references
- Workspace indexing completes in reasonable time (<30s)
Phase 3: Completion + Diagnostics + Hover¤
Task 3.1: Completion Feature¤
- [ ] Create
ivy_lsp/features/completion.py - Effort: 6 hours
- Dependencies: Phase 2 complete
- Details:
- Register
textDocument/completionhandler - Completion contexts:
- After
.: Scope-based completion (e.g.,frame.-> fields/actions offrame) - Look up the object/module before the dot in symbol table
- Return its children as completion items
- After
include: List available.ivyfilenames in workspace + standard library - After keywords (
type,action,object, etc.): Suggest common patterns - General: All symbols in scope (from current file + transitive includes)
- Keywords from
ivy_lexer.pyall_reserveddict (includesunprovable,globally,eventuallyon production) - Add
unprovableto keyword completions for property-related contexts (e.g., after typing in a property/invariant block) - All defined symbols visible through include chain
- After
CompletionItemwith:label: symbol namekind: mapped from IvySymbol kinddetail: type signature or definition summarydocumentation: extracted from nearby comments (if any)insertText: smart insertion (e.g., action with parameters -> snippet)
- Acceptance criteria:
frame.produces field completions for theframeobjectincludeproduces available filenames- General completion shows all visible symbols + keywords
- No duplicate completions
Task 3.2: Diagnostics Feature¤
- [ ] Create
ivy_lsp/features/diagnostics.py - Effort: 4 hours
- Dependencies: Task 1.3
- Details:
- Parser diagnostics (always available):
- Convert
ivy_parserParseError/ErrorListto LSPDiagnostic - Map
iu.Locationto LSP Range - Severity:
Errorfor parse errors,Warningfor redefinitions
- Convert
- Structural diagnostics (from fallback scanner):
- Missing
#lang ivy1.7directive -> Warning - Unmatched braces -> Error
- Unresolved
include(file not found) -> Error
- Missing
- Deep diagnostics (async, when
ivycavailable, on save):- Shell out to
ivy_checkor.ivy ivyc target=test.ivy - Parse stderr for error messages:
: line : - Check if
ivyc/ivy_checkis on PATH, skip silently if not - Run in background asyncio task, publish results when complete
- Shell out to
- Publish diagnostics on
textDocument/didOpen,textDocument/didChange(debounced),textDocument/didSave - Acceptance criteria:
- Parse errors show as diagnostics with correct line numbers
- Missing
#langdirective produces a warning - Unresolved includes show as errors
ivycdiagnostics appear after save (when available)- No diagnostics when
ivycis not available (graceful degradation)
Task 3.3: Hover Feature¤
- [ ] Create
ivy_lsp/features/hover.py - Effort: 3 hours
- Dependencies: Phase 2 complete
- Details:
- Register
textDocument/hoverhandler - On hover:
- Get word at cursor
- Look up symbol in index
- Return formatted hover content:
- For actions:
action name(param: type, ...) returns (ret: type) - For types:
type nameortype name = {enum1, enum2, ...}ortype name = struct { field: type, ... } - For relations:
relation name(param: type, ...) - For functions:
function name(param: type, ...) : return_type - For instances:
instance name : module_name(params) - For aliases:
alias name = target_type - Format as markdown code block with
ivylanguage tag
- Acceptance criteria:
- Hover on action name shows full signature
- Hover on type name shows type definition
- Hover on instance shows module being instantiated
Task 3.4: Phase 3 Testing¤
- [ ] Test completion, diagnostics, and hover against real files
- Effort: 3 hours
- Dependencies: All Phase 3 tasks
- Details:
- Test completion in various contexts (after
., afterinclude, general) - Test diagnostics with valid files (no diagnostics), invalid files (parser errors), unresolved includes
- Test hover on different symbol types
- Test
ivycasync diagnostics (requires Docker or local ivyc installation) - Acceptance criteria:
- All completion contexts produce relevant suggestions
- Diagnostics have correct positions and messages
- Hover shows useful type information
Phase 4: Serena Integration + Polish¤
Task 4.1: Fork Serena and Add IVY Language¤
- [ ] Add IVY language support to Serena
- Effort: 4 hours
- Dependencies: Phase 3 complete
- Details:
- Fork
https://github.com/oraios/serena - Add
IVY = "ivy"toLanguageenum insrc/solidlsp/ls_config.py - Implement
get_source_fn_matcher():FilenameMatcher("*.ivy") - Implement
get_ls_class(): returnIvyLanguageServerclass - Implement
is_experimental(): returnTrueinitially - Create
src/solidlsp/language_servers/ivy_language_server.py:IvyLanguageServer(SolidLanguageServer)subclass_start_server(): Startpython -m ivy_lspover stdio_create_dependency_provider(): Check forivy_lspon PATH or in projectis_ignored_dirname(): Skipbuild/,submodules/,pycache/
- Test with
uv run serenapointing at panther_ivy workspace - Acceptance criteria:
- Serena recognizes
.ivyfiles and starts the Ivy LSP get_symbols_overviewworks on.ivyfilesfind_symbolworks for Ivy symbolsfind_referencing_symbolsworks across Ivy files
Task 4.2: Update Serena Project Configuration¤
- [ ] Update
.serena/project.ymlto include ivy - Effort: 30 minutes
- Dependencies: Task 4.1
- Details:
- Add
ivyto thelanguageslist in.serena/project.yml - Test that Serena picks up
.ivyfiles - Acceptance criteria:
- Serena processes
.ivyfiles in the PANTHER workspace
Task 4.3: Package ivy_lsp¤
- [ ] Add ivy_lsp to setup.py
- Effort: 1 hour
- Dependencies: Phase 3 complete
- Details:
- Package is now
panther_ms_ivyv1.8.26 (renamed fromms_ivyv1.8.25 during VMT-RF integration). Updatesetup.py(line 28:name="panther_ms_ivy", line 30:version="1.8.26"). - Add
"lsp": ["pygls", "lsprotocol"]toextras_requireinsetup.py - Add entry point:
ivy_lsp=ivy_lsp.main:maininconsole_scripts - Ensure
ivy_lspcan locate theivypackage (parent directory or installed) - Test installation:
pip install -e ".[lsp]"and verifypython -m ivy_lspstarts - Acceptance criteria:
pip install -e ".[lsp]"installs ivy_lsp with all dependenciespython -m ivy_lspstarts the language server
Task 4.4: Corpus Testing¤
- [ ] Test against full protocol-testing corpus
- Effort: 4 hours
- Dependencies: Phase 3 complete
- Details:
- Parse all
.ivyfiles inprotocol-testing/(currently 667 files) anddoc/examples/cav2024/(7 VMT-RF files added post-PR #9). Total corpus across the repo (excluding submodules): ~1430.ivyfiles. - Measure: parse success rate, fallback rate, average parse time
- Fix any parser crashes or edge cases discovered
- Test include resolution across all files
- Test workspace indexing performance
- Generate test report
- Acceptance criteria:
- 95%+ files parse successfully with full parser
- Remaining files produce fallback symbols
- No crashes on any file
- Workspace indexing completes in <60 seconds for full corpus
Task 4.5: End-to-End Serena Workflow Test¤
- [ ] Test complete Serena MCP workflow
- Effort: 2 hours
- Dependencies: Task 4.1, Task 4.2
- Details:
- Start Serena MCP with Ivy LSP configured
- Test each Serena tool with
.ivyfiles:get_symbols_overview("quic_types.ivy")-> symbol listfind_symbol("pkt_num")-> definition with bodyfind_referencing_symbols("pkt_num", "quic_types.ivy")-> referencesreplace_symbol_body("some_action", ...)-> edit worksinsert_after_symbol("some_type", ...)-> insertion works
- Verify symbol editing works (replace, insert before/after)
- Acceptance criteria:
- All Serena tools work correctly with
.ivyfiles - Symbol editing produces valid Ivy code
- LLM can use Serena to navigate and modify Ivy specifications
Task 4.6: Documentation¤
- [ ] Write usage documentation
- Effort: 2 hours
- Dependencies: All Phase 4 tasks
- Details:
- Update
CLAUDE.mdwith Ivy LSP information - Write
ivy_lsp/README.md:- Installation instructions
- Serena configuration
- Supported LSP features
- Known limitations
- Troubleshooting
- Add to panther_ivy's
README.md - Acceptance criteria:
- A new developer can set up and use the Ivy LSP from the documentation
Phase 5: VSCode Extension¤
Branch:
feature/ivy-lsp-integrationDesign Doc:docs/plans/2026-02-25-vscode-extension-design.mdThis phase adds a thin VSCode client extension that connects to the Python LSP server (
ivy_lsp/) over stdio, plus TextMate grammar for syntax highlighting, language configuration, and snippets.Location:
panther_ivy/vscode-ivy/
Architecture¤
VSCode <--> extension.ts (TypeScript thin client)
|
| stdio (JSON-RPC)
v
python -m ivy_lsp (Python, pygls)
|
v
ivy_parser / ivy_lexer (PLY-based)
Directory Structure¤
panther_ivy/vscode-ivy/
package.json # Extension manifest
tsconfig.json # TypeScript config
language-configuration.json # Brackets, comments, indentation
.vscodeignore # Exclude source from VSIX
.eslintrc.json # Linting
README.md # Extension readme
syntaxes/
ivy.tmLanguage.json # TextMate grammar (syntax highlighting)
snippets/
ivy.json # Code snippets
src/
extension.ts # Activation, LSP client lifecycle
pythonFinder.ts # Python + ivy_lsp discovery logic
test/
runTest.ts # Test runner setup
suite/
index.ts # Mocha test suite loader
grammar.test.ts # Grammar tokenization tests
extension.test.ts # Extension activation tests
Task 5.1: Extension Scaffolding¤
- [ ] Create directory structure,
package.json,tsconfig.json,.vscodeignore - Effort: 2 hours
- Dependencies: None (can start immediately, parallel with Phases 1-4)
- Files:
package.json— Extension manifest with:name:ivy-language,displayName:Ivy Languageengines.vscode:^1.75.0activationEvents:["onLanguage:ivy"]main:./out/extension.jscontributes.languages:id: ivy, extensions.ivy, config./language-configuration.jsoncontributes.grammars:scopeName: source.ivy, path./syntaxes/ivy.tmLanguage.jsoncontributes.snippets: path./snippets/ivy.jsoncontributes.configuration: settings forivy.pythonPath,ivy.lsp.enabled,ivy.lsp.args,ivy.lsp.trace.serverdependencies:vscode-languageclient ^9.0.1devDependencies:@types/vscode,@types/mocha,@types/node,typescript,@vscode/test-electron,@vscode/vsce,eslint,@typescript-eslint/*,mocha,vscode-tmgrammar-testscripts:compile,watch,lint,pretest,test,vscode:prepublish
tsconfig.json—module: Node16,target: ES2022,outDir: out,rootDir: src,strict: true.vscodeignore— Excludesrc/,node_modules/,.ts,.map,.vscode-test/.eslintrc.json— TypeScript ESLint with@typescript-eslint/recommended- Acceptance criteria:
npm install && npm run compilesucceedsvsce packageproduces.vsixfile- Directory structure matches the plan
Task 5.2: TextMate Grammar¤
- [ ] Create
syntaxes/ivy.tmLanguage.json— complete Ivy syntax highlighting - Effort: 6 hours
- Dependencies: None (parallel with 5.1)
- Key source:
ivy/ivy_lexer.pylines 51-173 for definitive keyword list (80+ keywords) - Details:
- Root scope:
source.ivy - Pattern priority order (higher first):
#lang ivy1.7version pragma ->keyword.control.directive.ivy(NOT a comment)#line comments ->comment.line.number-sign.ivy- Double-quoted strings ->
string.quoted.double.ivy <<<...>>>native quotes ->string.unquoted.native.ivy(multiline)- Numeric literals: decimal
[0-9]+, hex0x[0-9a-fA-F]+->constant.numeric.ivy - Declaration keywords (30+):
action,object,module,type,isolate,struct,relation,function,individual,axiom,conjecture,schema,instantiate,instance,derived,concept,init,method,field,state,macro,interpret,mixin,execute,destructor,constructor,definition,alias,var,attribute,variant,class,scenario,proof,tactic->keyword.declaration.ivy - Control keywords:
if,else,while,for,match,call,local,let,entry,returns,in,of->keyword.control.ivy - Specification keywords:
property,invariant,axiom,conjecture,assume,assert,ensures,requires,modifies,ensure,require,theorem->keyword.other.specification.ivy - Quantifiers/temporal:
forall,exists,globally,eventually,temporal,decreases->keyword.operator.quantifier.ivy - Scope/module keywords:
export,import,include,using,delegate,with,before,after,around,extract,process,specification,implementation,private,common,global,parameter,trusted,ghost,named,explicit,finite,unprovable->keyword.other.ivy - Boolean/null constants:
true,false,null,this,old,fresh->constant.language.ivy - Other reserved:
set,update,params,from,some,maximizing,minimizing,implement,progress,rely,mixord,apply,showgoals,defergoal,spoil,thunk,isa,autoinstance,unfold,forget,debug,subclass,whenfirst,whenlast,whenprev,whennext,trigger->keyword.other.ivy - Operators:
:=(assign),->(arrow),<->(iff),&(and),|(or),~(tilda),~=(tildaeq),..(range),*>(pto) ->keyword.operator.ivy - Uppercase identifiers
[A-Z][_a-zA-Z0-9]*->variable.other.ivy - Label syntax
[name]->entity.name.tag.ivy - Type annotations in
name:typepatterns ->entity.name.type.ivyfor the type part - Unicode temporal operators:
\u25A1(globally box),\u25C7(eventually diamond) ->keyword.operator.temporal.ivy
- Nested scopes:
object name = { ... }— begin/end with{ include: "$self" }module name = { ... }— begin/end with{ include: "$self" }struct { ... }— begin/end with{ include: "$self" }
- Word boundary: All keyword patterns use
\bboundaries to avoid matching inside identifiers (e.g.,property_nameshould NOT highlightproperty) - Acceptance criteria:
- Real files (
quic_types.ivy,quic_frame.ivy) highlight correctly #lang ivy1.7is a directive, not a comment- Keywords inside identifiers (e.g.,
property_name) are NOT highlighted <<<...>>>native quotes highlight as strings- Uppercase variables (
X,Y,Z) highlight differently from lowercase identifiers - Labels
[name]highlight as tags - Comments after
#are correctly scoped (except#langwhich is a directive)
Task 5.3: Language Configuration¤
- [ ] Create
language-configuration.json - Effort: 1 hour
- Dependencies: None (parallel)
- Details:
- Line comment:
#(prefix#) - No block comments (Ivy has none)
- Bracket pairs:
(),{},[] - Auto-closing pairs:
(),{},[],"",<<</>>> - Surrounding pairs:
(),{},[],"",<<</>>> wordPattern:[a-zA-Z_][a-zA-Z0-9_](.[a-zA-Z_][a-zA-Z0-9_])*— includes.for qualified names likeframe.ack.range- Folding markers:
{/}and#region/#endregion - Indent rules:
- Increase: lines ending with
= {,{, or( - Decrease: lines starting with
}or)
- Increase: lines ending with
onEnterRules: after= {$insert indented newline- Acceptance criteria:
#toggles line comments{auto-closes with}- Enter after
= {indents the next line - Double-click selects full qualified name (e.g.,
frame.ack.range) - Code folding works on
{/}blocks
Task 5.4: Snippets¤
- [ ] Create
snippets/ivy.json— common Ivy patterns - Effort: 1 hour
- Dependencies: None (parallel)
- Details: Snippets with tab stops for:
#langpragma:#lang ivy1.7module:module $1 = { ... }object:object $1 = { ... }action:action $1($2) returns ($3) = { ... }type:type $1(simple) andtype $1 = {$2}(enum)struct:type $1 = struct { $2:$3 }isolate:isolate $1 = $2 with $3property:property $1 = $2invariant:invariant [spec] $1after init:after init { ... }require/ensure: guardsforall/exists: quantifiersinclude:include $1instance:instance $1 : $2before/afteraction mixins- Acceptance criteria:
- Typing prefix + Tab inserts correct template with tab stops
- All snippets produce valid Ivy syntax
- Tab stops navigate logically through the template
Task 5.5: Extension Client (TypeScript)¤
- [ ] Create
src/extension.tsandsrc/pythonFinder.ts - Effort: 4 hours
- Dependencies: Task 5.1 (needs
package.json), Phase 1 complete (LSP server must be startable) - Details:
src/pythonFinder.ts:findPython()function with 5-step discovery:- Check
ivy.pythonPathsetting (if non-empty) - Check workspace
.venv/bin/python(or.venv/Scripts/python.exeon Windows) - Try
python3on PATH - Try
pythonon PATH - Return
undefinedif none found checkIvyLsp(pythonPath: string): Promise— runspython -c "import ivy_lsp; print(ivy_lsp.version)"and returns version orundefined- Cache discovered Python path for session
src/extension.ts:activate(context: ExtensionContext):- If
ivy.lsp.enabledis false, return early (syntax-only mode) - Call
findPython()to discover Python - If Python not found: show warning, degrade to syntax-only
- Call
checkIvyLsp()to verifyivy_lspis importable - If
ivy_lspnot found: show error with install instructions (pip install -e ".[lsp]") - Create
LanguageClientwith:serverOptions:{ command: pythonPath, args: ["-m", "ivy_lsp", ...extraArgs] }clientOptions:{ documentSelector: [{ scheme: "file", language: "ivy" }] }- Trace level from
ivy.lsp.trace.serversetting
- Start the client
- Create status bar item:
"Ivy LSP: Running"/"Ivy LSP: Error"/"Ivy LSP: Syntax Only" deactivate(): Stop client, dispose status bar item- Config change listener: restart server when
ivy.pythonPathorivy.lsp.argschanges - Error handling: auto-restart built into
vscode-languageclient(3 restarts / 5 min)
- Acceptance criteria:
- Opening
.ivyfile starts LSP server - Status bar shows "Ivy LSP: Running"
- Changing
ivy.pythonPathrestarts the server - Clear error messages when Python /
ivy_lspnot found - Extension still provides syntax highlighting when LSP is unavailable
Task 5.6: Extension Testing¤
- [ ] Create test suite for grammar tokenization and extension activation
- Effort: 3 hours
- Dependencies: Tasks 5.2, 5.5
- Details:
src/test/runTest.ts: Test runner using@vscode/test-electronto launch VSCode instancesrc/test/suite/index.ts: Mocha test suite loadersrc/test/suite/grammar.test.ts: Grammar tokenization tests:- Test
#lang ivy1.7->keyword.control.directive.ivy - Test
# comment->comment.line.number-sign.ivy - Test
action foo(x:t)->keyword.declaration.ivy+source.ivy - Test
object bar = { }->keyword.declaration.ivy - Test
<<->>> string.unquoted.native.ivy - Test
Xuppercase ->variable.other.ivy - Test
[label]->entity.name.tag.ivy - Test
true,false->constant.language.ivy - Test
forall,exists->keyword.operator.quantifier.ivy - Test
property,invariant->keyword.other.specification.ivy - Test keyword inside identifier NOT highlighted (e.g.,
property_namestayssource.ivy)
- Test
src/test/suite/extension.test.ts: Extension activation tests:- Test extension activates on
.ivyfile - Test configuration reading
- Test Python finder (mocked)
- Test extension activates on
- Acceptance criteria:
npm testpasses- Covers 10+ Ivy syntax patterns
- Tests are reliable (no flaky tests)
Task 5.7: Integration with setup.py¤
- [ ] Update
setup.pyto includevscode-ivyas package data - Effort: 30 min
- Dependencies: Task 5.1
- Details:
- Add
"lsp": ["pygls>=1.0", "lsprotocol"]toextras_requireinsetup.py - Add entry point:
"ivy_lsp=ivy_lsp.main:main"toconsole_scripts - Add
"ivy_lsp"to thepackageslist (or adjustfind_packages()to include it) - Ensure
vscode-ivy/is excluded from the Python package (it's a separate Node package) - Acceptance criteria:
pip install -e ".[lsp]"installspyglsandlsprotocolpython -m ivy_lspstarts the language serverivy_lspconsole script also starts the server
Task 5.8: End-to-End Testing¤
- [ ] Test complete workflow: extension -> LSP -> real Ivy files
- Effort: 3 hours
- Dependencies: All Phase 5 tasks + LSP Phases 1-3
- Details:
- Open
quic_types.ivy,quic_frame.ivyin VSCode Development Host - Verify syntax highlighting for all element types:
#lang ivy1.7is a directive (not grayed as comment)type,object,actionare keyword-colored# commentsare comment-colored<<<...>>>native quotes are string-colored- Uppercase variables are distinctly colored
- Verify document outline (Cmd+Shift+O) shows symbol hierarchy
- Verify workspace symbols (Cmd+T) finds symbols across files
- Test with broken
.ivyfile (fallback symbols should still appear) - Test server crash recovery (kill Python process, verify auto-restart)
- Test syntax-only mode (disable LSP, verify highlighting still works)
- Acceptance criteria:
- All syntax elements highlight correctly
- Document symbols match the file hierarchy
- No crashes on the QUIC corpus
- Server recovers from crashes within 5 seconds
Task 5.9: Documentation¤
- [ ] Write extension README and installation guide
- Effort: 1.5 hours
- Dependencies: All Phase 5 tasks
- Details:
vscode-ivy/README.mdwith:- Feature overview (syntax highlighting, LSP features, snippets)
- Installation methods: VSIX file, from source
- Configuration reference (all
ivy.*settings) - Prerequisites: Python 3.10+,
ivy_lsppackage - Troubleshooting: Python not found,
ivy_lspnot installed, server crashes - Screenshots (placeholder paths for future)
- Development guide: building, testing, packaging
- Update
panther_ivy/CLAUDE.mdwith VSCode extension information - Acceptance criteria:
- New developer can install and use the extension from README alone
- All settings are documented
- Troubleshooting covers common failure modes
Phase 5 Parallelism¤
5.1 (scaffold) -------> 5.5 (client TS) -------> 5.8 (E2E)
5.2 (grammar) -------> 5.6 (testing) ----------> 5.9 (docs)
5.3 (lang config) --+
5.4 (snippets) -----+
5.7 (setup.py) -----+
Tasks 5.1, 5.2, 5.3, 5.4 can ALL proceed in parallel (no dependencies). Phase 5 has NO hard dependency on Phases 2-4 — extension works with Phase 1 alone (syntax + document symbols).
Summary¤
| Phase | Tasks | Estimated Effort |
|---|---|---|
| Phase 1: Core Parser + Symbols | 9 tasks | ~26 hours |
| Phase 2: Cross-File Navigation | 6 tasks | ~21 hours |
| Phase 3: Completion + Diagnostics | 4 tasks | ~16 hours |
| Phase 4: Serena Integration | 6 tasks | ~13.5 hours |
| Phase 5: VSCode Extension | 9 tasks | ~22 hours |
| Total | 34 tasks | ~98.5 hours |
Critical Path¤
1.1 -> 1.3 -> 1.4 -> 1.6 -> 2.3 -> 2.4 -> 3.1 -> 4.1 -> 4.5
| |
v v
1.5 2.5
5.1 -> 5.5 -> 5.8
5.2 -> 5.6 -> 5.9
Parallelizable Tasks¤
- Task 1.2 (symbols) and Task 1.8 (position utils) can be done in parallel
- Task 1.4 (AST converter) and Task 1.5 (fallback scanner) can be done in parallel
- Task 2.4 (definition) and Task 2.5 (references) can be done in parallel
- Task 3.1 (completion), Task 3.2 (diagnostics), and Task 3.3 (hover) can be done in parallel
- Task 4.1 (Serena fork) and Task 4.3 (packaging) can be done in parallel
- Tasks 5.1, 5.2, 5.3, 5.4 can ALL proceed in parallel
- Task 5.5 and Task 5.7 can proceed in parallel (both depend on 5.1)
Critical Files for Phase 5¤
| File | Purpose |
|---|---|
ivy/ivy_lexer.py (lines 51-173) |
Definitive keyword list for TextMate grammar |
ivy_lsp/server.py |
LSP server class the extension spawns |
ivy_lsp/main.py |
Entry point python -m ivy_lsp (interface contract) |
ivy_lsp/init.py |
Version string (0.1.0) checked by Python finder |
setup.py |
Package integration (Task 5.7) |