Skip to content

Commit 8e42c09

Browse files
domenkozarclaude
andcommitted
add create-language agent skill for language module scaffolding
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 84c3d0f commit 8e42c09

1 file changed

Lines changed: 200 additions & 0 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
---
2+
name: create-language
3+
description: This skill should be used when the user asks to "create a language module", "add a new language", "write a language module for", "implement language support for", or wants to add a new language to src/modules/languages/. Provides the patterns and conventions for devenv language modules.
4+
argument-hint: [language-name]
5+
---
6+
7+
# Create a devenv Language Module
8+
9+
This skill guides the creation of new language modules under `src/modules/languages/`.
10+
11+
## Process
12+
13+
1. Research the language: package name in nixpkgs, LSP server package, common development tools, environment variables, version overlay availability
14+
2. Read existing modules in `src/modules/languages/` for reference (e.g., `nim.nix` for simple, `go.nix` for medium, `rust.nix` for complex)
15+
3. Create `src/modules/languages/<name>.nix` following the patterns below (auto-discovered)
16+
4. Add a test under `tests/`
17+
18+
## Module Structure
19+
20+
Every language module follows this skeleton:
21+
22+
```nix
23+
{ pkgs, config, lib, ... }:
24+
25+
let
26+
cfg = config.languages.<name>;
27+
in
28+
{
29+
options.languages.<name> = {
30+
enable = lib.mkEnableOption "tools for <Name> development";
31+
32+
package = lib.mkOption {
33+
type = lib.types.package;
34+
default = pkgs.<name>;
35+
defaultText = lib.literalExpression "pkgs.<name>";
36+
description = "The <Name> package to use.";
37+
};
38+
39+
# Optional: version pinning via overlay (see Version Pinning below)
40+
41+
lsp = {
42+
enable = lib.mkEnableOption "<Name> Language Server" // { default = true; };
43+
package = lib.mkOption {
44+
type = lib.types.package;
45+
default = pkgs.<lsp-package>;
46+
defaultText = lib.literalExpression "pkgs.<lsp-package>";
47+
description = "The <Name> language server package to use.";
48+
};
49+
};
50+
51+
# Add language-specific options here
52+
};
53+
54+
config = lib.mkIf cfg.enable {
55+
packages = [
56+
cfg.package
57+
] ++ lib.optional cfg.lsp.enable cfg.lsp.package;
58+
59+
# Optional: environment variables
60+
# env.GOROOT = cfg.package + "/share/go/";
61+
62+
# Optional: PATH additions or shell setup
63+
# enterShell = ''
64+
# export PATH=$SOME_PATH/bin:$PATH
65+
# '';
66+
};
67+
}
68+
```
69+
70+
## Key Conventions
71+
72+
### LSP Support
73+
74+
Most language modules include an LSP sub-option. The `enable` default should be `true` so users get IDE support out of the box:
75+
76+
```nix
77+
lsp = {
78+
enable = lib.mkEnableOption "<Name> Language Server" // { default = true; };
79+
package = lib.mkOption {
80+
type = lib.types.package;
81+
default = pkgs.<lsp-package>;
82+
defaultText = lib.literalExpression "pkgs.<lsp-package>";
83+
description = "The <Name> language server package to use.";
84+
};
85+
};
86+
```
87+
88+
Add the LSP package conditionally:
89+
90+
```nix
91+
packages = [
92+
cfg.package
93+
] ++ lib.optional cfg.lsp.enable cfg.lsp.package;
94+
```
95+
96+
### Version Pinning via Overlays
97+
98+
When a language has a Nix overlay for version management, use `config.lib.getInput` for lazy input fetching:
99+
100+
```nix
101+
let
102+
overlay = config.lib.getInput {
103+
name = "<name>-overlay";
104+
url = "github:<owner>/<overlay-repo>";
105+
attribute = "languages.<name>.version";
106+
follows = [ "nixpkgs" ];
107+
};
108+
in
109+
{
110+
options.languages.<name> = {
111+
version = lib.mkOption {
112+
type = lib.types.nullOr lib.types.str;
113+
default = null;
114+
description = ''
115+
The <Name> version to use.
116+
This automatically sets `languages.<name>.package` using [<overlay-name>](<overlay-url>).
117+
'';
118+
example = "<example-version>";
119+
};
120+
# ...
121+
};
122+
123+
config = lib.mkIf cfg.enable {
124+
languages.<name>.package = lib.mkIf (cfg.version != null) (
125+
overlay.packages.${pkgs.stdenv.system}.${cfg.version}
126+
);
127+
# ...
128+
};
129+
}
130+
```
131+
132+
The `attribute` field in `getInput` tells devenv which user-facing option triggers fetching this input. The input is only fetched when that option is set to a non-default value.
133+
134+
### Environment Variables
135+
136+
Set language-specific environment variables in `env`:
137+
138+
```nix
139+
env.GOROOT = cfg.package + "/share/go/";
140+
env.GOPATH = config.env.DEVENV_STATE + "/go";
141+
```
142+
143+
Use `config.env.DEVENV_STATE` for persistent state directories (e.g., package caches, installed binaries).
144+
145+
### Shell Setup (enterShell)
146+
147+
Use `enterShell` for PATH additions or runtime setup that can't be done via `env`:
148+
149+
```nix
150+
enterShell = ''
151+
export PATH=$GOPATH/bin:$PATH
152+
'';
153+
```
154+
155+
### Git Hooks Integration
156+
157+
When the language has formatter/linter tools supported by git-hooks, wire them up:
158+
159+
```nix
160+
# Point hook tools at the configured package
161+
git-hooks.tools = {
162+
cargo = config.lib.mkOverrideDefault cfg.toolchainPackage;
163+
rustfmt = config.lib.mkOverrideDefault cfg.toolchainPackage;
164+
};
165+
166+
# Or set hook packages directly
167+
git-hooks.hooks = {
168+
mix-format.package = cfg.package;
169+
};
170+
```
171+
172+
### Enabling Companion Languages
173+
174+
When a language requires another (e.g., Rust needs a C compiler):
175+
176+
```nix
177+
languages.c.enable = lib.mkDefault true;
178+
```
179+
180+
Use `lib.mkDefault` so users can override if needed.
181+
182+
### Backward Compatibility (Renamed Options)
183+
184+
When migrating options, use `mkRenamedOptionModule` in imports:
185+
186+
```nix
187+
imports = [
188+
(lib.mkRenamedOptionModule [ "languages" "<name>" "old-option" ] [ "languages" "<name>" "new-option" ])
189+
];
190+
```
191+
192+
### Complexity Levels
193+
194+
**Simple** (nim.nix, elixir.nix): Just `enable`, `package`, `lsp`, and packages list. Use this for languages with straightforward nixpkgs support and no special setup.
195+
196+
**Medium** (go.nix, zig.nix): Adds `version` option with overlay, environment variables, and `enterShell`. Use when the language benefits from version pinning or needs runtime paths configured.
197+
198+
**Complex** (rust.nix, javascript.nix): Multiple sub-features (toolchain management, package managers, build tools), assertions, `mkMerge` for conditional config blocks. Use only when the language ecosystem genuinely demands it.
199+
200+
Start simple and add complexity only as needed.

0 commit comments

Comments
 (0)