Commit c8e6a22

HPCesia <me@hpcesia.com>
2026-06-21 10:19:47
Add LLM agents config
1 parent c0e8da6
Changed files (7)
modules
develop
agents
users
hpcesia
modules/develop/agents/skills/grilling/SKILL.md
@@ -0,0 +1,16 @@
+---
+name: grilling
+description: Grill the user relentlessly about a plan or design. Use when the user wants to stress-test a plan before building, or uses any 'grill' trigger phrases.
+---
+
+Interview me relentlessly about every aspect of this plan until we reach a shared understanding.
+Walk down each branch of the design tree, resolving dependencies between decisions one-by-one.
+For each question, provide your recommended answer.
+
+Ask the questions one at a time, waiting for feedback on each question before continuing. Asking
+multiple questions at once is bewildering.
+
+If a *fact* can be found by exploring the codebase, look it up rather than asking me. The
+*decisions*, though, are mine — put each one to me and wait for my answer.
+
+Do not enact the plan until I confirm we have reached a shared understanding.
modules/develop/agents/skills/jujutsu/SKILL.md
@@ -0,0 +1,441 @@
+---
+name: jujutsu
+description: "**REQUIRED** - Always activate FIRST on any git/VCS operations (commit, status, branch, push, etc.), especially when HEAD is detached. If `.jj/` exists -> this is a Jujutsu (jj) repo - raw git commands can corrupt data. Essential git safety instructions inside. DO NOT IGNORE."
+allowed-tools: Bash(jj *)
+---
+
+# Jujutsu (jj) Version Control System
+
+This skill helps you work with Jujutsu, a Git-compatible VCS with mutable commits and automatic rebasing.
+
+**Tested with jj v0.37.0** - Commands may differ in other versions.
+
+## Important: Automated/Agent Environment
+
+When running as an agent:
+
+1. **Always use `--no-pager`** to prevent commands from opening an interactive pager (like `less`), which will hang the agent:
+
+```bash
+# Always use --no-pager on commands that produce output
+jj --no-pager log          # NOT: jj log
+jj --no-pager diff         # NOT: jj diff
+jj --no-pager show <id>    # NOT: jj show <id>
+```
+
+2. **Always use `-m` flags** to provide messages inline rather than relying on editor prompts:
+
+```bash
+# Always use -m to avoid editor prompts
+jj desc -m "message"      # NOT: jj desc
+jj squash -m "message"    # NOT: jj squash (which opens editor)
+```
+
+Editor-based commands will fail in non-interactive environments.
+
+3. **Verify operations with `jj st`** after mutations (`squash`, `abandon`, `rebase`, `restore`) to confirm the operation succeeded.
+
+## Core Concepts
+
+### The Working Copy is a Commit
+
+In jj, your working directory is always a commit (referenced as `@`). Changes are automatically snapshotted when you run any jj command. There is no staging area.
+
+There is no need to run `jj commit`.
+
+### Commits Are Mutable
+
+**CRITICAL**: Unlike git, jj commits can be freely modified after creation. You can update descriptions, squash changes, rebase, and absorb — all without creating new commits. See "Essential Workflow" below for the recommended working pattern.
+
+### Change IDs vs Commit IDs
+
+- **Change ID**: A stable identifier (like `tqpwlqmp`) that persists when a commit is rewritten — prefer these when referencing commits
+- **Commit ID**: A content hash (like `3ccf7581`) that changes when commit content changes
+
+### Revsets
+
+jj uses a revset language to select commits in commands. Common revsets:
+
+- `@` — the working copy commit
+- `@-` — the parent of the working copy
+- `::@` — all ancestors of `@`
+- `@::` — all descendants of `@`
+- `trunk()..@` — commits between trunk and `@` (your branch)
+- `bookmarks()` — all commits with bookmarks
+
+Use revsets with `-r` flags: `jj log -r 'trunk()..@'`
+
+## Essential Workflow
+
+### Starting Work: Describe First, Then Code
+
+**Always create your commit message before writing code:**
+
+Validate that you're on a blank revision with `jj st`. If you are not, you should type:
+
+```bash
+jj new
+```
+
+```bash
+# First, describe what you intend to do
+jj desc -m "Add user authentication to login endpoint"
+
+# Then make your changes - they automatically become part of this commit
+# ... edit files ...
+
+# Check status
+jj st
+```
+
+### Creating Atomic Commits
+
+Each commit should represent ONE logical change. Use this format for commit messages:
+
+```
+Examples:
+- "Add validation to user input forms"
+- "Fix null pointer in payment processor"
+- "Remove deprecated API endpoints"
+- "Update dependencies to latest versions"
+```
+
+### Viewing History
+
+```bash
+# View recent commits
+jj --no-pager log
+
+# View with patches
+jj --no-pager log -p
+
+# View specific commit
+jj --no-pager show <change-id>
+
+# View diff of working copy (use --git for familiar +/- format)
+jj --no-pager diff --git
+```
+
+**IMPORTANT: `jj diff` output format**: The default `jj diff` output uses a side-by-side line number format (e.g. `26   26:`) that looks very different from git's `+`/`-` prefix format. This is **normal and correct** — it is NOT corrupted or showing stale content. However, to avoid confusion, **always use `jj diff --git`** to get standard unified diff format with `+`/`-` lines.
+
+### Moving Between Commits
+
+```bash
+# Create a new empty commit on top of current
+jj new
+
+# Create new commit with message
+jj new -m "Commit message"
+
+# Edit an existing commit (working copy becomes that commit)
+jj edit <change-id>
+
+# Edit the previous commit
+jj prev -e
+
+# Edit the next commit
+jj next -e
+```
+
+## Refining Commits
+
+### Squashing Changes
+
+Move changes from current commit into its parent:
+
+```bash
+# Squash all changes into parent
+jj squash
+```
+
+**Note**: `jj squash -i` opens an interactive UI and will hang in agent environments. Avoid it.
+
+### Splitting Commits
+
+**Warning**: `jj split` is interactive and will hang in agent environments. To divide a commit, use `jj restore` to move changes out, then create separate commits manually.
+
+### Absorbing Changes
+
+Automatically distribute changes to the commits that last modified those lines:
+
+```bash
+# Absorb working copy changes into appropriate ancestor commits
+jj absorb
+```
+
+### Abandoning Commits
+
+Remove a commit entirely (descendants are rebased to its parent):
+
+```bash
+jj abandon <change-id>
+```
+
+### Undoing Operations
+
+Reverse the last jj operation:
+
+```bash
+jj undo
+```
+
+This reverts the repository to its state before the previous command. Useful for recovering from mistakes like accidental `abandon`, `squash`, or `rebase`.
+
+### Rebasing Commits
+
+Move commits to a different parent:
+
+```bash
+# Rebase current branch onto a destination
+jj rebase -d <destination>
+
+# Rebase a specific revision (without descendants) onto a destination
+jj rebase -r <change-id> -d <destination>
+
+# Rebase a revision and all its descendants
+jj rebase -s <change-id> -d <destination>
+
+# Rebase onto trunk (common: update your branch to latest main)
+jj rebase -d main
+```
+
+### Restoring Files
+
+Discard changes to specific files or restore files from another revision:
+
+```bash
+# Discard all uncommitted changes in working copy (restore from parent)
+jj restore
+
+# Discard changes to specific files
+jj restore path/to/file.txt
+
+# Restore files from a specific revision
+jj restore --from <change-id> path/to/file.txt
+```
+
+## Working with Bookmarks (Branches)
+
+Bookmarks are jj's equivalent to git branches:
+
+```bash
+# Create a bookmark at current commit
+jj bookmark create my-feature -r@
+
+# Move bookmark to a different commit
+jj bookmark move my-feature --to <change-id>
+
+# List bookmarks
+jj --no-pager bookmark list
+
+# Delete a bookmark
+jj bookmark delete my-feature
+```
+
+## Workspaces
+
+A **workspace** is a working copy plus its associated repo. One repo can have multiple workspaces — each with its own working directory and working-copy commit (`@`) — all sharing the same commits, operations, and bookmarks. This is jj's equivalent of `git worktree`.
+
+Useful for running a long build or test in one workspace while editing in another. Workspaces are a rarely-needed feature; consult the [official docs](https://docs.jj-vcs.dev/latest/working-copy/#workspaces) for anything beyond the basics below.
+
+### Common commands
+
+```bash
+# Create a new workspace (defaults: name = basename of path, parent = current @'s parent)
+jj workspace add ../my-tests
+jj workspace add --name tests -r <change-id> ../my-tests   # explicit name and base
+
+# Inspect
+jj --no-pager workspace list
+jj workspace root [--name <ws>]
+
+# Remove (does NOT delete files on disk — rm the directory separately)
+jj workspace forget [<ws>]
+
+# Rename current workspace
+jj workspace rename <new-name>
+```
+
+In `jj log`, each workspace's `@` appears as `<workspace-name>@`.
+
+### Key semantics
+
+- **Isolation by default.** `jj workspace add` gives the new workspace its own fresh empty commit; workspaces don't start out sharing `@`, and on-disk files are never live-mirrored between them.
+- **Propagation at command boundaries.** Each jj command snapshots the current workspace's files and reads the op log, so it sees commits/bookmarks made by other workspaces. There is no filesystem watcher.
+- **Stale working copy.** If another workspace rewrites this workspace's `@` (e.g. via `jj squash`, `rebase`, `abandon`), jj refuses commands here until you run `jj workspace update-stale`. Same recovery path if a command was interrupted mid-update.
+- **Shared `@` is sharp-edged.** `jj edit <id>` lets two workspaces point at the same change without warning. When one mutates it, the other goes stale; if the stale one had un-snapshotted edits, `update-stale` preserves them as a **divergent commit** (same change ID, shown as `xyz??` in `jj log`) that you must resolve. Avoid sharing `@` unless both workspaces are read-only.
+
+### Agent guidance
+
+- Always pass `--no-pager` to `jj workspace list`.
+- Don't `jj edit` a change another workspace already has as its `@` — main cause of accidental divergence.
+- Don't `rm -rf` a workspace directory without also running `jj workspace forget <name>`.
+
+## Git Integration
+
+### Working with Existing Git Repos
+
+```bash
+# Clone a git repository
+jj git clone <url>
+
+# Initialize jj in an existing git repo
+jj git init --colocate
+```
+
+### Fetching Remote Changes
+
+```bash
+# Fetch all branches from the default remote
+jj git fetch
+
+# Fetch from a specific remote
+jj git fetch --remote <remote-name>
+
+# Fetch specific branches
+jj git fetch -b <branch-name>
+```
+
+After fetching, rebase your work onto the updated trunk: `jj rebase -d main`
+
+### Switching Between jj and git (Colocated Repos Only)
+
+**This section only applies to colocated repos** (where both `.jj/` and `.git/` exist). In non-colocated repos, do not use git commands — they will corrupt jj state.
+
+In a colocated repository, you can use both jj and git commands with care:
+
+**Switching to git mode** (e.g., for merge workflows):
+```bash
+# First, ensure your jj working copy is clean
+jj st
+
+# Then checkout a branch with git
+git checkout <branch-name>
+```
+
+**Switching back to jj mode**:
+```bash
+# Use jj edit to resume working with jj
+jj edit <change-id>
+```
+
+**Important notes:**
+- Git may complain about uncommitted changes if jj's working copy differs from the git HEAD
+- ALWAYS ensure your work is committed in jj before switching to git
+- After git operations, jj will detect and incorporate the changes on next command
+
+### Pushing Changes
+
+When the user asks you to push changes:
+
+```bash
+# Push a specific bookmark to the remote
+jj git push -b <bookmark-name>
+
+# Example: push the main bookmark
+jj git push -b main
+```
+
+**Before pushing, ensure:**
+1. Your bookmark points to the correct commit (bookmarks don't auto-advance like git branches)
+2. The commits are refined and atomic
+3. The user has explicitly requested the push
+
+**IMPORTANT**: Unlike git branches, jj bookmarks do not automatically move when you create new commits. You must manually update them before pushing:
+
+```bash
+# Move an existing bookmark to the current commit
+jj bookmark move my-feature --to @
+
+# Then push it
+jj git push -b my-feature
+```
+
+If no bookmark exists for your changes, create one first:
+
+```bash
+# Create a bookmark at the current commit
+jj bookmark create my-feature
+
+# Then push it
+jj git push -b my-feature
+```
+
+## Handling Conflicts
+
+jj allows committing conflicts — you can resolve them later:
+
+```bash
+# View conflicts
+jj st
+```
+
+**Agent conflict resolution**: Do not use `jj resolve` (interactive). Instead, edit the conflicted files directly to remove conflict markers, then run `jj st` to verify resolution.
+
+## Preserving Commit Quality
+
+**IMPORTANT**: Because commits are mutable, always refine them before considering work done:
+
+1. **Review your commit**: `jj --no-pager show @` or `jj --no-pager diff --git`
+2. **Is it atomic?** One logical change per commit
+3. **Is the message clear?** Use imperative verb phrase in sentence case format with no full stop: e.g. "Add login endpoint", "Fix null pointer in payment processor", "Remove deprecated API endpoints"
+4. **Are there unrelated changes?** Use `jj restore` to move changes out, then create separate commits
+5. **Should changes be elsewhere?** Use `jj squash` or `jj absorb`
+
+## AI attribution
+
+Detect before writing any commit.
+
+- **Model**: stated in system prompt as "You are powered by the model named X" — use X. Never hardcode.
+- **Agent**: identity declared in system prompt (e.g. "Your designated identity is Build") — use that. Never use "Claude".
+- **CLI**: check env vars — `OPENCODE=1` → `opencode`; `CLAUDECODE` set → `claude`; neither → omit CLI part.
+
+## Commits
+
+Conventional Commits format by default, prefer to use project's commit style. Subject ≤50 chars. Body only when "why" isn't obvious.
+
+Always append trailer:
+```
+Assisted-by: <agent>:<model> <cli>
+```
+
+Example:
+```
+feat(hooks): add per_dir_hook support for lorem_ipsum
+
+Assisted-by: build:deepseek-v4-pro opencode
+```
+
+## Quick Reference
+
+| Action | Command |
+|--------|---------|
+| Describe commit | `jj desc -m "message"` |
+| View status | `jj st` |
+| View log | `jj --no-pager log` |
+| View diff | `jj --no-pager diff --git` |
+| New commit | `jj new -m "message"` (use `jj st` first; skip if `@` is empty) |
+| Edit commit | `jj edit <id>` |
+| Squash to parent | `jj squash` |
+| Auto-distribute | `jj absorb` |
+| Rebase | `jj rebase -d <destination>` |
+| Abandon commit | `jj abandon <id>` |
+| Undo last operation | `jj undo` |
+| Restore files | `jj restore [paths]` |
+| Create bookmark | `jj bookmark create <name>` |
+| Fetch remote | `jj git fetch` |
+| Push bookmark | `jj git push -b <name>` |
+| Add workspace | `jj workspace add <path>` |
+| List workspaces | `jj --no-pager workspace list` |
+| Forget workspace | `jj workspace forget [name]` |
+| Fix stale working copy | `jj workspace update-stale` |
+
+## Best Practices Summary
+
+1. **Describe first**: Set the commit message before coding
+2. **One change per commit**: Keep commits atomic and focused
+3. **Use change IDs**: They're stable across rewrites
+4. **Refine commits**: Leverage mutability for clean history
+5. **Embrace the workflow**: No staging area, no stashing - just commit
+6. **Use Conventional Commits**: Follow conventional commits format, subject ≤50 chars
+7. **Always attribute**: Append `Assisted-by: <agent>:<model> <cli>` trailer on every commit
modules/develop/agents/default.nix
@@ -0,0 +1,16 @@
+{inputs, ...}: {
+  flake-file.inputs.llm-agents = {
+    url = "github:numtide/llm-agents.nix";
+    inputs.flake-parts.follows = "flake-parts";
+    inputs.nixpkgs.follows = "nixpkgs";
+  };
+
+  den.aspects.develop.agents = {
+    nixos = {
+      nixpkgs.overlays = [inputs.llm-agents.overlays.shared-nixpkgs];
+    };
+    homeManager = {
+      nixpkgs.overlays = [inputs.llm-agents.overlays.shared-nixpkgs];
+    };
+  };
+}
modules/develop/agents/opencode.nix
@@ -0,0 +1,70 @@
+{den, ...}: {
+  den.aspects.develop.agents.opencode = {
+    includes = [den.aspects.develop.agents];
+
+    persistHome = {config, ...}: {
+      directories = [
+        "${config.xdg.configHome}/opencode"
+      ];
+    };
+
+    cacheHome = {config, ...}: {
+      directories = [
+        "${config.xdg.dataHome}/opencode"
+        "${config.xdg.stateHome}/opencode"
+      ];
+    };
+
+    homeManager = {pkgs, ...}: {
+      programs.opencode = {
+        enable = true;
+        package = pkgs.llm-agents.opencode;
+        settings = {
+          permission = {
+            external_directory = {
+              "/nix/store/**" = "allow";
+            };
+            edit = {
+              "*" = "ask";
+              "/nix/store/**" = "deny";
+            };
+            grep = {
+              "/nix/store" = "deny";
+            };
+            webfetch = "ask";
+          };
+          mcp = {
+            deepwiki = {
+              type = "remote";
+              url = "https://mcp.deepwiki.com/mcp";
+            };
+          };
+          formatter = {
+            alejandra = {
+              command = ["alejandra" "$FILE"];
+              extensions = [".nix"];
+            };
+          };
+          plugin = [
+            "opencode-wakatime"
+            "@simonwjackson/opencode-direnv"
+          ];
+          skills = {
+            grilling = ./skills/grilling;
+            jujutsu = ./skills/jujutsu;
+          };
+        };
+      };
+    };
+  };
+
+  den.aspects.develop.opencode.themes = {
+    catppuccin = {
+      homeManager = {
+        programs.opencode = {
+          tui.theme = "catppuccin";
+        };
+      };
+    };
+  };
+}
modules/users/hpcesia/default.nix
@@ -10,6 +10,7 @@
       ++ (den.lib.policy.when
         ({host, ...}: host.hasAspect den.aspects.roles.develop)
         (with den.aspects; [
+          develop.agents.opencode
           develop.editor.helix
           develop.editor.helix.lang.nix
           develop.jujutsu
flake.lock
@@ -16,6 +16,64 @@
         "type": "github"
       }
     },
+    "blueprint": {
+      "inputs": {
+        "nixpkgs": [
+          "llm-agents",
+          "nixpkgs"
+        ],
+        "systems": [
+          "llm-agents",
+          "systems"
+        ]
+      },
+      "locked": {
+        "lastModified": 1776249299,
+        "narHash": "sha256-Dt9t1TGRmJFc0xVYhttNBD6QsAgHOHCArqGa0AyjrJY=",
+        "owner": "numtide",
+        "repo": "blueprint",
+        "rev": "56131e8628f173d24a27f6d27c0215eff57e40dd",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "blueprint",
+        "type": "github"
+      }
+    },
+    "bun2nix": {
+      "inputs": {
+        "flake-parts": [
+          "llm-agents",
+          "flake-parts"
+        ],
+        "nixpkgs": [
+          "llm-agents",
+          "nixpkgs"
+        ],
+        "systems": [
+          "llm-agents",
+          "systems"
+        ],
+        "treefmt-nix": [
+          "llm-agents",
+          "treefmt-nix"
+        ]
+      },
+      "locked": {
+        "lastModified": 1782772816,
+        "narHash": "sha256-s9BuFv0mRuZx9C1MF8qPHRdcAK14ONi0A5m6E2wqOoM=",
+        "owner": "nix-community",
+        "repo": "bun2nix",
+        "rev": "5a39d717029e94163ac223aee8d5c9946cafed1c",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "repo": "bun2nix",
+        "type": "github"
+      }
+    },
     "crane": {
       "locked": {
         "lastModified": 1780532242,
@@ -214,6 +272,33 @@
         "type": "github"
       }
     },
+    "llm-agents": {
+      "inputs": {
+        "blueprint": "blueprint",
+        "bun2nix": "bun2nix",
+        "flake-parts": [
+          "flake-parts"
+        ],
+        "nixpkgs": [
+          "nixpkgs"
+        ],
+        "systems": "systems",
+        "treefmt-nix": "treefmt-nix"
+      },
+      "locked": {
+        "lastModified": 1783172456,
+        "narHash": "sha256-+U2IkhDolieLOgQThpVpRA0SCdMhJ/RxILORByrnxgc=",
+        "owner": "numtide",
+        "repo": "llm-agents.nix",
+        "rev": "a18193ae823a56f4e72bc3b5a02f549887477fe7",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "llm-agents.nix",
+        "type": "github"
+      }
+    },
     "nix-flatpak": {
       "locked": {
         "lastModified": 1780908363,
@@ -342,6 +427,7 @@
         "home-manager": "home-manager",
         "impermanence": "impermanence",
         "import-tree": "import-tree",
+        "llm-agents": "llm-agents",
         "nix-flatpak": "nix-flatpak",
         "nixpkgs": "nixpkgs",
         "noctalia": "noctalia",
@@ -350,6 +436,42 @@
         "vaultix": "vaultix"
       }
     },
+    "systems": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    },
+    "treefmt-nix": {
+      "inputs": {
+        "nixpkgs": [
+          "llm-agents",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1780220602,
+        "narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=",
+        "owner": "numtide",
+        "repo": "treefmt-nix",
+        "rev": "db947814a175b7ca6ded66e21383d938df01c227",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "treefmt-nix",
+        "type": "github"
+      }
+    },
     "vaultix": {
       "inputs": {
         "advisory-db": "advisory-db",
flake.nix
@@ -33,6 +33,13 @@
       };
     };
     import-tree.url = "github:denful/import-tree";
+    llm-agents = {
+      url = "github:numtide/llm-agents.nix";
+      inputs = {
+        flake-parts.follows = "flake-parts";
+        nixpkgs.follows = "nixpkgs";
+      };
+    };
     nix-flatpak.url = "github:gmodena/nix-flatpak";
     nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
     noctalia = {