upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/AGENTS.md
blob: ef45a524b31c1a2b51052178afd5fa1862e4023c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# AGENTS.md

This file provides guidance to agents when working with code in this repository.

## Project Structure

**Workspace with Two Rust Projects:**

- Root: `ngit-grasp` (main GRASP relay implementation)
- `grasp-audit/`: Separate subproject with own `Cargo.toml` and `flake.nix`

Cannot build grasp-audit from root - must `cd grasp-audit` first.

## Build & Test

### Nix Flakes (Non-Standard)

**CRITICAL:** Use `nix develop`, NOT `nix-shell` (we use flake.nix, not shell.nix)

```bash
# ✅ Correct
cd grasp-audit
nix develop -c cargo build
nix develop -c cargo test

# ❌ Wrong
nix-shell
nix-shell --run "cargo build"
```

### Testing ngit-grasp (Main Project)

**ngit-grasp integration tests use the [`TestRelay`](tests/common/relay.rs:14) fixture:**

The `TestRelay` fixture automatically starts an instance of ngit-grasp itself and manages its lifecycle:

```bash
# Run all ngit-grasp tests (from project root)
cargo test

# Run integration tests only
cargo test --test '*'

# Run specific test file
cargo test --test nip01_compliance
```

**How TestRelay works:**

- Spawns `ngit-grasp` binary on a random available port
- Creates temporary directories for git and relay data
- Provides `url()` and `domain()` methods for test clients
- Automatically cleans up on drop

**Example test pattern:**

```rust
use common::TestRelay;

#[tokio::test]
async fn test_something() {
    let relay = TestRelay::start().await;
    // relay.url() returns "ws://127.0.0.1:{port}"
    // ... run test against ngit-grasp ...
    relay.stop().await;
}
```

### Summary: Which Test Command for What

| What you're testing         | Command                                                                |
| --------------------------- | ---------------------------------------------------------------------- |
| ngit-grasp (this project)   | `cargo test` from project root                                         |
| ngit-relay (reference impl) | `cd grasp-audit && nix develop -c bash test-ngit-relay.sh --mode test` |
| grasp-audit unit tests      | `cd grasp-audit && nix develop -c cargo test --lib`                    |

### Running Single Test

```bash
# ngit-grasp test (from project root)
cargo test --test nip01_compliance test_websocket_connection -- --nocapture

# grasp-audit test (from grasp-audit/)
nix develop -c cargo test --lib specific_test_name -- --nocapture
```

### Troubleshooting

**Buffer Size Errors:**
If you see mpsc channel buffer size panics on first test run, this is usually transient. Simply run the tests again.

**Port Conflicts:**
Both `TestRelay` and `test-ngit-relay.sh` use random ports to avoid conflicts. If you see port errors, ensure no stale processes are running.

## Code Patterns

### nostr-sdk 0.43 Breaking Changes (vs 0.35)

**Field access, not method calls:**

```rust
// ❌ WRONG (0.35 API)
event.id()
event.tags()
for tag in &event.tags { }

// ✅ CORRECT (0.43 API)
event.id          // Direct field access
event.tags        // Direct field access
event.tags.iter() // Iterator method
```

**Tag API changed:**

```rust
// ❌ WRONG (0.35)
Tag::Generic(TagKind::Custom("clone".into()), vec![...])

// ✅ CORRECT (0.43)
Tag::custom(TagKind::custom("clone"), vec![...])
```

**EventBuilder signature changed:**

```rust
// ❌ WRONG (0.35)
EventBuilder::new(kind, content, &[tags])

// ✅ CORRECT (0.43)
EventBuilder::new(kind, content).tags(tags)
```

### Audit Event Tagging (grasp-audit)

**All audit events automatically include cleanup tags:**

The grasp-audit system automatically adds three tags to every event for production cleanup and test isolation. These tags are added transparently via [`AuditEventBuilder::build()`](grasp-audit/src/audit.rs:120-129) with 100% coverage through [`AuditClient::event_builder()`](grasp-audit/src/client.rs:107-138).

**Automatic Tags (no manual intervention needed):**

```rust
// These tags are automatically added to EVERY audit event:
["t", "grasp-audit-test-event"]              // Identifies all audit test events
["t", "audit-{run_id}"]                      // Unique ID for this audit run (correlates events)
["t", "audit-cleanup-after-{unix_timestamp}"] // Unix timestamp for cleanup scheduling
```

**Tag Format Details:**

- Uses standard NIP-01 `"t"` (hashtag) tags for maximum compatibility
- Unix timestamps (not ISO 8601) for easier database queries
- All tags added automatically when calling `client.event_builder().build()`
- No manual tag management required

**Verifying Tags in Tests:**

```rust
// Test that verifies automatic tag addition:
// See: grasp-audit/src/client.rs:273-302
#[test]
fn test_audit_tags_automatically_added() {
    // Creates event and verifies all three tags are present
}
```

**Testing Implications:**

- All audit events are tagged for easy cleanup
- Use `run_id` tag to correlate events from same audit run
- Tags enable production relay cleanup scripts
- No special handling needed in test code - tags are automatic

## Configuration Management

**⚠️ CRITICAL: Keep Configuration in Sync Across All Four Sources**

Configuration options must be consistent across four locations:

1. **Source code** (`src/config.rs`) - Defines actual config structs, env vars, and defaults
2. **Documentation** (`docs/reference/configuration.md`) - User-facing reference for all options
3. **NixOS module** (`nix/module.nix`) - NixOS deployment configuration
4. **Example env file** (`.env.example`) - Template for development and Docker deployments

**When adding/modifying ANY configuration option:**

1. **Update `src/config.rs`** - Add/modify the field with proper env var name
2. **Update `docs/reference/configuration.md`** - Document the option with examples and defaults
3. **Update `nix/module.nix`** - Add/modify the NixOS option in `instanceOptions`
4. **Update `.env.example`** - Add the option with comments explaining usage and defaults
5. **Verify consistency** - Check env var names, defaults, and descriptions match exactly

**Critical consistency checks:**

- Environment variable names must match: `NGIT_*` in code, docs, module, and .env.example
- Default values must match across all four sources
- Option names should be consistent (snake_case in code, camelCase in NixOS)
- Descriptions should be similar (can be more detailed in docs)

**Example: Adding a new config option**

```rust
// 1. src/config.rs
#[arg(long, env = "NGIT_NEW_OPTION", default_value_t = 42)]
pub new_option: u32,
```

```markdown
<!-- 2. docs/reference/configuration.md -->
#### `NGIT_NEW_OPTION`
**Description:** What this option does
**Type:** Integer
**Default:** `42`
**Required:** No
```

```nix
# 3. nix/module.nix (in instanceOptions)
newOption = mkOption {
  type = types.int;
  default = 42;
  description = "What this option does";
};

# Also add to environment mapping in mkService:
environment = {
  # ...
  NGIT_NEW_OPTION = toString cfg.newOption;
};
```

```bash
# 4. .env.example
# What this option does
# CLI: --new-option <value>
# Default: 42
# NGIT_NEW_OPTION=42
```

## Documentation

**⚠️ CRITICAL: Keep Architecture Docs Updated**

Architecture and design documents are LIVING DOCUMENTS. When implementation diverges from the documented plan:

1. **Update the doc IMMEDIATELY** - Don't wait until "later"
2. **Document what was actually built**, not what was originally planned
3. **Note why decisions changed** - Future readers need this context
4. **Files to watch:** `docs/explanation/architecture.md`, `docs/explanation/decisions.md`

This was a key learning from GRASP-01: docs described plans, not implementation, causing confusion.

**Diátaxis Framework Used:**

- `docs/tutorials/` - Learning-oriented
- `docs/how-to/` - Task-oriented
- `docs/reference/` - Information-oriented
- `docs/explanation/` - Understanding-oriented

**Session files go in `work/` (gitignored except README.md)**

- Archive valuable content to `docs/archive/YYYY-MM-DD-*.md` at session end
- Delete temporary files
- Keep root clean (only README.md, AGENTS.md)

## Critical Gotchas

1. **Workspace compilation:** Can't `cargo build` from root for grasp-audit
2. **Nix environment:** Must use `nix develop`, not `nix-shell`
3. **nostr-sdk API:** Fields not methods in 0.43
4. **Test isolation:** Integration tests use `TestRelay` (ngit-grasp) or `test-ngit-relay.sh` (ngit-relay)
5. **Work directory:** All session docs go in `work/`, NOT root
6. **Archive naming:** Use `YYYY-MM-DD-description.md` format
7. **test-ngit-relay.sh tests ngit-relay**: This script tests the reference implementation, NOT ngit-grasp
8. **Configuration sync:** Config changes MUST be updated in all four places: `src/config.rs`, `docs/reference/configuration.md`, `nix/module.nix`, AND `.env.example`
9. **Git dependencies:** When updating `Cargo.toml` or `Cargo.lock` with ANY git dependency (not crates.io), you MUST update hashes in BOTH `flake.nix` and `nix/module.nix`. See [docs/how-to/update-git-dependencies.md](docs/how-to/update-git-dependencies.md) - this is MANDATORY and builds will fail if skipped

## File Restrictions by Mode

Code mode can only edit files matching specific patterns (enforced by system):

- Example: Architect mode restricted to `\.md$` files only
- Attempting to edit restricted files causes FileRestrictionError
- Check mode configuration if edit attempts fail unexpectedly

## Quick Reference

```bash
# Test ngit-grasp (main project)
cargo test

# Build grasp-audit
cd grasp-audit && nix develop -c cargo build

# Run grasp-audit unit tests
cd grasp-audit && nix develop -c cargo test --lib

# Check session files
ls work/  # Should only have README.md when clean
```