diff --git a/.gitignore b/.gitignore index 136e23d..73ce6bf 100644 --- a/.gitignore +++ b/.gitignore @@ -43,5 +43,5 @@ flake.lock.bak .sidecar-start.sh .sidecar-base .td-root -.pi-lens .cache +.pi* diff --git a/.pi-lens/cache/jscpd.json b/.pi-lens/cache/jscpd.json deleted file mode 100644 index ee25c61..0000000 --- a/.pi-lens/cache/jscpd.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "success": true, - "clones": [], - "duplicatedLines": 0, - "totalLines": 0, - "percentage": 0 -} \ No newline at end of file diff --git a/.pi-lens/cache/jscpd.meta.json b/.pi-lens/cache/jscpd.meta.json deleted file mode 100644 index 555945b..0000000 --- a/.pi-lens/cache/jscpd.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "timestamp": "2026-04-11T04:17:20.531Z" -} \ No newline at end of file diff --git a/.pi-lens/cache/knip.json b/.pi-lens/cache/knip.json deleted file mode 100644 index a4147c6..0000000 --- a/.pi-lens/cache/knip.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "success": false, - "issues": [], - "unusedExports": [], - "unusedFiles": [], - "unusedDeps": [], - "unlistedDeps": [], - "summary": "Failed to parse output" -} \ No newline at end of file diff --git a/.pi-lens/cache/knip.meta.json b/.pi-lens/cache/knip.meta.json deleted file mode 100644 index 2eda7c6..0000000 --- a/.pi-lens/cache/knip.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "timestamp": "2026-04-11T04:17:21.374Z" -} \ No newline at end of file diff --git a/.pi-lens/cache/session-start-guidance.json b/.pi-lens/cache/session-start-guidance.json deleted file mode 100644 index ec747fa..0000000 --- a/.pi-lens/cache/session-start-guidance.json +++ /dev/null @@ -1 +0,0 @@ -null \ No newline at end of file diff --git a/.pi-lens/cache/session-start-guidance.meta.json b/.pi-lens/cache/session-start-guidance.meta.json deleted file mode 100644 index 1f1b98c..0000000 --- a/.pi-lens/cache/session-start-guidance.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "timestamp": "2026-04-19T15:52:39.989Z" -} \ No newline at end of file diff --git a/.pi-lens/cache/todo-baseline.json b/.pi-lens/cache/todo-baseline.json deleted file mode 100644 index fc69ce2..0000000 --- a/.pi-lens/cache/todo-baseline.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "items": [] -} \ No newline at end of file diff --git a/.pi-lens/cache/todo-baseline.meta.json b/.pi-lens/cache/todo-baseline.meta.json deleted file mode 100644 index 8515b90..0000000 --- a/.pi-lens/cache/todo-baseline.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "timestamp": "2026-04-19T15:42:10.963Z" -} \ No newline at end of file diff --git a/.pi-lens/turn-state.json b/.pi-lens/turn-state.json deleted file mode 100644 index dd32709..0000000 --- a/.pi-lens/turn-state.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "files": {}, - "turnCycles": 0, - "maxCycles": 3, - "lastUpdated": "2026-04-11T04:17:22.397Z" -} \ No newline at end of file diff --git a/.td-root b/.td-root deleted file mode 100644 index a81e2a7..0000000 --- a/.td-root +++ /dev/null @@ -1 +0,0 @@ -/home/sascha.koenig/p/NIX/nixpkgs diff --git a/flake.lock b/flake.lock index e39e795..458b6ac 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,21 @@ { "nodes": { + "agents": { + "flake": false, + "locked": { + "lastModified": 1776092721, + "narHash": "sha256-avV4Snqp0K57I9s8D61+GHlg9DYZFSIvjaS4d4RYpG8=", + "ref": "refs/heads/master", + "rev": "0ad41acb03eee0e22cba611b2171a3d3ee30cb10", + "revCount": 72, + "type": "git", + "url": "https://code.m3ta.dev/m3tam3re/AGENTS" + }, + "original": { + "type": "git", + "url": "https://code.m3ta.dev/m3tam3re/AGENTS" + } + }, "basecamp": { "inputs": { "nixpkgs": [ @@ -96,6 +112,7 @@ }, "root": { "inputs": { + "agents": "agents", "basecamp": "basecamp", "nixpkgs": "nixpkgs", "nixpkgs-master": "nixpkgs-master", diff --git a/flake.nix b/flake.nix index 7af484e..c6b39ad 100644 --- a/flake.nix +++ b/flake.nix @@ -21,6 +21,12 @@ url = "github:Fission-AI/OpenSpec"; inputs.nixpkgs.follows = "nixpkgs"; }; + + # Agent definitions and coding rules + agents = { + url = "git+https://code.m3ta.dev/m3tam3re/AGENTS"; + flake = false; + }; }; outputs = { @@ -81,11 +87,11 @@ # Development shells for various programming environments # Usage: nix develop .# - # Available shells: default, python, devops, opencode + # Available shells: default, python, devops, coding devShells = forAllSystems (system: let pkgs = pkgsFor system; in - import ./shells {inherit pkgs inputs;}); + import ./shells {inherit pkgs inputs; agents = inputs.agents;}); # Formatter for 'nix fmt' formatter = forAllSystems (system: (pkgsFor system).alejandra); diff --git a/lib/agents.nix b/lib/agents.nix index 147e9af..abe9252 100644 --- a/lib/agents.nix +++ b/lib/agents.nix @@ -223,7 +223,10 @@ canonical, modelOverrides ? {}, primaryAgent ? null, + codingRules ? null, }: let + # Import coding-rules lib for concatRulesMd when codingRules is provided + codingRulesLib = (import ./coding-rules.nix {inherit lib;}); # Find the primary agent (there should be exactly one). primaryAgents = lib.filterAttrs (_: a: a.mode == "primary") canonical; primaryNames = lib.attrNames primaryAgents; @@ -306,6 +309,17 @@ "- **" + dn + "**: " + agent.description; in lib.mapAttrsToList mkEntry subagents; + # ── Coding rules section (optional) ──────────────────────── + # When codingRules is provided, append selected rules to AGENTS.md. + # codingRules attrset: { agents, languages, concerns, frameworks } + codingRulesSection = + if codingRules != null + then let + section = codingRulesLib.mkRulesMdSection codingRules; + in + if section != "" then "\n" + section else "" + else ""; + agentsMd = "# Agent Instructions\n" + "\n" @@ -320,7 +334,8 @@ if subagents == {} then "" else "## Available Specialists\n\n" + lib.concatStringsSep "\n" specialistEntries + "\n" - ); + ) + + codingRulesSection; agentsMdFile = pkgs.writeText "AGENTS.md" agentsMd; systemMdFile = pkgs.writeText "SYSTEM.md" primary.systemPrompt; @@ -346,6 +361,7 @@ agentsInput, tool, modelOverrides ? {}, + codingRules ? null, }: let canonical = agentsInput.lib.loadAgents; in @@ -362,7 +378,7 @@ else if tool == "pi" then agentsLib.renderForPi { - inherit pkgs canonical modelOverrides; + inherit pkgs canonical modelOverrides codingRules; } else throw "lib.agents.renderForTool: unknown tool '${tool}'. Must be opencode, claude-code, or pi."; @@ -386,9 +402,10 @@ agentsInput, tool, modelOverrides ? {}, + codingRules ? null, }: let rendered = agentsLib.renderForTool { - inherit pkgs agentsInput tool modelOverrides; + inherit pkgs agentsInput tool modelOverrides codingRules; }; in if tool == "opencode" diff --git a/lib/coding-rules.nix b/lib/coding-rules.nix index 3220029..a878576 100644 --- a/lib/coding-rules.nix +++ b/lib/coding-rules.nix @@ -27,6 +27,7 @@ # The shellHook creates: # - A `.opencode-rules/` symlink pointing to the AGENTS repository rules directory # - A `coding-rules.json` file with a $schema reference and instructions list +# - (Optional) Appends coding rules to `AGENTS.md` for Pi agent discovery # # The instructions list contains paths relative to the project root, all prefixed # with `.opencode-rules/`, making them portable across different project locations. @@ -43,6 +44,9 @@ # (e.g., [ "react" "fastapi" "django" ]) # extraInstructions: Optional list of additional instruction paths # (for custom rules outside standard locations) + # forPi: Whether to also append rules to AGENTS.md for Pi agent (default: true) + # Pi discovers AGENTS.md files by walking parent dirs + cwd and concatenates them. + # When enabled, a delimited block is appended to (or created in) AGENTS.md. # # Returns: # An attribute set containing: @@ -83,6 +87,7 @@ frameworks ? [], extraInstructions ? [], rulesDir ? ".opencode-rules", + forPi ? false, }: let # Build instructions list by mapping concerns, languages, frameworks to their file paths # All paths are relative to project root via the rulesDir symlink @@ -97,11 +102,46 @@ "$schema" = "https://opencode.ai/config.json"; inherit instructions; }; + + # Pi rules content (concatenated markdown) — only computed when forPi is true + piRulesSection = + if forPi + then mkRulesMdSection {inherit agents languages concerns frameworks;} + else ""; + + # Bash snippet to append rules to AGENTS.md for Pi discovery. + # Uses HTML comment markers for idempotent updates: + # - Removes any existing CODING-RULES block + # - Appends the new block + # - Creates AGENTS.md if it doesn't exist + # Note: Uses plain if-then-else instead of lib.optionalString to avoid + # forcing the `lib` argument (which may come from import ) + # when forPi is false. + piShellHook = + if forPi && piRulesSection != "" + then '' + # Pi agent: append coding rules to AGENTS.md + if [ -f AGENTS.md ]; then + # Remove existing coding-rules block (if any) + sed -i '//,//d' AGENTS.md + # Append new coding-rules block + cat >> AGENTS.md <<'PIRULES_EOF' + ${piRulesSection} + PIRULES_EOF + else + # Create AGENTS.md with just the coding rules + cat > AGENTS.md <<'PIRULES_EOF' + ${piRulesSection} + PIRULES_EOF + fi + '' + else ""; in { inherit instructions; # Shell hook to set up rules in the project # Creates a symlink to the AGENTS rules directory and generates coding-rules.json + # Optionally appends rules to AGENTS.md for Pi agent discovery shellHook = '' # Create/update symlink to AGENTS rules directory ln -sfn ${agents}/rules ${rulesDir} @@ -110,8 +150,73 @@ cat > coding-rules.json <<'RULES_EOF' ${builtins.toJSON rulesConfig} RULES_EOF + + ${piShellHook} ''; }; + # Concatenate selected rule files from the AGENTS repository into a single + # markdown string. Used by Pi (append to AGENTS.md) and could be used by + # other tools that don't support an instructions list. + # + # Args: + # agents: Path to the AGENTS repository (non-flake input) + # languages: Optional list of language-specific rules to include + # concerns: Optional list of concern rules to include + # Default: [ "coding-style" "naming" "documentation" "testing" "git-workflow" "project-structure" ] + # frameworks: Optional list of framework-specific rules to include + # + # Returns: A single concatenated markdown string with all selected rules. + # + # Example: + # concatRulesMd { + # agents = inputs.agents; + # languages = [ "python" ]; + # concerns = [ "coding-style" ]; + # } + # # Returns: "\n# Coding Style\n\n...python rules...\n" + concatRulesMd = { + agents, + languages ? [], + concerns ? [ + "coding-style" + "naming" + "documentation" + "testing" + "git-workflow" + "project-structure" + ], + frameworks ? [], + }: let + rulePaths = + (map (c: {kind = "concerns"; name = c;}) concerns) + ++ (map (l: {kind = "languages"; name = l;}) languages) + ++ (map (f: {kind = "frameworks"; name = f;}) frameworks); + + readRule = rule: builtins.readFile "${agents}/rules/${rule.kind}/${rule.name}.md"; + ruleContents = map readRule rulePaths; + in + lib.concatStringsSep "\n\n" ruleContents; + + # Build a coding rules section suitable for appending to AGENTS.md. + # Wraps concatRulesMd output with a header and HTML comment markers + # for idempotent updates in project-level shellHooks. + # + # Args: Same as concatRulesMd + # + # Returns: A markdown string with start/end markers and a header. + mkRulesMdSection = args: let + content = concatRulesMd args; + in + if builtins.stringLength content == 0 + then "" + else '' + + # Coding Rules + + ${content} + + ''; + in { - inherit mkCodingRules; + inherit mkCodingRules concatRulesMd mkRulesMdSection; } diff --git a/modules/home-manager/coding/agents/pi.nix b/modules/home-manager/coding/agents/pi.nix index 9c5bab6..dd1ee04 100644 --- a/modules/home-manager/coding/agents/pi.nix +++ b/modules/home-manager/coding/agents/pi.nix @@ -44,6 +44,60 @@ in ''; }; + codingRules = mkOption { + type = types.nullOr (types.submodule { + options = { + languages = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Language-specific coding rules to include + (e.g. [ "python" "typescript" "nix" ]). + Rule files are read from the AGENTS repo's rules/languages/ directory. + ''; + }; + + concerns = mkOption { + type = types.listOf types.str; + default = [ + "coding-style" + "naming" + "documentation" + "testing" + "git-workflow" + "project-structure" + ]; + description = '' + Concern rules to include from the AGENTS repo's rules/concerns/ directory. + ''; + }; + + frameworks = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Framework-specific coding rules to include + (e.g. [ "react" "fastapi" ]). + Rule files are read from the AGENTS repo's rules/frameworks/ directory. + ''; + }; + }; + }); + default = null; + description = '' + Coding rules to inject into ~/.pi/agent/AGENTS.md. + Rules are read from the AGENTS repository and appended as markdown sections. + Requires agentsInput to be set. + ''; + example = literalExpression '' + { + languages = [ "python" "typescript" ]; + concerns = [ "coding-style" "testing" ]; + frameworks = [ "fastapi" ]; + } + ''; + }; + settings = mkOption { type = types.submodule { freeformType = types.attrsOf types.anything; @@ -166,6 +220,12 @@ in piSettings = filterNulls cfg.settings; + # Coding rules config for renderForPi (only when both agentsInput and codingRules are set) + piCodingRules = + if cfg.agentsInput != null && cfg.codingRules != null + then cfg.codingRules // { agents = cfg.agentsInput; } + else null; + # Rendered agents (only computed when agentsInput is set) rendered = if cfg.agentsInput != null @@ -175,6 +235,7 @@ in canonical = cfg.agentsInput.lib.loadAgents; modelOverrides = cfg.modelOverrides; primaryAgent = cfg.primaryAgent; + codingRules = piCodingRules; } else null; diff --git a/shells/coding.nix b/shells/coding.nix new file mode 100644 index 0000000..b6b39c2 --- /dev/null +++ b/shells/coding.nix @@ -0,0 +1,111 @@ +# AI coding agent development environment with coding rules +# Sets up coding rules for OpenCode and Pi, plus useful companion tools. +# Usage: nix develop .#coding +# +# To enable coding rules, add the agents input to your flake: +# agents = { +# url = "git+https://code.m3ta.dev/m3tam3re/AGENTS"; +# flake = false; +# }; +{ + pkgs, + lib ? pkgs.lib, + inputs ? null, + agents ? null, +}: let + # Import the coding-rules library + m3taLib = import ../lib {lib = pkgs.lib;}; + + # Import custom packages + customPackages = import ../pkgs {inherit pkgs inputs;}; + + # Create rules configuration only if agents input is provided + rulesConfig = lib.optionalAttrs (agents != null) { + rules = m3taLib.coding-rules.mkCodingRules { + inherit agents; + + # Languages relevant to this repository + languages = ["nix" "python" "shell"]; + + # Frameworks used in this repo + frameworks = ["n8n"]; + + # Standard concerns for development + concerns = [ + "coding-style" + "naming" + "documentation" + "testing" + "git-workflow" + "project-structure" + ]; + + # Also append rules to AGENTS.md for Pi agent discovery + forPi = true; + }; + }; +in + pkgs.mkShell { + name = "coding"; + + # Development tools + buildInputs = with pkgs; + [ + # Task management for AI coding sessions + customPackages.td + + # Companion tool for CLI agents (diffs, file trees, task management) + customPackages.sidecar + + # Code analysis tools + customPackages.code2prompt + + # Nix development tools (for this repo) + nil + alejandra + statix + deadnix + ]; + + shellHook = '' + echo "🤖 AI Coding Environment" + echo "" + + ${ + if (agents != null) + then '' + # Set up coding rules for OpenCode + Pi + ${rulesConfig.rules.shellHook} + + echo "✅ Coding rules configured (OpenCode + Pi)" + echo " Languages: nix, python, shell" + echo " Frameworks: n8n" + echo " Concerns: coding-style, naming, documentation, testing, git-workflow, project-structure" + '' + else '' + echo "⚠️ Coding rules not configured" + echo "" + echo "To enable, add the agents input to your flake.nix:" + echo "" + echo " agents = {" + echo " url = \"git+https://code.m3ta.dev/m3tam3re/AGENTS\";" + echo " flake = false;" + echo " };" + '' + } + + echo "" + echo "Available tools:" + echo " opencode - AI coding agent" + echo " td usage --new-session - View current tasks" + echo " sidecar - Companion tool (diffs, file trees, tasks)" + echo " code2prompt - Convert code to prompts" + echo "" + echo "Nix development tools:" + echo " nix flake check - Check flake validity" + echo " nix fmt . - Format Nix files" + echo " statix check . - Lint Nix files" + echo " deadnix . - Find dead code" + echo "" + ''; + } diff --git a/shells/default.nix b/shells/default.nix index b1dfdb5..e121e36 100644 --- a/shells/default.nix +++ b/shells/default.nix @@ -4,6 +4,7 @@ { pkgs, inputs, + agents ? null, }: { # Default shell for working on this repository default = pkgs.mkShell { @@ -32,5 +33,5 @@ # Import all individual shell environments python = import ./python.nix {inherit pkgs inputs;}; devops = import ./devops.nix {inherit pkgs inputs;}; - opencode = import ./opencode.nix {inherit pkgs inputs;}; + coding = import ./coding.nix {inherit pkgs inputs agents;}; } diff --git a/shells/opencode.nix b/shells/opencode.nix deleted file mode 100644 index 42f4721..0000000 --- a/shells/opencode.nix +++ /dev/null @@ -1,155 +0,0 @@ -# OpenCode development environment with AI coding rules -# This shell demonstrates the mkCodingRules library provided by this repository -# Usage: nix develop .#opencode -# -# To enable OpenCode rules, add the agents input to your flake: -# agents = { -# url = "git+https://code.m3ta.dev/m3tam3re/AGENTS"; -# flake = false; -# }; -{ - pkgs, - lib ? pkgs.lib, - inputs ? null, - agents ? null, -}: let - # Import the coding-rules library - m3taLib = import ../lib {lib = pkgs.lib;}; - - # Import custom packages - customPackages = import ../pkgs {inherit pkgs inputs;}; - - # Create rules configuration only if agents input is provided - # This demonstrates how to use mkCodingRules in a real project - rulesConfig = lib.optionalAttrs (agents != null) { - rules = m3taLib.coding-rules.mkCodingRules { - # Pass the AGENTS repository path - inherit agents; - - # Languages relevant to this repository - languages = ["python" "typescript" "nix"]; - - # Frameworks used in this repo - frameworks = ["n8n"]; - - # Standard concerns for development - concerns = [ - "coding-style" - "naming" - "documentation" - "testing" - "git-workflow" - "project-structure" - ]; - }; - }; -in - pkgs.mkShell { - name = "opencode-dev"; - - # Development tools - buildInputs = with pkgs; - [ - # OpenCode AI coding agent (if inputs are available) - ] - ++ lib.optionals (inputs != null) - [inputs.opencode.packages.${pkgs.stdenv.hostPlatform.system}.opencode] - ++ [ - # Task management for AI coding sessions - customPackages.td - - # Companion tool for CLI agents (diffs, file trees, task management) - customPackages.sidecar - - # Code analysis tools - customPackages.code2prompt - - # Nix development tools (for this repo) - nil - alejandra - statix - deadnix - ]; - - # Shell hook that sets up OpenCode rules - shellHook = '' - echo "🤖 OpenCode Development Environment" - echo "" - echo "This environment demonstrates the mkCodingRules library" - echo "provided by the m3ta-nixpkgs repository." - echo "" - - ${ - if (agents != null) - then '' - # Execute the OpenCode rules shellHook - ${rulesConfig.rules.shellHook} - - echo "✅ OpenCode rules configured!" - '' - else '' - echo "⚠️ OpenCode rules not configured" - echo "" - echo "To enable OpenCode rules, add the agents input to your flake.nix:" - echo "" - echo " inputs = {" - echo " m3ta-nixpkgs.url = \"git+https://code.m3ta.dev/m3tam3re/nixpkgs\";" - echo " agents = {" - echo " url = \"git+https://code.m3ta.dev/m3tam3re/AGENTS\";" - echo " flake = false;" - echo " };" - echo " };" - echo "" - echo "Then pass agents to the shell:" - echo " opencode = import ./opencode.nix { inherit pkgs inputs agents; };" - '' - } - - echo "" - echo "Available tools:" - echo " opencode - AI coding agent" - echo " td usage --new-session - View current tasks" - echo " sidecar - Companion tool (diffs, file trees, tasks)" - echo " code2prompt - Convert code to prompts" - echo "" - echo "Nix development tools:" - echo " nix flake check - Check flake validity" - echo " nix fmt . - Format Nix files" - echo " statix check . - Lint Nix files" - echo " deadnix . - Find dead code" - echo "" - ${ - if (agents == null) - then '' - echo "💡 Using mkCodingRules in your project:" - echo "" - echo "Add to your flake.nix:" - echo " inputs = {" - echo " m3ta-nixpkgs.url = \"git+https://code.m3ta.dev/m3tam3re/nixpkgs\";" - echo " agents = {" - echo " url = \"git+https://code.m3ta.dev/m3tam3re/AGENTS\";" - echo " flake = false;" - echo " };" - echo " };" - echo "" - echo " outputs = {self, nixpkgs, m3ta-nixpkgs, agents, ...}:" - echo " let" - echo " system = \"x86_64-linux\";" - echo " pkgs = nixpkgs.legacyPackages.''${system};" - echo " m3taLib = m3ta-nixpkgs.lib.''${system};" - echo " rules = m3taLib.coding-rules.mkCodingRules { - echo " inherit agents;" - echo " languages = [\"python\" \"typescript\"];" - echo " frameworks = [\"n8n\"];" - echo " };" - echo " in {" - echo " devShells.''${system}.default = pkgs.mkShell {" - echo " shellHook = rules.shellHook;" - echo " };" - echo " };" - '' - else "" - } - echo "" - ''; - } diff --git a/tests/lib/agents-test.nix b/tests/lib/agents-test.nix index 9fe2bed..913a87f 100644 --- a/tests/lib/agents-test.nix +++ b/tests/lib/agents-test.nix @@ -20,7 +20,67 @@ let result = agentsLib.loadCanonical {agentsInput = fakeInput;}; in assert result == {test = {description = "test";};}; {result = "pass";}; + + # Test 3: renderForPi accepts codingRules parameter without error (null case) + # Verifies that passing codingRules = null produces the same result as omitting it. + # Uses a minimal fake canonical set instead of a real agents repo. + testPiNullCodingRules = let + pkgs = import {}; + canonical = { + chiron = { + mode = "primary"; + description = "Test primary agent"; + display_name = "Chiron"; + systemPrompt = "You are a test agent."; + permissions = {}; + }; + }; + result = agentsLib.renderForPi { + inherit pkgs canonical; + codingRules = null; + }; + agentsMd = builtins.readFile "${result}/AGENTS.md"; + hasMarkers = builtins.match ".*CODING-RULES:START.*" agentsMd != null; + in + assert hasMarkers == false; {result = "pass";}; + + # Test 4: renderForPi with codingRules includes rules in AGENTS.md + # Uses the real AGENTS repo to read rule files (requires --impure or local path) + testPiWithCodingRules = let + agentsPath = /home/sascha.koenig/p/AI/AGENTS; + pkgs = import {}; + canonical = { + chiron = { + mode = "primary"; + description = "Test primary agent"; + display_name = "Chiron"; + systemPrompt = "You are a test agent."; + permissions = {}; + }; + }; + result = agentsLib.renderForPi { + inherit pkgs canonical; + codingRules = { + agents = agentsPath; + concerns = ["coding-style"]; + languages = []; + frameworks = []; + }; + }; + agentsMd = builtins.readFile "${result}/AGENTS.md"; + hasStartMarker = builtins.match ".*CODING-RULES:START.*" agentsMd != null; + hasEndMarker = builtins.match ".*CODING-RULES:END.*" agentsMd != null; + hasCodingStyle = builtins.match ".*Coding Style.*" agentsMd != null; + # Also verify agent descriptions are still present + hasAgentInstructions = builtins.match ".*Agent Instructions.*" agentsMd != null; + in + assert hasStartMarker == true; + assert hasEndMarker == true; + assert hasCodingStyle == true; + assert hasAgentInstructions == true; {result = "pass";}; in { unknown-tool-throws = testUnknownTool; load-canonical = testLoadCanonical; + pi-null-coding-rules = testPiNullCodingRules; + pi-with-coding-rules = testPiWithCodingRules; } diff --git a/tests/lib/coding-rules-test.nix b/tests/lib/coding-rules-test.nix index f5f4bb7..fdf7d8a 100644 --- a/tests/lib/coding-rules-test.nix +++ b/tests/lib/coding-rules-test.nix @@ -37,8 +37,74 @@ let in assert hasSymlink; assert hasConfigGen; {result = "pass";}; + + # Test 4: forPi=false does not include AGENTS.md logic in shellHook + testForPiDisabled = let + rules = codingRulesLib.mkCodingRules { + agents = "/tmp/fake-agents"; + forPi = false; + }; + hook = rules.shellHook; + hasPiBlock = builtins.match ".*CODING-RULES:START.*" hook != null; + in + assert hasPiBlock == false; {result = "pass";}; + + # Test 5: forPi=true adds CODING-RULES markers to shellHook (when agents path has rules) + # Note: This test uses the real AGENTS repo at /home/sascha.koenig/p/AI/AGENTS + # It is only run when the path exists. + testForPiEnabled = let + agentsPath = /home/sascha.koenig/p/AI/AGENTS; + rules = codingRulesLib.mkCodingRules { + agents = agentsPath; + forPi = true; + concerns = ["coding-style"]; + languages = []; + frameworks = []; + }; + hook = rules.shellHook; + hasPiBlock = builtins.match ".*CODING-RULES:START.*" hook != null; + hasCodingStyle = builtins.match ".*Coding Style.*" hook != null; + in + assert hasPiBlock == true; + assert hasCodingStyle == true; {result = "pass";}; + + # Test 6: concatRulesMd produces concatenated markdown (with real agents path) + testConcatRulesMd = let + agentsPath = /home/sascha.koenig/p/AI/AGENTS; + md = codingRulesLib.concatRulesMd { + agents = agentsPath; + concerns = ["coding-style"]; + languages = []; + frameworks = []; + }; + hasHeader = builtins.match ".*Coding Style.*" md != null; + hasCritical = builtins.match ".*Critical Rules.*" md != null; + in + assert hasHeader == true; + assert hasCritical == true; {result = "pass";}; + + # Test 7: mkRulesMdSection wraps content with markers + testRulesMdSection = let + agentsPath = /home/sascha.koenig/p/AI/AGENTS; + section = codingRulesLib.mkRulesMdSection { + agents = agentsPath; + concerns = ["coding-style"]; + languages = []; + frameworks = []; + }; + hasStartMarker = builtins.match ".*CODING-RULES:START.*" section != null; + hasEndMarker = builtins.match ".*CODING-RULES:END.*" section != null; + hasHeader = builtins.match ".*# Coding Rules.*" section != null; + in + assert hasStartMarker == true; + assert hasEndMarker == true; + assert hasHeader == true; {result = "pass";}; in { instructions-correct = testInstructions; default-rules-dir = testDefaultRulesDir; shell-hook = testShellHook; + forpi-disabled = testForPiDisabled; + forpi-enabled = testForPiEnabled; + concat-rules-md = testConcatRulesMd; + rules-md-section = testRulesMdSection; }