Compare commits

..

10 Commits

Author SHA1 Message Date
Chiron
54fa93574b refactor: remove dead code, extract shared agent options, optimize flake
- Remove dead overlays/default.nix (flake defines overlays inline)
- Remove orphaned overlays/mods/{beads,n8n}.nix (never imported)
- Remove docs/packages/notesmd-cli.md (package doesn't exist)
- Extract externalSkills submodule to shared-options.nix (eliminates
  ~100 lines of duplication across opencode/claude-code/pi modules)
- Fix lib output: use nixpkgs.lib directly instead of instantiating
  a full nixpkgs just to get lib
- Add lib unit tests to flake checks
- Update stale comment in coding-rules.nix
2026-04-18 10:15:50 +00:00
Chiron
c9ecc0809f fix: add externalSkills option to pi agent module
Skills from flake inputs (e.g. Basecamp) were not being passed to
mkOpencodeSkills for the pi agent, so they never appeared in
~/.pi/agent/skills/. This adds the same externalSkills option that
the opencode agent module already has.
2026-04-18 10:07:44 +00:00
Chiron
44c7e0d19a chore: sync non-pi changes from remote (eigent update, formatting fixes, gitignore) 2026-04-18 10:05:59 +00:00
Chiron
a0f4d401df docs: update AGENTS.md to reflect current codebase state 2026-04-18 10:05:16 +00:00
Chiron
d04d405297 test: add basic lib function tests for agents and coding-rules 2026-04-18 10:05:16 +00:00
Chiron
2494da1054 docs: add CHANGELOG.md 2026-04-18 10:05:16 +00:00
Chiron
b2c8c935af refactor: remove redundant 'additions' overlay (identical to 'default') 2026-04-18 10:05:16 +00:00
Chiron
c6d8376dda refactor: tool-agnostic naming in coding-rules.nix internals 2026-04-18 10:05:16 +00:00
Chiron
0331316755 chore: remove dead overlay entries for non-existent flake inputs 2026-04-18 10:05:16 +00:00
Chiron
a4e540630d docs: clarify system binding in pkgs/default.nix 2026-04-18 10:05:16 +00:00
20 changed files with 642 additions and 671 deletions

2
.gitignore vendored
View File

@@ -43,3 +43,5 @@ flake.lock.bak
.sidecar-start.sh .sidecar-start.sh
.sidecar-base .sidecar-base
.td-root .td-root
.pi-lens
.cache

View File

@@ -1,10 +1,5 @@
# m3ta-nixpkgs Knowledge Base # m3ta-nixpkgs Knowledge Base
## MANDATORY: Use td for Task Management
You must run td usage --new-session at conversation start (or after /clear) to see current work.
Use td usage -q for subsequent reads.
**Generated:** 2026-02-14 **Generated:** 2026-02-14
**Commit:** dc2f3b6 **Commit:** dc2f3b6
**Branch:** master **Branch:** master
@@ -114,8 +109,8 @@ Port management utilities. See [Port Management](#port-management).
### `lib.agents` ### `lib.agents`
Harness-agnostic agent management. Reads canonical `agent.toml` from the AGENTS Harness-agnostic agent management. Reads canonical `agent.toml` +
flake input and renders tool-specific configs. `system-prompt.md` from the AGENTS flake input and renders tool-specific configs.
**Functions:** **Functions:**
@@ -124,17 +119,18 @@ flake input and renders tool-specific configs.
| `loadCanonical { agentsInput }` | Load canonical agents from AGENTS flake | | `loadCanonical { agentsInput }` | Load canonical agents from AGENTS flake |
| `renderForOpencode { pkgs, canonical, modelOverrides }` | Render to OpenCode file-based agents | | `renderForOpencode { pkgs, canonical, modelOverrides }` | Render to OpenCode file-based agents |
| `renderForClaudeCode { pkgs, canonical, modelOverrides }` | Render to Claude Code agents + settings.json | | `renderForClaudeCode { pkgs, canonical, modelOverrides }` | Render to Claude Code agents + settings.json |
| `renderForPi { pkgs, canonical }` | Render to Pi AGENTS.md + SYSTEM.md | | `renderForPi { pkgs, canonical, modelOverrides, primaryAgent }` | Render to Pi AGENTS.md + SYSTEM.md + agents/ |
| `renderForTool { pkgs, agentsInput, tool, modelOverrides }` | Dispatch to correct renderer | | `renderForTool { pkgs, agentsInput, tool, modelOverrides }` | Dispatch to correct renderer by tool name |
| `shellHookForTool { pkgs, agentsInput, tool, modelOverrides }` | Generate devShell shellHook | | `shellHookForTool { pkgs, agentsInput, tool, modelOverrides }` | Generate devShell shellHook (symlinks rendered files) |
### `lib.coding-rules` ### `lib.coding-rules`
Coding rules injection (renamed from `lib.opencode-rules`). The old name still works. Coding rules injection. Generates `coding-rules.json` + symlinks rules from
the AGENTS repository. The old `lib.opencode-rules` name still works.
| Function | Purpose | | Function | Purpose |
|----------|--------| |----------|--------|
| `mkCodingRules { agents, languages, concerns, frameworks }` | Generate rules config + shellHook | | `mkCodingRules { agents, languages, concerns, frameworks, rulesDir }` | Generate rules config + shellHook. `rulesDir` defaults to `.opencode-rules` |
| `mkOpencodeRules` | Backward-compat alias for `mkCodingRules` | | `mkOpencodeRules` | Backward-compat alias for `mkCodingRules` |
## PORT MANAGEMENT ## PORT MANAGEMENT
@@ -188,38 +184,34 @@ Types: `feat`, `fix`, `docs`, `style`, `refactor`, `chore`
## Task Management ## Task Management
This project uses **td** for tracking tasks across AI coding sessions. **td** is an optional task-tracking package. See `docs/packages/td.md` for details.
Run `td usage --new-session` at conversation start to see current work.
Use `td usage -q` for subsequent reads.
**Quick reference:** ## Agent System Architecture
- `td usage --new-session` - Start new session and view tasks The agent system uses harness-agnostic canonical definitions stored as
- `td usage -q` - Quick view of current tasks (subsequent reads) `agent.toml` + `system-prompt.md` in the AGENTS repository. Renderers in
- `td version` - Check version `lib/agents.nix` transform these into tool-specific configs at build time.
For full workflow details, see the [td documentation](./docs/packages/td.md). ### How it works
## MIGRATION: Agent System (OpenCode → Canonical TOML) 1. **Canonical definitions** live in the AGENTS repo as `agent.toml` files
(one per agent) with shared fields: name, description, mode, systemPrompt,
permissions, skills.
2. **`loadCanonical`** reads all agent definitions from the AGENTS flake input.
3. **Renderers** produce tool-specific output:
- `renderForOpencode``*.md` files with YAML frontmatter for `.opencode/agents/`
- `renderForClaudeCode``.claude/agents/*.md` + `.claude/settings.json` with permission rules
- `renderForPi``AGENTS.md`, `SYSTEM.md`, `agents/*.md` for Pi's subagent format
4. **`renderForTool`** dispatches to the correct renderer by tool name
(`"opencode"`, `"claude-code"`, or `"pi"`).
5. **`shellHookForTool`** generates a devShell shellHook that symlinks rendered
files into the project directory.
6. **HM modules** in `modules/home-manager/coding/agents/` handle per-tool
Home Manager integration.
The agent system was migrated from embedded `agents.json` to harness-agnostic ### Key files in this repo
canonical `agent.toml` + `system-prompt.md` in the AGENTS repo. Renderers in
`lib/agents.nix` generate tool-specific configs.
### What changed in this repo - `lib/agents.nix` — renderers, dispatcher, shellHook generator
- `lib/coding-rules.nix` — coding rules injection (`mkCodingRules`)
- **`lib/agents.nix`**: New — 3 renderers (OpenCode, Claude Code, Pi) + dispatcher + shellHook - `modules/home-manager/coding/agents/` — per-tool HM sub-modules (opencode, claude-code, pi)
- **`lib/coding-rules.nix`**: Renamed from `opencode-rules.nix`, `mkCodingRules` replaces `mkOpencodeRules` - `modules/home-manager/coding/opencode.nix` — OpenCode HM module (slimmed, agents handled separately)
- **`modules/home-manager/coding/agents/`**: New — per-tool HM sub-modules
- **`modules/home-manager/coding/opencode.nix`**: Slimmed — no longer handles agents/skills/context
- **`flake.nix`**: Exports new `agents` HM module
### What the user must do
See `modules/home-manager/AGENTS.md` for the full migration guide. Summary:
1. Move `agentsInput`/`externalSkills` from `coding.opencode` to `coding.agents.opencode`
2. Add `modelOverrides` with previously hardcoded model strings
3. Run `home-manager switch`
4. Remove legacy `agents.json` + `prompts/*.txt` from AGENTS repo
5. Remove `lib.agentsJson` backward-compat bridge from AGENTS `flake.nix`

107
CHANGELOG.md Normal file
View File

@@ -0,0 +1,107 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/).
## [Unreleased]
### Changed
- Remove duplicate opencode-rules.nix (backward-compat alias preserved)
- Tool-agnostic naming in coding-rules lib internals
- Remove redundant overlay entries for non-existent flake inputs
- Remove redundant 'additions' overlay (identical to 'default')
### Removed
- Dead overlay entries for non-existent flake inputs
## [0.4.0] - 2026-04-15
### Added
- Pi-agent wrapper with systemd sandbox and per-host-user policy
- Containerized Pi agent
- `lib.agents.nix` with loadCanonical, renderers (OpenCode, Claude Code, Pi), and shellHook
- `lib.coding-rules` helper for per-project rule injection (renamed from opencode-rules)
- Home Manager modules for coding agents: `claude-code`, `opencode`, `pi`
- Agents rework with canonical TOML format and harness-agnostic renderers
- `vibetyper` and `eigent` packages
- `openspec` package
- `basecamp-cli` package
- `openshell` package (0.0.14 through 0.0.23)
- `openwork` package
- Opencode config moved into m3ta-nixpkgs
- Opencode dev shell with mkCodingRules demo
### Changed
- OpenCode flake input updated through v1.1.65 to v1.3.6
- Switched from local opencode package to upstream flake input
- Removed opencode-desktop (awaiting upstream fix), later re-enabled
- Nix eval warnings resolved
- Flake inputs updated throughout
### Fixed
- Pi settings sync
- Remove openwork sidecars in preFixup to prevent .opencode-wrapped conflict
- Remove sidecar binaries from openwork $out/bin to fix buildEnv conflict
- Vibetyper .desktop entry
- Opencode module formatting
- Formatting opencode module
## [0.3.0] - 2026-02-20
### Added
- `notesmd-cli` package with flake checks
- `sidecar` and `td` packages
- `opencode-desktop` package with Wayland support
- `mem0` package (1.0.2 through 1.0.9)
- `kestracli` / `kestractl` package (1.0.0 to 1.2.2)
### Changed
- Nix-update CI workflow optimized with caching and parallel processing
- Restructured n8n version handling for nix-update compatibility
- Switched formatter from nixpkgs-fmt to alejandra
- Replace local opencode with upstream flake input v1.1.27
### Fixed
- n8n build error
- n8n pnpm hash
- n8n update script
- Gitea runner opencode.url flake input
- nix-update workflow: YAML syntax, jobs indentation, PR body formatting
- Arithmetic increment failing with set -e in nix-update workflow
- Removed magic-nix-cache-action causing platform mapping error
- Opencode bun version requirement patched to match upstream lockfile
- Deprecated opencode update logic removed
- nix fmt without arg in workflow
- Extra Lua config renamed initLua
- Stt-ptt use pkill for better process management
## [0.2.0] - 2026-01-13
### Added
- Gitea Actions workflow for automated package updates with nix-update
- `n8n`, `beads`, and `opencode` packages
- `stt-ptt` package with auto-language detection
- `rofi-project-opener` for rofi-based project launching
- Hierarchical AGENTS.md knowledge base
- Dev shell structure with python and devops shells
- Port management modules (NixOS + Home Manager)
- Port helper library (`lib/ports.nix`)
### Changed
- Beads updated through v0.49.1
- N8n updated through v2.8.1
- Opencode updated through v1.1.18
- Documentation expanded with comprehensive patterns and HM module docs
### Fixed
- Python env version fix for marimo
## [0.1.0] - 2025-10-04
### Added
- Initial flake setup with packages, overlays, modules, and shells
- NixOS and Home Manager module infrastructure
- `lib/` shared utilities
- `overlays/mods/` for package modifications
- `templates/` for new packages/modules
- `examples/` for usage documentation

View File

@@ -1,117 +0,0 @@
# notesmd-cli
Obsidian CLI (Community) - Interact with Obsidian in the terminal.
## Description
notesmd-cli is a command-line interface for interacting with Obsidian, the popular knowledge management and note-taking application. It allows you to create, search, and manipulate notes directly from the terminal.
## Features
- 📝 **Note Creation**: Create new notes from the command line
- 🔍 **Search**: Search through your Obsidian vault
- 📂 **Vault Management**: Interact with your vault structure
- 🔗 **WikiLink Support**: Work with Obsidian's WikiLink format
- 🏷️ **Tag Support**: Manage and search by tags
-**Fast**: Lightweight Go binary with no external dependencies
## Installation
### Via Overlay
```nix
{pkgs, ...}: {
environment.systemPackages = with pkgs; [
notesmd-cli
];
}
```
### Direct Reference
```nix
{pkgs, ...}: {
environment.systemPackages = with pkgs; [
inputs.m3ta-nixpkgs.packages.${pkgs.system}.notesmd-cli
];
}
```
### Run Directly
```bash
nix run git+https://code.m3ta.dev/m3tam3re/nixpkgs#notesmd-cli
```
## Usage
### Basic Commands
```bash
# Show help
notesmd-cli --help
# Create a new note
notesmd-cli new "My Note Title"
# Search notes
notesmd-cli search "search term"
# List notes
notesmd-cli list
```
### Working with Vaults
```bash
# Specify vault path
notesmd-cli --vault /path/to/vault new "Note Title"
# Open a note in Obsidian
notesmd-cli open "Note Name"
```
### Advanced Usage
```bash
# Search with tags
notesmd-cli search --tag "project"
# Append to existing note
notesmd-cli append "Note Name" "Additional content"
```
## Configuration
### Environment Variables
- `OBSIDIAN_VAULT`: Default vault path
### Command Line Options
Run `notesmd-cli --help` for a complete list of options.
## Build Information
- **Version**: 0.3.0
- **Language**: Go
- **License**: MIT
- **Source**: [GitHub](https://github.com/Yakitrak/notesmd-cli)
- **Vendor Hash**: null (no external dependencies)
## Platform Support
- Linux
- macOS (Unix systems)
## Notes
- No vendor dependencies (pure Go stdlib)
- The binary is named `notesmd-cli` (not `notesmd`)
- This is the community CLI, not the official Obsidian CLI
## Related
- [Obsidian](https://obsidian.md) - The Obsidian application
- [Adding Packages](../guides/adding-packages.md) - How to add new packages
- [Quick Start](../QUICKSTART.md) - Getting started guide

View File

@@ -56,13 +56,6 @@
inputs = inputs; inputs = inputs;
}; };
# Individual overlays for more granular control
additions = final: prev:
import ./pkgs {
pkgs = final;
inputs = inputs;
};
modifications = final: prev: import ./overlays/mods {inherit prev;}; modifications = final: prev: import ./overlays/mods {inherit prev;};
}; };
@@ -84,10 +77,7 @@
}; };
# Library functions - helper utilities for your configuration # Library functions - helper utilities for your configuration
lib = forAllSystems (system: let lib = forAllSystems (system: import ./lib {lib = nixpkgs.lib;});
pkgs = pkgsFor system;
in
import ./lib {lib = pkgs.lib;});
# Development shells for various programming environments # Development shells for various programming environments
# Usage: nix develop .#<shell-name> # Usage: nix develop .#<shell-name>
@@ -111,6 +101,9 @@
${pkgs.alejandra}/bin/alejandra --check ${./.} ${pkgs.alejandra}/bin/alejandra --check ${./.}
touch $out touch $out
''; '';
# Lib unit tests
lib-agents = import ./tests/lib/agents-test.nix;
lib-coding-rules = import ./tests/lib/coding-rules-test.nix;
}); });
# Templates for creating new packages/modules # Templates for creating new packages/modules

View File

@@ -1,4 +1,4 @@
# Opencode rules management utilities # Coding rules management utilities
# #
# This module provides functions to configure Opencode agent rules across # This module provides functions to configure Opencode agent rules across
# multiple projects. Rules are defined in the AGENTS repository and can be # multiple projects. Rules are defined in the AGENTS repository and can be
@@ -26,7 +26,7 @@
# #
# The shellHook creates: # The shellHook creates:
# - A `.opencode-rules/` symlink pointing to the AGENTS repository rules directory # - A `.opencode-rules/` symlink pointing to the AGENTS repository rules directory
# - An `opencode.json` file with a $schema reference and instructions list # - A `coding-rules.json` file with a $schema reference and instructions list
# #
# The instructions list contains paths relative to the project root, all prefixed # The instructions list contains paths relative to the project root, all prefixed
# with `.opencode-rules/`, making them portable across different project locations. # with `.opencode-rules/`, making them portable across different project locations.
@@ -46,7 +46,7 @@
# #
# Returns: # Returns:
# An attribute set containing: # An attribute set containing:
# - shellHook: Bash code to create symlink and opencode.json # - shellHook: Bash code to create symlink and coding-rules.json
# - instructions: List of rule file paths (relative to project root) # - instructions: List of rule file paths (relative to project root)
# #
# Example: # Example:
@@ -82,9 +82,8 @@
], ],
frameworks ? [], frameworks ? [],
extraInstructions ? [], extraInstructions ? [],
rulesDir ? ".opencode-rules",
}: let }: let
rulesDir = ".opencode-rules";
# Build instructions list by mapping concerns, languages, frameworks to their file paths # Build instructions list by mapping concerns, languages, frameworks to their file paths
# All paths are relative to project root via the rulesDir symlink # All paths are relative to project root via the rulesDir symlink
instructions = instructions =
@@ -93,8 +92,8 @@
++ (map (f: "${rulesDir}/frameworks/${f}.md") frameworks) ++ (map (f: "${rulesDir}/frameworks/${f}.md") frameworks)
++ extraInstructions; ++ extraInstructions;
# Generate JSON configuration for Opencode # Generate JSON configuration for coding rules
opencodeConfig = { rulesConfig = {
"$schema" = "https://opencode.ai/config.json"; "$schema" = "https://opencode.ai/config.json";
inherit instructions; inherit instructions;
}; };
@@ -102,15 +101,15 @@
inherit instructions; inherit instructions;
# Shell hook to set up rules in the project # Shell hook to set up rules in the project
# Creates a symlink to the AGENTS rules directory and generates opencode.json # Creates a symlink to the AGENTS rules directory and generates coding-rules.json
shellHook = '' shellHook = ''
# Create/update symlink to AGENTS rules directory # Create/update symlink to AGENTS rules directory
ln -sfn ${agents}/rules ${rulesDir} ln -sfn ${agents}/rules ${rulesDir}
# Generate opencode.json configuration file # Generate coding-rules.json configuration file
cat > opencode.json <<'OPENCODE_EOF' cat > coding-rules.json <<'RULES_EOF'
${builtins.toJSON opencodeConfig} ${builtins.toJSON rulesConfig}
OPENCODE_EOF RULES_EOF
''; '';
}; };

View File

@@ -3,88 +3,87 @@
lib, lib,
pkgs, pkgs,
... ...
}: }: let
with lib; let shared = import ./shared-options.nix {inherit lib;};
cfg = config.coding.agents.claude-code; in
mcpCfg = config.programs.mcp or null; with lib; let
in { cfg = config.coding.agents.claude-code;
options.coding.agents.claude-code = { mcpCfg = config.programs.mcp or null;
enable = mkEnableOption "Claude Code agent management via canonical agent.toml definitions"; in {
options.coding.agents.claude-code = {
enable = mkEnableOption "Claude Code agent management via canonical agent.toml definitions";
agentsInput = mkOption { agentsInput = shared.mkAgentsInputOption ''
type = types.nullOr types.anything;
default = null;
description = ''
The `agents` flake input (your personal AGENTS repo). The `agents` flake input (your personal AGENTS repo).
When set, agents are rendered from canonical agent.toml files When set, agents are rendered from canonical agent.toml files
and symlinked to ~/.claude/agents/. and symlinked to ~/.claude/agents/.
''; '';
modelOverrides = shared.mkModelOverridesOption;
externalSkills = shared.externalSkillsOption;
mcpServers = mkOption {
type = types.attrsOf types.anything;
default =
if mcpCfg != null
then mcpCfg.servers
else {};
defaultText = literalExpression "config.programs.mcp.servers";
description = ''
MCP server configurations for Claude Code.
Merged into ~/.claude/settings.json alongside permissions.
Automatically inherits from config.programs.mcp.servers.
'';
};
}; };
modelOverrides = mkOption { config = mkIf cfg.enable (let
type = types.attrsOf types.str; agentsLib = (import ../../../../lib {inherit lib;}).agents;
default = {};
description = '' # Rendered agents + permissions (only if agentsInput is set)
Per-agent model overrides. Maps agent slug to model alias or ID. rendered = mkIf (cfg.agentsInput != null) (
Example: { chiron = "claude-sonnet-4-20250514"; } agentsLib.renderForClaudeCode {
''; inherit pkgs;
example = literalExpression '' canonical = cfg.agentsInput.lib.loadAgents;
{ modelOverrides = cfg.modelOverrides;
chiron = "claude-sonnet-4-20250514";
"chiron-forge" = "claude-sonnet-4-20250514";
} }
''; );
};
mcpServers = mkOption { # Merge MCP servers into the rendered settings.json.
type = types.attrsOf types.anything; # The renderer produces { permissions: { allow, deny } }.
default = if mcpCfg != null then mcpCfg.servers else {}; # We add mcpServers on top.
defaultText = literalExpression "config.programs.mcp.servers"; settingsJson =
description = '' if cfg.agentsInput != null
MCP server configurations for Claude Code. then let
Merged into ~/.claude/settings.json alongside permissions. renderedSettings = builtins.fromJSON (builtins.readFile "${rendered}/.claude/settings.json");
Automatically inherits from config.programs.mcp.servers. withMcp =
''; if cfg.mcpServers != {}
}; then renderedSettings // {mcpServers = cfg.mcpServers;}
}; else renderedSettings;
in
pkgs.writeText "claude-settings.json" (builtins.toJSON withMcp)
else if cfg.mcpServers != {}
then pkgs.writeText "claude-settings.json" (builtins.toJSON {mcpServers = cfg.mcpServers;})
else null;
in {
# Rendered agent files symlinked to ~/.claude/agents/
home.file.".claude/agents" = mkIf (cfg.agentsInput != null) {
source = "${rendered}/.claude/agents";
};
config = mkIf cfg.enable (let # Skills (merged from personal AGENTS repo + optional external skills)
agentsLib = (import ../../../../lib {inherit lib;}).agents; home.file.".claude/skills" = mkIf (cfg.agentsInput != null) {
source = cfg.agentsInput.lib.mkOpencodeSkills {
inherit pkgs;
customSkills = "${cfg.agentsInput}/skills";
externalSkills = shared.mapExternalSkills cfg.externalSkills;
};
};
# Rendered agents + permissions (only if agentsInput is set) # Rendered settings.json with permissions + MCP servers
rendered = mkIf (cfg.agentsInput != null) ( home.file.".claude/settings.json" = mkIf (settingsJson != null) {
agentsLib.renderForClaudeCode { source = "${settingsJson}";
inherit pkgs; };
canonical = cfg.agentsInput.lib.loadAgents; });
modelOverrides = cfg.modelOverrides; }
}
);
# Merge MCP servers into the rendered settings.json.
# The renderer produces { permissions: { allow, deny } }.
# We add mcpServers on top.
settingsJson =
if cfg.agentsInput != null
then let
renderedSettings = builtins.fromJSON (builtins.readFile "${rendered}/.claude/settings.json");
withMcp =
if cfg.mcpServers != {}
then renderedSettings // {mcpServers = cfg.mcpServers;}
else renderedSettings;
in
pkgs.writeText "claude-settings.json" (builtins.toJSON withMcp)
else if cfg.mcpServers != {}
then pkgs.writeText "claude-settings.json" (builtins.toJSON {mcpServers = cfg.mcpServers;})
else null;
in {
# Rendered agent files symlinked to ~/.claude/agents/
home.file.".claude/agents" = mkIf (cfg.agentsInput != null) {
source = "${rendered}/.claude/agents";
};
# Rendered settings.json with permissions + MCP servers
home.file.".claude/settings.json" = mkIf (settingsJson != null) {
source = "${settingsJson}";
};
});
}

View File

@@ -3,111 +3,67 @@
lib, lib,
pkgs, pkgs,
... ...
}: }: let
with lib; let shared = import ./shared-options.nix {inherit lib;};
cfg = config.coding.agents.opencode; in
in { with lib; {
options.coding.agents.opencode = { options.coding.agents.opencode = {
enable = mkEnableOption "OpenCode agent management via canonical agent.toml definitions"; enable = mkEnableOption "OpenCode agent management via canonical agent.toml definitions";
agentsInput = mkOption { agentsInput = shared.mkAgentsInputOption ''
type = types.nullOr types.anything;
default = null;
description = ''
The `agents` flake input (your personal AGENTS repo). The `agents` flake input (your personal AGENTS repo).
When set, agents are rendered from canonical agent.toml files When set, agents are rendered from canonical agent.toml files
and symlinked to ~/.config/opencode/agents/. and symlinked to ~/.config/opencode/agents/.
''; '';
modelOverrides = shared.mkModelOverridesOption;
externalSkills = shared.externalSkillsOption;
}; };
modelOverrides = mkOption { config = mkIf config.coding.agents.opencode.enable {
type = types.attrsOf types.str; # Rendered agent files symlinked to ~/.config/opencode/agents/
default = {}; xdg.configFile."opencode/agents" = let
description = '' cfg = config.coding.agents.opencode;
Per-agent model overrides. Maps agent slug to model string. in
Example: { chiron = "anthropic/claude-sonnet-4"; } mkIf (cfg.agentsInput != null) {
''; source = (import ../../../../lib {inherit lib;}).agents.renderForOpencode {
example = literalExpression '' inherit pkgs;
{ canonical = cfg.agentsInput.lib.loadAgents;
chiron = "anthropic/claude-sonnet-4"; modelOverrides = cfg.modelOverrides;
"chiron-forge" = "anthropic/claude-sonnet-4";
}
'';
};
externalSkills = mkOption {
type = types.listOf (types.submodule {
options = {
src = mkOption {
type = types.anything;
description = "Flake input pointing to a skills repository root.";
};
skillsDir = mkOption {
type = types.str;
default = "skills";
description = ''
Subdirectory inside src that contains skill folders.
'';
};
selectSkills = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
description = ''
List of skill names to cherry-pick from this source.
null means include every skill found in skillsDir.
'';
}; };
}; };
});
default = [];
description = ''
External skill sources passed to mkOpencodeSkills.
Each entry maps directly to an element of the externalSkills
list accepted by the AGENTS flake's lib.mkOpencodeSkills.
'';
example = literalExpression ''
[
{ src = inputs.skills-anthropic; selectSkills = [ "claude-api" ]; }
{ src = inputs.skills-vercel; }
]
'';
};
};
config = mkIf cfg.enable { # Skills (merged from personal AGENTS repo + optional external skills)
# Rendered agent files symlinked to ~/.config/opencode/agents/ xdg.configFile."opencode/skills" = let
xdg.configFile."opencode/agents" = mkIf (cfg.agentsInput != null) { cfg = config.coding.agents.opencode;
source = (import ../../../../lib {inherit lib;}).agents.renderForOpencode { in
inherit pkgs; mkIf (cfg.agentsInput != null) {
canonical = cfg.agentsInput.lib.loadAgents; source = cfg.agentsInput.lib.mkOpencodeSkills {
modelOverrides = cfg.modelOverrides; inherit pkgs;
}; customSkills = "${cfg.agentsInput}/skills";
}; externalSkills = shared.mapExternalSkills cfg.externalSkills;
};
};
# Skills (merged from personal AGENTS repo + optional external skills) # Static config dirs from AGENTS repo
xdg.configFile."opencode/skills" = mkIf (cfg.agentsInput != null) { xdg.configFile."opencode/context" = let
source = cfg.agentsInput.lib.mkOpencodeSkills { cfg = config.coding.agents.opencode;
inherit pkgs; in
customSkills = "${cfg.agentsInput}/skills"; mkIf (cfg.agentsInput != null) {
externalSkills = source = "${cfg.agentsInput}/context";
map ( };
entry: xdg.configFile."opencode/commands" = let
{inherit (entry) src skillsDir;} cfg = config.coding.agents.opencode;
// optionalAttrs (entry.selectSkills != null) {inherit (entry) selectSkills;} in
) mkIf (cfg.agentsInput != null) {
cfg.externalSkills; source = "${cfg.agentsInput}/commands";
}; };
xdg.configFile."opencode/prompts" = let
cfg = config.coding.agents.opencode;
in
mkIf (cfg.agentsInput != null) {
source = "${cfg.agentsInput}/prompts";
};
}; };
}
# Static config dirs from AGENTS repo
xdg.configFile."opencode/context" = mkIf (cfg.agentsInput != null) {
source = "${cfg.agentsInput}/context";
};
xdg.configFile."opencode/commands" = mkIf (cfg.agentsInput != null) {
source = "${cfg.agentsInput}/commands";
};
xdg.configFile."opencode/prompts" = mkIf (cfg.agentsInput != null) {
source = "${cfg.agentsInput}/prompts";
};
};
}

View File

@@ -3,232 +3,225 @@
lib, lib,
pkgs, pkgs,
... ...
}: }: let
with lib; let shared = import ./shared-options.nix {inherit lib;};
cfg = config.coding.agents.pi; in
mcpCfg = config.programs.mcp or null; with lib; let
in { cfg = config.coding.agents.pi;
options.coding.agents.pi = { mcpCfg = config.programs.mcp or null;
enable = mkEnableOption "Pi agent management via canonical agent.toml definitions"; in {
options.coding.agents.pi = {
enable = mkEnableOption "Pi agent management via canonical agent.toml definitions";
mcpServers = mkOption { mcpServers = mkOption {
type = types.attrsOf types.anything; type = types.attrsOf types.anything;
default = if mcpCfg != null then mcpCfg.servers else {}; default = if mcpCfg != null then mcpCfg.servers else {};
defaultText = literalExpression "config.programs.mcp.servers"; defaultText = literalExpression "config.programs.mcp.servers";
description = '' description = ''
MCP server configurations for Pi (pi-mcp-adapter). MCP server configurations for Pi (pi-mcp-adapter).
Written to ~/.pi/agent/mcp.json. Written to ~/.pi/agent/mcp.json.
Automatically inherits from config.programs.mcp.servers. Automatically inherits from config.programs.mcp.servers.
''; '';
}; };
agentsInput = mkOption { agentsInput = shared.mkAgentsInputOption ''
type = types.nullOr types.anything;
default = null;
description = ''
The `agents` flake input (your personal AGENTS repo). The `agents` flake input (your personal AGENTS repo).
When set, the primary agent's system prompt is rendered as SYSTEM.md, When set, the primary agent's system prompt is rendered as SYSTEM.md,
all agents are listed in AGENTS.md, and subagent .md files are deployed. all agents are listed in AGENTS.md, and subagent .md files are deployed.
''; '';
};
modelOverrides = mkOption { modelOverrides = shared.mkModelOverridesOption;
type = types.attrsOf types.str;
default = {};
description = ''
Per-agent model overrides for Pi subagents.
Maps agent slug to model string, e.g.:
{ chiron = "anthropic/claude-sonnet-4"; chiron-forge = "anthropic/claude-sonnet-4"; }
'';
};
primaryAgent = mkOption { primaryAgent = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
description = '' description = ''
Override which canonical agent is used as primary for SYSTEM.md. Override which canonical agent is used as primary for SYSTEM.md.
When null, the first agent with mode="primary" is used. When null, the first agent with mode="primary" is used.
''; '';
};
settings = mkOption {
type = types.submodule {
freeformType = types.attrsOf types.anything;
options = {
packages = mkOption {
type = types.listOf types.str;
default = [];
description = ''
Pi packages to install (npm:, git:, or local paths).
These are written to ~/.pi/agent/settings.json.
'';
};
defaultProvider = mkOption {
type = types.nullOr types.str;
default = null;
description = "Default LLM provider (e.g. 'anthropic', 'openai', 'zai').";
};
defaultModel = mkOption {
type = types.nullOr types.str;
default = null;
description = "Default model ID.";
};
defaultThinkingLevel = mkOption {
type = types.nullOr (types.enum ["off" "minimal" "low" "medium" "high" "xhigh"]);
default = null;
description = "Default extended thinking level.";
};
theme = mkOption {
type = types.nullOr types.str;
default = null;
description = "Pi theme name.";
};
hideThinkingBlock = mkOption {
type = types.nullOr types.bool;
default = null;
description = "Hide thinking blocks in output.";
};
quietStartup = mkOption {
type = types.nullOr types.bool;
default = null;
description = "Hide startup header.";
};
compaction = mkOption {
type = types.nullOr (types.submodule {
options = {
enabled = mkOption {
type = types.nullOr types.bool;
default = null;
};
reserveTokens = mkOption {
type = types.nullOr types.int;
default = null;
};
keepRecentTokens = mkOption {
type = types.nullOr types.int;
default = null;
};
};
});
default = null;
description = "Auto-compaction settings.";
};
enabledModels = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
description = "Model patterns for Ctrl+P cycling.";
};
sessionDir = mkOption {
type = types.nullOr types.str;
default = null;
description = "Directory where session files are stored.";
};
extensions = mkOption {
type = types.listOf types.str;
default = [];
description = "Local extension file paths or directories.";
};
skills = mkOption {
type = types.listOf types.str;
default = [];
description = "Local skill file paths or directories.";
};
};
}; };
default = {};
description = ''
Pi settings written to ~/.pi/agent/settings.json.
Only non-null values are included in the generated JSON.
See pi docs/settings.md for all options.
'';
};
};
config = mkIf cfg.enable (let externalSkills = shared.externalSkillsOption;
# Build settings.json by filtering out null values recursively
filterNulls = attrs:
lib.filterAttrs (_: v: v != null) (
builtins.mapAttrs (_: v:
if builtins.isAttrs v
then let
filtered = filterNulls v;
in
if filtered == {} then null else filtered
else v) attrs
);
piSettings = filterNulls cfg.settings; settings = mkOption {
type = types.submodule {
freeformType = types.attrsOf types.anything;
options = {
packages = mkOption {
type = types.listOf types.str;
default = [];
description = ''
Pi packages to install (npm:, git:, or local paths).
These are written to ~/.pi/agent/settings.json.
'';
};
# Rendered agents (only computed when agentsInput is set) defaultProvider = mkOption {
rendered = type = types.nullOr types.str;
if cfg.agentsInput != null default = null;
then description = "Default LLM provider (e.g. 'anthropic', 'openai', 'zai').";
(import ../../../../lib {inherit lib;}).agents.renderForPi { };
inherit pkgs;
canonical = cfg.agentsInput.lib.loadAgents;
modelOverrides = cfg.modelOverrides;
primaryAgent = cfg.primaryAgent;
}
else null;
# Dynamic home.file entries for agent .md files defaultModel = mkOption {
agentFiles = type = types.nullOr types.str;
if cfg.agentsInput != null default = null;
then description = "Default model ID.";
let };
agentNames = builtins.attrNames cfg.agentsInput.lib.loadAgents;
in
builtins.listToAttrs (
map (name: {
name = ".pi/agent/agents/${name}.md";
value = {source = "${rendered}/agents/${name}.md";};
})
agentNames
)
else {};
in {
home.file = mkMerge [
# ── MCP servers from programs.mcp → ~/.pi/agent/mcp.json ───────
(mkIf (cfg.mcpServers != {}) {
".pi/agent/mcp.json".text = builtins.toJSON {mcpServers = cfg.mcpServers;};
})
# ── ~/.pi/agent/settings.json ────────────────────────────────── defaultThinkingLevel = mkOption {
{ type = types.nullOr (types.enum ["off" "minimal" "low" "medium" "high" "xhigh"]);
".pi/agent/settings.json".text = builtins.toJSON piSettings; default = null;
} description = "Default extended thinking level.";
};
# ── AGENTS.md — agent descriptions and specialist listing ────── theme = mkOption {
(mkIf (cfg.agentsInput != null) { type = types.nullOr types.str;
".pi/agent/AGENTS.md".source = "${rendered}/AGENTS.md"; default = null;
}) description = "Pi theme name.";
};
# ── SYSTEM.md — primary agent's system prompt ────────────────── hideThinkingBlock = mkOption {
(mkIf (cfg.agentsInput != null) { type = types.nullOr types.bool;
".pi/agent/SYSTEM.md".source = "${rendered}/SYSTEM.md"; default = null;
}) description = "Hide thinking blocks in output.";
};
# ── Agents — pi-subagents .md files ──────────────────────────── quietStartup = mkOption {
agentFiles type = types.nullOr types.bool;
default = null;
description = "Hide startup header.";
};
# ── Skills symlinked from AGENTS repo ────────────────────────── compaction = mkOption {
(mkIf (cfg.agentsInput != null) { type = types.nullOr (types.submodule {
".pi/agent/skills".source = cfg.agentsInput.lib.mkOpencodeSkills { options = {
inherit pkgs; enabled = mkOption {
customSkills = "${cfg.agentsInput}/skills"; type = types.nullOr types.bool;
default = null;
};
reserveTokens = mkOption {
type = types.nullOr types.int;
default = null;
};
keepRecentTokens = mkOption {
type = types.nullOr types.int;
default = null;
};
};
});
default = null;
description = "Auto-compaction settings.";
};
enabledModels = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
description = "Model patterns for Ctrl+P cycling.";
};
sessionDir = mkOption {
type = types.nullOr types.str;
default = null;
description = "Directory where session files are stored.";
};
extensions = mkOption {
type = types.listOf types.str;
default = [];
description = "Local extension file paths or directories.";
};
skills = mkOption {
type = types.listOf types.str;
default = [];
description = "Local skill file paths or directories.";
};
};
}; };
}) default = {};
]; description = ''
}); Pi settings written to ~/.pi/agent/settings.json.
} Only non-null values are included in the generated JSON.
See pi docs/settings.md for all options.
'';
};
};
config = mkIf cfg.enable (let
# Build settings.json by filtering out null values recursively
filterNulls = attrs:
lib.filterAttrs (_: v: v != null) (
builtins.mapAttrs (_: v:
if builtins.isAttrs v
then let
filtered = filterNulls v;
in
if filtered == {} then null else filtered
else v) attrs
);
piSettings = filterNulls cfg.settings;
# Rendered agents (only computed when agentsInput is set)
rendered =
if cfg.agentsInput != null
then
(import ../../../../lib {inherit lib;}).agents.renderForPi {
inherit pkgs;
canonical = cfg.agentsInput.lib.loadAgents;
modelOverrides = cfg.modelOverrides;
primaryAgent = cfg.primaryAgent;
}
else null;
# Dynamic home.file entries for agent .md files
agentFiles =
if cfg.agentsInput != null
then
let
agentNames = builtins.attrNames cfg.agentsInput.lib.loadAgents;
in
builtins.listToAttrs (
map (name: {
name = ".pi/agent/agents/${name}.md";
value = {source = "${rendered}/agents/${name}.md";};
})
agentNames
)
else {};
in {
home.file = mkMerge [
# ── MCP servers from programs.mcp → ~/.pi/agent/mcp.json ───────
(mkIf (cfg.mcpServers != {}) {
".pi/agent/mcp.json".text = builtins.toJSON {mcpServers = cfg.mcpServers;};
})
# ── ~/.pi/agent/settings.json ──────────────────────────────────
{
".pi/agent/settings.json".text = builtins.toJSON piSettings;
}
# ── AGENTS.md — agent descriptions and specialist listing ──────
(mkIf (cfg.agentsInput != null) {
".pi/agent/AGENTS.md".source = "${rendered}/AGENTS.md";
})
# ── SYSTEM.md — primary agent's system prompt ──────────────────
(mkIf (cfg.agentsInput != null) {
".pi/agent/SYSTEM.md".source = "${rendered}/SYSTEM.md";
})
# ── Agents — pi-subagents .md files ────────────────────────────
agentFiles
# ── Skills symlinked from AGENTS repo + external skills ────────
(mkIf (cfg.agentsInput != null) {
".pi/agent/skills".source = cfg.agentsInput.lib.mkOpencodeSkills {
inherit pkgs;
customSkills = "${cfg.agentsInput}/skills";
externalSkills = shared.mapExternalSkills cfg.externalSkills;
};
})
];
});
}

View File

@@ -0,0 +1,75 @@
# Shared option definitions for agent modules.
# Prevents copy-pasting the externalSkills submodule across opencode/claude-code/pi.
{lib}: let
inherit (lib) mkOption mkEnableOption types literalExpression;
in {
# Common agentsInput option used by all agent modules.
mkAgentsInputOption = description: mkOption {
type = types.nullOr types.anything;
default = null;
inherit description;
};
# Common modelOverrides option.
mkModelOverridesOption = mkOption {
type = types.attrsOf types.str;
default = {};
description = ''
Per-agent model overrides. Maps agent slug to model string.
Example: { chiron = "anthropic/claude-sonnet-4"; }
'';
example = literalExpression ''
{
chiron = "anthropic/claude-sonnet-4";
"chiron-forge" = "anthropic/claude-sonnet-4";
}
'';
};
# External skills submodule — used by opencode, claude-code, and pi modules.
externalSkillsOption = mkOption {
type = types.listOf (types.submodule {
options = {
src = mkOption {
type = types.anything;
description = "Flake input pointing to a skills repository root.";
};
skillsDir = mkOption {
type = types.str;
default = "skills";
description = ''
Subdirectory inside src that contains skill folders.
'';
};
selectSkills = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
description = ''
List of skill names to cherry-pick from this source.
null means include every skill found in skillsDir.
'';
};
};
});
default = [];
description = ''
External skill sources passed to mkOpencodeSkills.
Each entry maps directly to an element of the externalSkills
list accepted by the AGENTS flake's lib.mkOpencodeSkills.
'';
example = literalExpression ''
[
{ src = inputs.skills-anthropic; selectSkills = [ "claude-api" ]; }
{ src = inputs.basecamp; }
]
'';
};
# Helper to map externalSkills from module config to mkOpencodeSkills format.
mapExternalSkills = cfgEntries:
map (
entry:
{inherit (entry) src skillsDir;}
// lib.optionalAttrs (entry.selectSkills != null) {inherit (entry) selectSkills;}
) cfgEntries;
}

View File

@@ -1,70 +0,0 @@
{inputs, ...}: {
# This one brings our custom packages from the 'pkgs' directory
additions = final: prev:
(import ../pkgs {pkgs = final;})
# // (inputs.hyprpanel.overlay final prev)
// {rose-pine-hyprcursor = inputs.rose-pine-hyprcursor.packages.${prev.stdenv.hostPlatform.system}.default;};
# This one contains whatever you want to overlay
# You can change versions, add patches, set compilation flags, anything really.
# https://nixos.wiki/wiki/Overlays
modifications = final: prev:
# Import all package modifications from mods directory
(import ./mods/default.nix {inherit prev;})
// {
# Direct configuration overrides
brave = prev.brave.override {
commandLineArgs = "--password-store=gnome-libsecret";
};
# nodejs_24 = inputs.nixpkgs-stable.legacyPackages.${prev.system}.nodejs_24;
# paperless-ngx = inputs.nixpkgs-45570c2.legacyPackages.${prev.system}.paperless-ngx;
# anytype-heart = inputs.nixpkgs-9e58ed7.legacyPackages.${prev.system}.anytype-heart;
# trezord = inputs.nixpkgs-2744d98.legacyPackages.${prev.system}.trezord;
# mesa = inputs.nixpkgs-master.legacyPackages.${prev.system}.mesa;
# hyprpanel = inputs.hyprpanel.packages.${prev.system}.default.overrideAttrs (prev: {
# version = "latest"; # or whatever version you want
# src = final.fetchFromGitHub {
# owner = "Jas-SinghFSU";
# repo = "HyprPanel";
# rev = "master"; # or a specific commit hash
# hash = "sha256-l623fIVhVCU/ylbBmohAtQNbK0YrWlEny0sC/vBJ+dU=";
# };
# });
};
temp-packages = final: _prev: {
temp = import inputs.nixpkgs-9e9486b {
system = final.stdenv.hostPlatform.system;
config.allowUnfree = true;
};
};
stable-packages = final: _prev: {
stable = import inputs.nixpkgs-stable {
system = final.stdenv.hostPlatform.system;
config.allowUnfree = true;
};
};
pinned-packages = final: _prev: {
pinned = import inputs.nixpkgs-9472de4 {
system = final.stdenv.hostPlatform.system;
config.allowUnfree = true;
};
};
locked-packages = final: _prev: {
locked = import inputs.nixpkgs-locked {
system = final.stdenv.hostPlatform.system;
config.allowUnfree = true;
};
};
master-packages = final: _prev: {
master = import inputs.nixpkgs-master {
system = final.stdenv.hostPlatform.system;
config.allowUnfree = true;
};
};
}

View File

@@ -1,16 +0,0 @@
{prev}:
prev.beads.overrideAttrs (oldAttrs: rec {
version = "0.47.1";
src = prev.fetchFromGitHub {
owner = "steveyegge";
repo = "beads";
tag = "v${version}";
hash = "sha256-DwIR/r1TJnpVd/CT1E2OTkAjU7k9/KHbcVwg5zziFVg=";
};
vendorHash = "sha256-pY5m5ODRgqghyELRwwxOr+xlW41gtJWLXaW53GlLaFw=";
# Tests require git worktree operations that fail in Nix sandbox
doCheck = false;
})

View File

@@ -2,9 +2,6 @@
# Package modifications # Package modifications
# This overlay contains package overrides and modifications # This overlay contains package overrides and modifications
# n8n = import ./n8n.nix {inherit prev;};
# beads = import ./beads.nix {inherit prev;};
# Add more modifications here as needed # Add more modifications here as needed
# example-package = prev.example-package.override { ... }; # example-package = prev.example-package.override { ... };
} }

View File

@@ -1,18 +0,0 @@
{prev}:
prev.n8n.overrideAttrs (oldAttrs: rec {
version = "2.4.1";
src = prev.fetchFromGitHub {
owner = "n8n-io";
repo = "n8n";
rev = "n8n@${version}";
hash = "sha256-EQP9ZI8kt30SUYE1+/UUpxQXpavzKqDu8qE24zsNifg=";
};
pnpmDeps = prev.pnpm_10.fetchDeps {
pname = oldAttrs.pname;
inherit version src;
fetcherVersion = 1;
hash = "sha256-Q30IuFEQD3896Hg0HCLd38YE2i8fJn74JY0o95LKJis=";
};
})

View File

@@ -3,6 +3,7 @@
inputs, inputs,
... ...
}: let }: let
# Used only for flake input pass-throughs (basecamp, openspec, opencode-desktop)
system = pkgs.stdenv.hostPlatform.system; system = pkgs.stdenv.hostPlatform.system;
in { in {
# Custom packages registry # Custom packages registry

View File

@@ -8,10 +8,10 @@
nix-update-script, nix-update-script,
}: let }: let
pname = "eigent"; pname = "eigent";
version = "0.0.89"; version = "0.0.90";
src = fetchurl { src = fetchurl {
url = "https://github.com/eigent-ai/eigent/releases/download/v${version}/Eigent-${version}.AppImage"; url = "https://github.com/eigent-ai/eigent/releases/download/v${version}/Eigent-${version}.AppImage";
hash = "sha256-9KuiFjegfXhCu1W/FCinWX4ae/DsNPudeBcXFfW18Hc="; hash = "sha256-mwCBx+D6mgGqQa8bDuUpo3h49EwFVkwasJwaYc6aXFE=";
}; };
appimageContents = appimageTools.extractType2 {inherit pname version src;}; appimageContents = appimageTools.extractType2 {inherit pname version src;};
in in

View File

@@ -31,7 +31,7 @@
# Upstream is missing outputHashes for git dependencies # Upstream is missing outputHashes for git dependencies
# Also fix stale npm deps hash in upstream node_modules FOD # Also fix stale npm deps hash in upstream node_modules FOD
fixedNodeModules = opencode.node_modules.overrideAttrs { fixedNodeModules = opencode.node_modules.overrideAttrs {
outputHash = "sha256-LRhPPrOKCGUSCEWTpAxPdWKTKVNkg82WrvD25cP3jts="; outputHash = "sha256-285KZ7rZLRoc6XqCZRHc25NE+mmpGh/BVeMpv8aPQtQ=";
}; };
opencode-desktop = rustPlatform.buildRustPackage (finalAttrs: { opencode-desktop = rustPlatform.buildRustPackage (finalAttrs: {

26
tests/lib/agents-test.nix Normal file
View File

@@ -0,0 +1,26 @@
let
lib = import <nixpkgs/lib>;
agentsLib = (import ../../lib {inherit lib;}).agents;
# Test 1: renderForTool throws for unknown tools
testUnknownTool = let
result = builtins.tryEval (
agentsLib.renderForTool {
pkgs = {};
agentsInput = {};
tool = "unknown-tool";
}
);
in
assert result.success == false; {result = "pass";};
# Test 2: loadCanonical extracts loadAgents from input
testLoadCanonical = let
fakeInput = {lib.loadAgents = {test = {description = "test";};};};
result = agentsLib.loadCanonical {agentsInput = fakeInput;};
in
assert result == {test = {description = "test";};}; {result = "pass";};
in {
unknown-tool-throws = testUnknownTool;
load-canonical = testLoadCanonical;
}

View File

@@ -0,0 +1,48 @@
let
lib = import <nixpkgs/lib>;
codingRulesLib = (import ../../lib {inherit lib;}).coding-rules;
# Test 1: instructions are generated correctly with custom rulesDir
testInstructions = let
rules = codingRulesLib.mkCodingRules {
agents = "/tmp/fake-agents";
languages = ["python"];
concerns = ["naming"];
rulesDir = ".coding-rules";
};
in
assert rules.instructions
== [
".coding-rules/concerns/naming.md"
".coding-rules/languages/python.md"
]; {result = "pass";};
# Test 2: default rulesDir is .opencode-rules
testDefaultRulesDir = let
rules = codingRulesLib.mkCodingRules {
agents = "/tmp/fake-agents";
};
hasCorrectPrefix = builtins.all (s: builtins.substring 0 15 s == ".opencode-rules") rules.instructions;
in
assert hasCorrectPrefix == true; {result = "pass";};
# Test 3: backward-compat alias exists
testBackwardCompat = assert codingRulesLib.mkOpencodeRules == codingRulesLib.mkCodingRules; {result = "pass";};
# Test 4: shellHook contains both the symlink command and the config generation
testShellHook = let
rules = codingRulesLib.mkCodingRules {
agents = "/tmp/fake-agents";
};
hook = rules.shellHook;
hasSymlink = builtins.match ".*ln -sfn.*" hook != null;
hasConfigGen = builtins.match ".*coding-rules.json.*" hook != null;
in
assert hasSymlink;
assert hasConfigGen; {result = "pass";};
in {
instructions-correct = testInstructions;
default-rules-dir = testDefaultRulesDir;
backward-compat = testBackwardCompat;
shell-hook = testShellHook;
}

4
tests/lib/default.nix Normal file
View File

@@ -0,0 +1,4 @@
{
coding-rules = import ./coding-rules-test.nix;
agents = import ./agents-test.nix;
}