mirror of
https://github.com/srid/nixos-config.git
synced 2026-05-06 22:26:12 +08:00
Upstreamed
This commit is contained in:
parent
19e85a553e
commit
ce989a6c66
4 changed files with 0 additions and 474 deletions
|
|
@ -1,68 +0,0 @@
|
|||
# Relude Best Practices
|
||||
|
||||
When using relude prelude, follow these HLint recommendations (from https://github.com/kowainik/relude/blob/main/.hlint.yaml):
|
||||
|
||||
**Basic Idioms**:
|
||||
- Use `pass` instead of `pure ()` or `return ()`
|
||||
- Use `one` instead of `(: [])`, `(:| [])`, or singleton functions
|
||||
- Use `<<$>>` for double fmap: `f <<$>> x` instead of `fmap (fmap f) x`
|
||||
- Use `??` (flap) operator: `ff ?? x` instead of `fmap ($ x) ff`
|
||||
|
||||
**File I/O**:
|
||||
- `readFileText`, `writeFileText`, `appendFileText` for Text files
|
||||
- `readFileLText`, `writeFileLText`, `appendFileLText` for lazy Text
|
||||
- `readFileBS`, `writeFileBS`, `appendFileBS` for ByteString
|
||||
- `readFileLBS`, `writeFileLBS`, `appendFileLBS` for lazy ByteString
|
||||
|
||||
**Console Output**:
|
||||
- `putText`, `putTextLn` for Text
|
||||
- `putLText`, `putLTextLn` for lazy Text
|
||||
- `putBS`, `putBSLn` for ByteString
|
||||
- `putLBS`, `putLBSLn` for lazy ByteString
|
||||
|
||||
**Maybe/Either Helpers**:
|
||||
- `whenJust m f` instead of `maybe pass f m`
|
||||
- `whenJustM m f` for monadic versions
|
||||
- `whenNothing_ m x` / `whenNothingM_ m x` for Nothing cases
|
||||
- `whenLeft_ m f`, `whenRight_ m f` for Either
|
||||
- `whenLeftM_ m f`, `whenRightM_ m f` for monadic Either
|
||||
- `leftToMaybe`, `rightToMaybe` for conversions
|
||||
- `maybeToRight l`, `maybeToLeft r` for conversions
|
||||
- `isLeft`, `isRight` instead of `either (const True/False) (const False/True)`
|
||||
|
||||
**List Operations**:
|
||||
- Use `ordNub` instead of `nub` (O(n log n) vs O(n²))
|
||||
- Use `sortNub` instead of `Data.Set.toList . Data.Set.fromList`
|
||||
- Use `sortWith f` instead of `sortBy (comparing f)` for simple cases
|
||||
- Use `viaNonEmpty f x` instead of `fmap f (nonEmpty x)`
|
||||
- Use `asumMap f xs` instead of `asum (map f xs)`
|
||||
- Use `toList` instead of `foldr (:) []`
|
||||
|
||||
**Monadic Operations**:
|
||||
- `andM s` instead of `and <$> sequence s`
|
||||
- `orM s` instead of `or <$> sequence s`
|
||||
- `allM f s` instead of `and <$> mapM f s`
|
||||
- `anyM f s` instead of `or <$> mapM f s`
|
||||
- `guardM f` instead of `f >>= guard`
|
||||
- `infinitely` instead of `forever` (better typed)
|
||||
- `unlessM (not <$> x)` → use `whenM x` instead
|
||||
- `whenM (not <$> x)` → use `unlessM x` instead
|
||||
|
||||
**State/Reader Operations**:
|
||||
- `usingReaderT` instead of `flip runReaderT`
|
||||
- `usingStateT` instead of `flip runStateT`
|
||||
- `evaluatingStateT s st` instead of `fst <$> usingStateT s st`
|
||||
- `executingStateT s st` instead of `snd <$> usingStateT s st`
|
||||
|
||||
**Transformer Lifting**:
|
||||
- `hoistMaybe m` instead of `MaybeT (pure m)`
|
||||
- `hoistEither m` instead of `ExceptT (pure m)`
|
||||
|
||||
**List Pattern Matching**:
|
||||
- `whenNotNull m f` for `case m of [] -> pass; (x:xs) -> f (x :| xs)`
|
||||
- `whenNotNullM m f` for monadic version
|
||||
|
||||
**Text/ByteString Conversions**:
|
||||
- Use relude's `toText`, `toString`, `toLText` instead of pack/unpack
|
||||
- Use relude's `encodeUtf8`, `decodeUtf8` for UTF-8 encoding
|
||||
- `fromStrict`, `toStrict` for lazy/strict conversions
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
---
|
||||
name: haskell
|
||||
description: Haskell coding with relude, ghcid-based workflow, hlint compliance, and strict error handling. Use when working with .hs files, Cabal projects, or ghcid.
|
||||
---
|
||||
|
||||
# Haskell Development
|
||||
|
||||
Expert assistance for Haskell programming.
|
||||
|
||||
## Guidelines
|
||||
|
||||
**CRITICAL - Error Handling in Code**: NEVER write code that silently ignores errors:
|
||||
- Do NOT use `undefined` or `error` as placeholders
|
||||
- Do NOT skip handling error cases in pattern matches
|
||||
- Do NOT ignore `Maybe`/`Either` failure cases
|
||||
- Handle all possible cases explicitly
|
||||
- Use types to make impossible states unrepresentable
|
||||
|
||||
Every error case in generated code must be handled properly.
|
||||
|
||||
**CRITICAL - Compile Status**:
|
||||
- The code MUST compile without errors.
|
||||
- You MUST check `ghcid.txt` after every change and fix any errors reported there.
|
||||
- Do NOT proceed to verification or linting until `ghcid.txt` is clean.
|
||||
|
||||
**CRITICAL - HLint Compliance**:
|
||||
- You MUST check for `.hlint.yaml` in the project root.
|
||||
- If it exists, you MUST run `hlint` on any file you modify.
|
||||
- You MUST fix ALL hlint warnings before considering the task complete.
|
||||
- Do NOT ignore hlint warnings unless explicitly instructed by the user.
|
||||
|
||||
**Code Quality**:
|
||||
- Write type signatures for all top-level definitions
|
||||
- Write total functions (avoid `head`, `tail`)
|
||||
- Prefer pure functions over IO when possible
|
||||
- Use explicit exports in modules
|
||||
- Leverage type system for safety
|
||||
- Favor composition over complex functions
|
||||
- Write Haddock documentation for public APIs
|
||||
|
||||
**Idiomatic Patterns**:
|
||||
- Prefer `Text` over `String`
|
||||
- Use `newtype` wrappers for domain types
|
||||
- Apply smart constructors for validation
|
||||
- Records:
|
||||
- Use RecordDotSyntax & OverloadedRecordDot (add pragma to modules that use the syntax)
|
||||
- Use DisambiguateRecordFields and DuplicateRecordFields for simple field names (add pragma to modules that use the syntax)
|
||||
- Use lenses for record manipulation when appropriate
|
||||
- Use `Applicative` and `Monad` appropriately
|
||||
- Avoid trivial `let` bindings when inlining keeps code simple and readable
|
||||
|
||||
**Working with Aeson**:
|
||||
- NEVER construct aeson objects by hand
|
||||
- Instead create a type and use `encode` and `decode` on it
|
||||
- These types should generally use generic deriving of aeson (no hand deriving)
|
||||
|
||||
|
||||
## Relude Best Practices
|
||||
|
||||
When using `relude`, refer to [RELUDE.md](RELUDE.md) for best practices and idioms.
|
||||
|
||||
## Testing
|
||||
|
||||
- Use QuickCheck for property-based testing
|
||||
- Use HUnit or Hspec for unit tests
|
||||
- Provide good examples in documentation
|
||||
|
||||
## Build instructions
|
||||
|
||||
As you make code changes, start a subagent in parallel to resolve any compile errors in `ghcid.txt`.
|
||||
|
||||
**IMPORTANT**: Do not run build commands yourself. The human runs ghcid in the terminal, which then updates `ghcid.txt` with any compile error or warning (if this file does not exist, or if ghcid has stopped, remind the human to address it). You should read `ghcid.txt` (in _entirety_) after making code changes; this file updates near-instantly.
|
||||
|
||||
**Adding/Deleting modules**: When a new `.hs` file is added or deleted, the `.cabal` file must be updated accordingly. However, if `package.yaml` exists in the project, run `hpack` instead to regenerate the `.cabal` file with the updated module list. This will trigger `ghcid` to restart automatically.
|
||||
|
||||
**HLint warnings**: MANDATORY. After `ghcid.txt` shows success, if `.hlint.yaml` exists, run `hlint` on the modified files. You are NOT done until `hlint` reports no issues.
|
||||
|
|
@ -1,220 +0,0 @@
|
|||
---
|
||||
name: optimize-nix
|
||||
description: Optimize Nix flake evaluation and `nix develop` startup time. Use when the user wants to speed up nix develop, nix-shell, or flake evaluation.
|
||||
---
|
||||
|
||||
# Nix Flake Evaluation Optimization
|
||||
|
||||
Systematic approach to reducing `nix develop` startup time.
|
||||
|
||||
## Workflow
|
||||
|
||||
Iterative optimization loop. For each change:
|
||||
|
||||
1. **Measure** baseline (5 samples, isolated `$HOME` to avoid cache interference)
|
||||
2. **Change** one thing
|
||||
3. **Measure** again
|
||||
4. **Commit** with before/after measurements in the commit message
|
||||
5. **Push** to the PR branch
|
||||
6. **CI** after each significant change
|
||||
7. Repeat
|
||||
|
||||
### Setup
|
||||
|
||||
Create a branch and open a **draft PR** before starting. Each optimization gets its own commit.
|
||||
|
||||
### Measurement
|
||||
|
||||
Use isolated `$HOME` so measurements don't destroy the developer's caches:
|
||||
|
||||
```bash
|
||||
for i in 1 2 3 4 5; do
|
||||
rm -rf /tmp/nix-bench/home && mkdir -p /tmp/nix-bench/home
|
||||
{ time HOME=/tmp/nix-bench/home nix develop -c echo "s$i" 2>/dev/null; } 2>&1 | grep real
|
||||
done
|
||||
```
|
||||
|
||||
Also measure warm (daemon-cached) performance — just run without resetting `$HOME`:
|
||||
|
||||
```bash
|
||||
# warm up first
|
||||
HOME=/tmp/nix-bench/home nix develop -c echo warmup 2>/dev/null
|
||||
for i in 1 2 3; do
|
||||
{ time HOME=/tmp/nix-bench/home nix develop -c echo "s$i" 2>/dev/null; } 2>&1 | grep real
|
||||
done
|
||||
```
|
||||
|
||||
### Commit Format
|
||||
|
||||
Use conventional commits. Include measurements in every commit body.
|
||||
|
||||
## Where Time Goes
|
||||
|
||||
`nix develop` time breaks down into:
|
||||
|
||||
1. **Flake fetcher-cache verification** (~1.5s per input) — nix re-verifies each input on every cold invocation. This is the #1 bottleneck.
|
||||
2. **Nixpkgs evaluation** (~0.2-0.5s) — importing the nixpkgs tree.
|
||||
3. **Package resolution** (~0.5-1.5s) — resolving specific packages.
|
||||
4. **Shell env realization** (~0.3-0.7s) — daemon overhead for building the shell derivation.
|
||||
|
||||
### Key Insight
|
||||
|
||||
With **zero flake inputs**, `nix develop` is fast: ~2.6s cold, ~0.3s warm (daemon eval cache). Each flake input adds ~1.5s. A typical flake with 4+ inputs (nixpkgs, flake-parts, git-hooks, systems) costs 7-18s.
|
||||
|
||||
## The Fix: Zero-Input Flake
|
||||
|
||||
**Import nixpkgs via `fetchTarball` instead of as a flake input.** This eliminates all fetcher-cache overhead while keeping `nix develop` as the interface.
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
nix/nixpkgs.nix ← single nixpkgs pin (fetchTarball)
|
||||
default.nix ← all packages + shared koluEnv
|
||||
shell.nix ← devShell (imports default.nix, accepts { pkgs } arg)
|
||||
flake.nix ← zero inputs, imports the above, exports packages + devShells
|
||||
```
|
||||
|
||||
### Source management with npins
|
||||
|
||||
Use [npins](https://github.com/andir/npins) to manage all fetched sources (nixpkgs, GitHub repos, etc.). This replaces hardcoded `fetchTarball`/`fetchFromGitHub` calls with a single `npins/sources.json`.
|
||||
|
||||
```bash
|
||||
npins init --bare
|
||||
npins add github nixos nixpkgs --branch nixpkgs-unstable --at <REV>
|
||||
npins add github owner repo --branch main --at <REV>
|
||||
npins update # bump all sources
|
||||
npins update nixpkgs # bump just nixpkgs
|
||||
```
|
||||
|
||||
### `nix/nixpkgs.nix` — Single source of truth
|
||||
|
||||
```nix
|
||||
# Managed by npins. To update: npins update nixpkgs
|
||||
let sources = import ../npins;
|
||||
in import sources.nixpkgs
|
||||
```
|
||||
|
||||
Other nix files can also use `sources`:
|
||||
|
||||
```nix
|
||||
# nix/some-dep/default.nix
|
||||
{ pkgs }:
|
||||
let sources = import ../../npins;
|
||||
in pkgs.runCommand "foo" {} ''
|
||||
cp -r ${sources.some-repo}/bar $out
|
||||
''
|
||||
```
|
||||
|
||||
Note: npins handles GitHub repos and tarballs. Plain `fetchurl` (e.g. font files from CDNs) stays hardcoded.
|
||||
|
||||
### `flake.nix` — Zero inputs
|
||||
|
||||
```nix
|
||||
# IMPORTANT: This flake intentionally has ZERO inputs.
|
||||
#
|
||||
# nixpkgs is imported via fetchTarball in nix/nixpkgs.nix, bypassing the
|
||||
# flake input system. Each flake input adds ~1.5s of fetcher-cache
|
||||
# verification. With zero inputs, `nix develop` cold is ~2.6s, warm ~0.3s.
|
||||
#
|
||||
# DO NOT add flake inputs (nixpkgs, flake-parts, git-hooks, etc.).
|
||||
# Instead, use fetchTarball or callPackage in nix/ files.
|
||||
{
|
||||
outputs = { self, ... }:
|
||||
let
|
||||
systems = [ "x86_64-linux" "aarch64-darwin" ];
|
||||
eachSystem = f: builtins.listToAttrs (map
|
||||
(system: {
|
||||
name = system;
|
||||
value = f (import ./nix/nixpkgs.nix { inherit system; });
|
||||
})
|
||||
systems);
|
||||
commitHash = self.shortRev or self.dirtyShortRev or "dev";
|
||||
in
|
||||
{
|
||||
packages = eachSystem (pkgs:
|
||||
let all = import ./default.nix { inherit pkgs commitHash; };
|
||||
in removeAttrs all [ "koluEnv" ]); # koluEnv is not a derivation
|
||||
devShells = eachSystem (pkgs:
|
||||
{ default = import ./shell.nix { inherit pkgs; }; });
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### `shell.nix` — Shared devShell
|
||||
|
||||
```nix
|
||||
{ pkgs ? import ./nix/nixpkgs.nix { } }:
|
||||
let packages = import ./default.nix { inherit pkgs; };
|
||||
in pkgs.mkShell {
|
||||
name = "my-shell";
|
||||
# Use mkShell's env attr — no duplicate export lines
|
||||
env = packages.koluEnv // { ... };
|
||||
shellHook = ''...'';
|
||||
packages = with pkgs; [ ... ];
|
||||
}
|
||||
```
|
||||
|
||||
### DRY shared env vars
|
||||
|
||||
Define env vars once in `default.nix` as an attrset, use in both the build derivation's `env` and `mkShell`'s `env`:
|
||||
|
||||
```nix
|
||||
# In default.nix
|
||||
koluEnv = {
|
||||
KOLU_THEMES_JSON = "${ghosttyThemes}/themes.json";
|
||||
KOLU_FONTS_DIR = "${fonts}";
|
||||
};
|
||||
|
||||
kolu = pkgs.stdenv.mkDerivation {
|
||||
env = { npm_config_nodedir = nodejs; } // koluEnv;
|
||||
...
|
||||
};
|
||||
```
|
||||
|
||||
**Important:** `koluEnv` is not a derivation — filter it out of flake `packages` output with `removeAttrs` or devour-flake will fail trying to build it.
|
||||
|
||||
### Replacements for common flake inputs
|
||||
|
||||
- **flake-parts** → `eachSystem` helper (3 lines)
|
||||
- **systems** → inline `[ "x86_64-linux" "aarch64-darwin" ]`
|
||||
- **git-hooks.nix** → static `.pre-commit-config.yaml` + tools in devShell
|
||||
- **process-compose-flake** → just's `[parallel]` attribute
|
||||
|
||||
### `.envrc`
|
||||
|
||||
```
|
||||
use flake
|
||||
```
|
||||
|
||||
### justfile
|
||||
|
||||
```just
|
||||
nix_shell := if env('IN_NIX_SHELL', '') != '' { '' } else { 'nix develop path:' + justfile_directory() + ' -c' }
|
||||
```
|
||||
|
||||
## Why Not nix-shell or env caching?
|
||||
|
||||
We benchmarked all approaches with a zero-input flake:
|
||||
|
||||
| Approach | Cold | Warm |
|
||||
|---|---|---|
|
||||
| `nix develop` (zero inputs) | **2.6s** | **0.3s** |
|
||||
| `nix-shell` (fetchTarball) | 2.4s | 2.4s (no daemon cache) |
|
||||
| env caching script | 5s miss | 0.014s hit |
|
||||
|
||||
`nix develop` wins: 0.3s warm with zero maintenance. `nix-shell` has no daemon eval cache so it's always 2.4s. Env caching (nix-shell-fast) achieves 0.014s but introduces staleness bugs, manual cache key maintenance, and shellHook side-effects not re-running — complexity not worth the gain.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **`nix flake lock` silently bumps inputs** — when removing inputs, nix may update remaining ones to latest. Always verify the nixpkgs rev matches master's. Run CI after any lock change.
|
||||
- **`fetchTarball` sha256** — use SRI format (`sha256-...`). With npins, hashes are managed automatically.
|
||||
- **npins `default.nix` is auto-generated** — don't edit it manually; `npins` overwrites it. Mark it in `.gitattributes` as `linguist-generated`.
|
||||
- **Non-derivation in packages** — if `default.nix` exports non-derivations (like a `koluEnv` attrset), filter them out in `flake.nix` or devour-flake/`nix flake check` will fail.
|
||||
- **direnv cache staleness** — after changing `nix/nixpkgs.nix`, delete `.direnv/` to force direnv re-evaluation. Otherwise `use flake` serves stale env vars.
|
||||
|
||||
## Expected Results
|
||||
|
||||
| | Cold eval cache | Warm (daemon cached) |
|
||||
|---|---|---|
|
||||
| Before (4+ inputs) | 7-18s | 3-7s |
|
||||
| After (0 inputs) | ~2.6s | ~0.3s |
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
---
|
||||
name: technical-writer
|
||||
description: Direct, no-nonsense technical writing. Use for blog posts, documentation, and READMEs. Eliminates AI-speak, hedging, and filler.
|
||||
---
|
||||
|
||||
# Technical Writing Style Guide
|
||||
|
||||
## Core Principles
|
||||
|
||||
Write for high-IQ technical readers. Assume expertise. Remove hand-holding.
|
||||
|
||||
## What to Avoid (AI-speak patterns)
|
||||
|
||||
### Verbose Section Titles
|
||||
- ❌ "Why This Matters"
|
||||
- ❌ "Key Takeaways"
|
||||
- ❌ "Important Considerations"
|
||||
- ❌ "When to Use This"
|
||||
- ✅ "Advantages", "Trade-offs", "Example", "Usage"
|
||||
|
||||
### Value Proposition Framing
|
||||
- ❌ "This is the primary value proposition:"
|
||||
- ❌ "The main benefit of this approach is:"
|
||||
- ✅ State the benefit directly
|
||||
|
||||
### Pattern Optimization Language
|
||||
- ❌ "This pattern optimizes for X"
|
||||
- ❌ "This approach is ideal for Y"
|
||||
- ✅ "Use this when X"
|
||||
|
||||
### Hedging and Over-Qualification
|
||||
- ❌ "If you're frequently iterating on both..."
|
||||
- ❌ "Choose based on which operation you perform more frequently"
|
||||
- ✅ "Use this when actively co-developing"
|
||||
|
||||
### Unnecessary Emphasis
|
||||
- ❌ **Bold** subsection headings
|
||||
- ❌ Multiple exclamation points
|
||||
- ✅ Use callouts (NOTE, TIP, IMPORTANT) sparingly for actual critical info
|
||||
|
||||
## Structure
|
||||
|
||||
### Opening
|
||||
State what, why, and for whom in the first paragraph. No preamble.
|
||||
|
||||
### Pattern/Implementation
|
||||
Show code first. Minimal explanation. Assume readers can read code.
|
||||
|
||||
### Technical Details
|
||||
Link to official documentation. Use callouts for non-obvious gotchas.
|
||||
- `[!NOTE]` for clarifications
|
||||
- `[!TIP]` for advanced understanding
|
||||
- `[!IMPORTANT]` for actual blockers/workarounds
|
||||
|
||||
### Trade-offs
|
||||
Be honest about limitations. Link to upstream issues. Don't sugarcoat.
|
||||
|
||||
### Usage Guidance
|
||||
Direct imperatives. "Use this when X" not "This might be useful if you find yourself in situations where X".
|
||||
|
||||
## Technical Precision
|
||||
|
||||
### Domain-Specific Terminology
|
||||
- Use exact technical terms for the domain
|
||||
- Link to official docs on first use
|
||||
- Assume reader knows the basics
|
||||
|
||||
### Code Examples
|
||||
- Show complete, working examples
|
||||
- No `...` placeholders or "existing code" comments
|
||||
- Include all relevant flags/options
|
||||
|
||||
### References Section
|
||||
- Link to upstream documentation
|
||||
- Link to relevant issues/discussions
|
||||
- Link to working examples
|
||||
- Keep descriptions minimal (one clause)
|
||||
|
||||
## Iteration Notes
|
||||
|
||||
When revising:
|
||||
1. Remove all "This is because..." explanations unless non-obvious
|
||||
2. Cut phrases like "as mentioned above", "as we saw earlier"
|
||||
3. Remove transition sentences between sections
|
||||
4. State facts directly - no "it's worth noting that"
|
||||
5. Replace "you can" with imperative: "Run X" not "You can run X"
|
||||
|
||||
## Example Transformations
|
||||
|
||||
### Before (AI-speak)
|
||||
> This pattern optimizes for active co-development. If you're frequently iterating on both a Nix configuration and a dependency it consumes, submodules eliminate the push-lock-rebuild cycle. Choose based on which operation you perform more frequently.
|
||||
|
||||
### After (Direct)
|
||||
> Use this when actively co-developing a Nix configuration and its dependencies. Submodules eliminate the push-lock-rebuild cycle.
|
||||
|
||||
---
|
||||
|
||||
### Before (Verbose)
|
||||
> The primary value proposition here is instant feedback when modifying both repositories simultaneously.
|
||||
|
||||
### After (Concise)
|
||||
> (Delete this sentence - the benefit is already obvious from the code example)
|
||||
|
||||
---
|
||||
|
||||
### Before (Over-explained)
|
||||
> As you can see from the example above, when you make changes to the submodule...
|
||||
|
||||
### After (Direct)
|
||||
> Changes to `vendor/AI/` are picked up immediately on rebuild.
|
||||
Loading…
Add table
Add a link
Reference in a new issue