fix(context): sync to the estate canonical context-sync.sh, drop Cursor/Aider
CD / Lint / Test / Vet (push) Successful in 24s
CD / Build & Import (push) Successful in 14s
CD / Deploy via GitOps (push) Successful in 5s

This repo's copy of context-sync.sh was two days stale against the version
Mathias's Cursor/Aider decision (2026-09-01) already landed in dma-cli:
still generated .cursorrules and .aider.conventions.md, and lacked the
--force/placeholder pre-flight and .context/mcp.json root-merge dma-cli's
copy has had since.

Found while building the estate-wide context-sync drift gate (ADR-0019) --
this is exactly the drift the gate exists to catch, found by hand while
establishing the baseline it will check going forward.

- script synced to the canonical hash (dma-cli's current copy)
- .cursorrules, .aider.conventions.md, .aider.conf.yml deleted
- Taskfile's drift assertion and file list narrowed to match
- AGENTS.md, .context/system-prompt.txt regenerated against canonical

task check -> exit 0.
This commit is contained in:
2026-09-05 08:01:07 +02:00
parent e4d07c106c
commit 419ae19cdb
7 changed files with 187 additions and 851 deletions
-2
View File
@@ -1,2 +0,0 @@
read: .aider.conventions.md
auto-commits: false
-386
View File
@@ -1,386 +0,0 @@
# Agent context — Mathias workspace
<!-- Canonical root context for all AI coding agents.
Lives at: ~/dev/.context/AGENT.md
Applies to every project under ~/dev/ unless overridden.
Run `task context:sync` from ~/dev/ to regenerate harness-specific files.
Project-level context in .context/PROJECT.md layers on top of this. -->
## Who I am
I'm Mathias, a digital product manager and technology consultant based in Sweden.
I build software, research emerging tech, and deliver consulting engagements
for clients under NDA. I work across AI/ML, financial automation, web applications,
and climate/sustainability tech.
## How I work with agents
- I think like a product manager — I care about *why* before *how*
- I want agents to be opinionated and push back, not just execute blindly
- I prefer concise responses; skip ceremony and get to the point
- When I say "build this", I mean production-quality with tests, not a demo
- Ask me before making irreversible changes or adding heavy dependencies
- I work with confidential client data — never send it to cloud APIs unless I explicitly say it's OK
## Behavior rules
These rules apply to every task across every project, regardless of harness.
0. **Pre-task ritual — before ANY implementation (non-negotiable).** Run this before writing a single line:
- **Query the brain** (`brain_query`) for the domain + symptom. If the result changes your approach, surface it before acting. 5 seconds beats 5 hours.
- **Load the relevant skill** — see trigger table in *Engineering Skills* below.
- **Write the failing test first.** Name the test before the function. If the target is untestable (e.g. `main()` wiring), extract the logic into a testable function first. No implementation without a red test.
- **State the observable success criterion** — what specific behavior, output, or passing test proves this is done?
**TDD is non-negotiable.** "Tests pass" is not proof of correctness — only proof the tests ran. Write tests that would catch the bug before writing code that fixes it.
1. **No assumptions.** Don't hide confusion — surface it. Surface tradeoffs explicitly.
Think before coding; if the problem is unclear, ask or state assumptions before acting.
2. **Minimum viable code.** Solve with the smallest change that works. Nothing
speculative, no "while we're here" cleanups, no premature abstractions. Simplicity first.
3. **Surgical changes.** Touch only what the task requires. Leave unrelated code,
files, and formatting alone. Diffs should be small and reviewable.
4. **Goal-driven execution.** Define clear success criteria up front for every task.
Loop — implement, verify, refine — until those criteria are met. Don't claim
completion without evidence (tests pass, command output, observed behavior).
5. **Trunk-Based Development — commit directly to main.** Every commit is one
logical change (one tool, one fix, one test) with passing tests. Main is always
deployable. Never create long-lived feature branches.
**Exception — parallel agents on same repo:** If another agent is known to be
actively working on the same repo simultaneously, create a short-lived branch
(`agent/<description>`), finish the task, and merge to main within the same
session. Do not leave agent branches open between sessions.
**Exception — external contributor or client four-eyes requirement:** Use
PR flow only when a human reviewer outside the project is required. Document
the reason in PROJECT.md.
6. **Close the loop — every substantive task ends with the same ritual.** Shipping
the code is not the end of the task; capturing it is. Run this unprompted:
- **Tag + bump SemVer** on the change (annotated tag; minor for a feature or
new/changed ADR, patch for a fix; docs in the same commit). Check the repo's
actual last tag — stated versions in docs drift stale.
- **Push** main and the tag (CI is the gate).
- **Persist generalizable learnings to the brain** (`brain_write`, wing/hall) —
the reusable patterns and the footguns that would bite anyone again, never
project status. See *Knowledge base — when to write* below.
- **File discovered-but-deferred work as tracker issues** on the project's own
repo — token-budget gaps, recorded ADR limitations, v2 follow-ups. Don't let
"out of scope, recorded" rot in a commit message; make it a ticket with a
source pointer.
- Surface the brain entries and issue numbers in the closing summary so the
trail is auditable.
## Default stack
| Layer | Default | Fallback | Last resort |
|-------|---------|----------|-------------|
| Language | Go | Python | TypeScript, Java, C |
| UI | HTMX + Templ | Server-rendered HTML | React (only if SPA is justified) |
| Build | Task (taskfile.dev) | Make | — |
| Containers | Docker Compose (dev), k3s (prod) | — | — |
| DB | PostgreSQL + sqlc | SQLite | — |
| Search | pgvector (vector), BM25 | Qdrant (when >1M vectors or hybrid retrieval) | — |
| Logging | slog (structured) | — | — |
| Testing | Table-driven, testify | — | — |
| Agents (Go) | google.golang.org/adk + pkg/litellm adapter | — | — |
Exploratory: Rust, Zig — I'll tell you when I want these.
## Code conventions
- **Go style**: golines, gofumpt, golangci-lint
- **Errors**: `fmt.Errorf("operation: %w", err)` — never naked, never log-and-return
- **Naming**: stdlib conventions, no stuttering
- **Architecture**: prefer stdlib over frameworks, constructor injection, env-var config parsed into typed structs
- **Git**: conventional commits (`feat:`, `fix:`, `chore:`), commit directly to main,
one logical change per commit, CI is the quality gate
- **Never**: long-lived feature branches, PRs for solo work, direct push without
passing `task check` locally first
- **Security**: no secrets in code, govulncheck before adding deps, SOPS for encrypted config
- **Dependencies**: prefer stdlib. testify, slog, templ, sqlc, google.golang.org/adk (agent projects only) are pre-approved; anything else needs justification in the commit message
## Secret handling (every harness, every command)
Tool output is persisted: terminal → `~/.claude/projects` transcripts →
claudewatcher → brain/wiki → gitea history. A secret printed once is
searchable forever, and clearing it means rotating the key. So:
1. **Never print, echo, log, or transform a secret to inspect it.** No
`base64`/`xxd`/`cat` of a key, and never pipe a secret through a transform
to defeat `op run`'s output masking (it masks raw values; base64 hides them
from the mask — that exact trick leaked a key on 2026-06-11).
2. **Secrets stay in the subprocess.** Reference them only as env vars consumed
*inside* `op run --env-file ~/.op-env -- <cmd>`. Never place a literal secret
in a command's argv (it lands in the tool call and the transcript).
3. **Existence check without revealing the value:** `[ -n "$X" ] && echo set` —
never `${X:-...}` (returns the value when set) and never echo a substring of it.
4. **Cross-host secrets:** run the secret-consuming command on the host that has
the secret; do not forward a raw key over ssh argv/stdout.
5. If a secret does leak into output, say so immediately and flag it for rotation —
don't bury it.
## Infrastructure
Three machines on Tailscale:
| Machine | Role | Key specs |
|---------|------|-----------|
| koala | GPU inference, heavy compute | RTX 5070, runs k3s + llama-swap + shared postgres18/pgvector |
| iguana | Services, builds | M2 Ultra Mac |
| flamingo | Daily driver, edge | Mac mini, ~/dev is here |
- **Model routing**: LiteLLM in front of llama-swap (local) + cloud APIs (when permitted)
- **Orchestration**: k3s cluster across all three machines
- **Networking**: Tailscale mesh
## Project landscape
All development repos live at `~/dev/` (softlink from `~/Documents/local-dev/`).
Organized in thematic folders:
| Folder | Focus | Count |
|--------|-------|-------|
| `GO/` | Go web frameworks, API integrations, learning projects | ~10 |
| `AI/` | ML research, AI frameworks (FinRL, DSPy, crawl4ai) | ~6 |
| `AGENTS/` | Autonomous agents, coding agents, MCP servers, infra | ~15 |
| `QKX/` | Invoice processing, financial automation, payment systems | ~13 |
| `XT/` | Climate data, sustainability (Klimatkollen, Garbo) | ~2 |
See `~/dev/PROJECT_SUMMARY.md` for detailed descriptions of each project.
### Key active projects
- **super-koala** (`AGENTS/`) — multi-component agent stack with LangGraph, DSPy, MCP
- **azure-tiger** (`QKX/`) — invoice extraction → ISO 20022 payment instructions
- **gocrwl** (`AGENTS/`) — Go web crawler with containerized deployment
- **koala-ai-stack** (`AGENTS/`) — local AI server infrastructure management
- **klimatkollen** (`XT/`) — Swedish municipal climate data platform
## Knowledge base — actively use it
A persistent brain (BM25 search + LLM-synthesised Q&A) survives across sessions,
hosts, and harnesses. It holds 100+ hard-won entries: infra incident postmortems,
Go pitfalls, framework gotchas, design principles, ADRs. **It is not optional
reference material — query it actively, not just when explicitly told.**
### When to query (treat as a reflex)
- **Before** starting a non-trivial task — search for prior art with the symptom
AND the system component ("how did we solve X in Y?"). 5 seconds beats 5 hours.
- **When debugging** — search for the error string, the stack frame, the affected
service. Past you may have already paid this tax.
- **Before adopting** a pattern, library, framework, or model name — check if it
was tried and rejected, or what the integration footguns are.
- **When making architectural decisions** — search for the domain + "ADR" or
"decision" to find prior reasoning before re-deriving it.
- **When a recommendation feels novel** — challenge yourself: "has this been
documented?" The brain often has it.
### When to write
After you discover something that **future-you would forget** and that **isn't
recoverable from the code, git log, or PR description alone**:
- Bugs whose root cause is non-obvious and generalisable beyond this project.
- Framework / library / model-name quirks that bit you and would bite anyone.
- Design principles validated under fire (e.g. "every `_get` needs a `_list`").
- Postmortems for incidents: what broke, why, how diagnosed, what to do next time.
DON'T write project status, sprint progress, PR summaries, or "what I did this
session" — those rot fast and the originals are in git/gitea anyway. Brain
entries that age well are about *why*, *how to avoid*, and *what to do when*.
### How to access (per harness)
| Harness | Query | Write |
|---------|-------|-------|
| **Claude Code, Claude Desktop** | `brain_query` (BM25), `brain_answer` (LLM-synth + sources) MCP tools | `brain_write` MCP tool |
| **Crush, Pi, Antigravity, other MCP-capable** | same MCP server: `ingestion-brain` (via the `mcp__*_brain__*` namespace once authenticated) | same |
| **Anything HTTP-only (curl, scripts)** | `POST https://brain-mcp.d-ma.be/query` with `{"query":"..."}` (auth via `BRAIN_MCP_TOKEN`) | `POST .../write` with `{"content":"...","filename":"..."}` |
| **Browser / human inspection** | `https://git.d-ma.be/mathias/hyperguild` → `knowledge/` and `wiki/` markdown files |
- **Scoping**: defaults to `public` collection; client projects filter to `{client}` + `public`.
- **Routing**: brain_answer's LLM uses berget.ai as primary, iguana ollama as
fallback. Both are configurable in the `supervisor/ingestion-deployment.yaml`
on the koala k3s cluster; don't hardcode local-only model names into the
berget URL (see knowledge entry on namespace mismatches).
### Quick reflex checks
If you find yourself about to say any of these out loud, you owe yourself a brain query first:
- "I think the issue might be..."
- "Let me try X and see..."
- "I'll just write a script to..."
- "This is probably a new bug..."
- "Has anyone done this before?" — *yes, probably, go check.*
## Client work rules
When working on a project tagged with a client name:
1. Never send code, data, or context to cloud APIs — use local models only
2. Never reference other client projects or their data
3. Keep all artifacts within the client's git org / directory
4. Treat everything as confidential unless told otherwise
## Harness-agnostic principles
This context is designed to work with any AI coding tool:
- Claude Code, Cursor, Aider, Open WebUI, Charmbracelet Mods/Crush
- Pi Coding Agent, Mistral Vibe, Antigravity
- Any tool that accepts a system prompt or reads a markdown context file
The canonical source is always `.context/AGENT.md` (root) and `.context/PROJECT.md` (per-project).
Derived files are committed (see *How context propagates* below) so a `git pull` on any host yields full agent context with no setup.
## How context propagates
Canonical sources of truth:
- Universal: `~/dev/.context/AGENT.md` (this file)
- Project: `<repo>/.context/PROJECT.md` (per-repo)
Derived files (committed, regenerated by `task context:sync`):
- `CLAUDE.md`, `AGENTS.md`, `.cursorrules`, `.aider.conventions.md`,
`.context/system-prompt.txt`
Workflow:
1. Edit a canonical file. Run `task context:sync`. Commit canonical and
derived together. Push.
2. On any other host, `git pull` brings both. Claude Code (tree-walking)
uses `CLAUDE.md`; Crush / Pi / Antigravity (cwd-only) use `AGENTS.md`;
Cursor uses `.cursorrules`; Aider uses `.aider.conventions.md`.
3. `task check` runs `context:sync` then asserts `git status --porcelain`
is empty over the derived files (catches both modified-tracked drift
and missing-untracked adapters). A drift fails the check with a
message telling you to stage the regenerated files.
Behavior rules in this file and per-project rules in `PROJECT.md` apply
unconditionally on every host, every harness.
## Engineering Skills
Shared engineering skills live in the **`mathias/skills`** repo (`git.d-ma.be/mathias/skills`). Clone it to `~/dev/skills/` and run `SKILLS_CHECKOUT_DIR="$PWD" bash install.sh` there to wire every skill into your harnesses (Claude Code, Crush, Antigravity, Mistral Vibe) as native, on-demand skills. (Use `install.sh`, not `task install` — the latter is currently broken, skills#7.) Load at task start — not "on demand" but on schedule, before writing code. Browse `~/dev/skills/SKILLS_INDEX.md` for the full list.
**Skill trigger table — load before starting, not after getting stuck:**
| Task type | Load |
|-----------|------|
| Any feature or bug fix | `tdd` |
| Refactor or design | `clean-code` or `solid` |
| Debug | `problem-analysis` |
| Review code or PRs | `code-review` |
| Frame a problem before coding | `problem-analysis` |
---
# Project context
<!-- Canonical project context. Edit this, run `task context:sync`.
Root agent context from ~/dev/.context/AGENT.md is automatically
prepended for harnesses that don't walk the directory tree. -->
## Identity
- **Name**: gitea-mcp
- **Owner**: Mathias
- **Client**: personal
- **Repo**: https://git.d-ma.be/mathias/gitea-mcp
- **Status**: active
## Stack
- **Primary language**: Go
- **UI layer**: HTMX + Templ (when applicable)
- **Fallback languages**: Python, TypeScript (justify in PR if used)
- **Build**: Task (taskfile.dev), not Make
- **Containers**: Docker (compose for dev, k3s for deploy)
- **Target infra**: koala (GPU workloads), iguana (services), flamingo (edge)
## Conventions
### Code style
- Go: follow `golines`, `gofumpt`, `golangci-lint` with project config
- Tests: table-driven, in `_test.go` next to source, `testify` for assertions
- Errors: wrap with `fmt.Errorf("operation: %w", err)`, no naked returns
- Naming: stdlib conventions, no stuttering (`http.Client` not `http.HTTPClient`)
### Architecture preferences
- Prefer standard library over frameworks (net/http over gin/echo)
- Dependency injection via constructor functions, not containers
- Configuration via environment variables, parsed at startup into a typed struct
- Structured logging via `slog`
### Git
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
- **Trunk-Based Development:** commit directly to main. One logical change per commit.
- Run `task check` locally before every push. CI is the quality gate, not branch protection.
- No feature branches, no PRs for solo/agent work.
- Exception: if a parallel agent session is active on this repo, use a short-lived
`agent/<description>` branch and merge within the same session.
### Security
- No secrets in code, ever — use env vars or SOPS-encrypted files
- Client data never leaves local network unless explicitly cleared
- Dependencies: audit with `govulncheck` before adding
## Knowledge base access
This project can query the shared knowledge base via MCP or HTTP:
- **MCP endpoint**: `mcp://localhost:3100/knowledge`
- **HTTP fallback**: `http://localhost:3100/api/v1/search`
- **Scoping**: queries are filtered to collection `personal` + `public`
## Behavior rules
These rules apply to every task in this project, regardless of harness.
1. **No assumptions.** Don't hide confusion — surface it. Surface tradeoffs explicitly.
Think before coding; if the problem is unclear, ask or state assumptions before acting.
2. **Minimum viable code.** Solve with the smallest change that works. Nothing
speculative, no "while we're here" cleanups, no premature abstractions. Simplicity first.
3. **Surgical changes.** Touch only what the task requires. Leave unrelated code,
files, and formatting alone. Diffs should be small and reviewable.
4. **Goal-driven execution.** Define clear success criteria up front for every task.
Loop — implement, verify, refine — until those criteria are met. Don't claim
completion without evidence (tests pass, command output, observed behavior).
## Agent instructions
When acting as a coding agent on this project:
1. Read this file and all `SKILL.md` files in `.skills/` before starting work
2. Run `task check` before committing (lint + test + vet)
3. If unsure about a convention, check `DECISIONS.md` or ask
4. Never modify files outside the project root without explicit permission
5. When adding a dependency, explain why in the commit message
6. Commit directly to main. Run `task check` before every push. Never create
feature branches unless a parallel agent is simultaneously active on this repo.
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
## Current state — v0.2.5 (2026-05-17)
All v0.2 work is complete and deployed. No active sprint.
### What shipped
| Tag | PR | Tools / fixes |
|-----|----|---------------|
| v0.2.2 | #21 | repo_create, repo_update, repo_mirror_push |
| v0.2.3 | #22 | repo_tree, repo_topics_update, file_read dir fix |
| v0.2.4 | #23 | issue_get, release_create, repo_delete |
| v0.2.5 | #26 | repo_update archived+template, create_project_from_template template_name, pr_files_diff loop fix |
Current main: `e31fd3f`. CI green. Deployed via Flux.
### Next up
1. **`hyperguild new-project` v1** — primary next target.
See brain node `adr-new-project-gitea-first-github-mirror` for full flow spec.
2. **Issue #19** — end-to-end mirror flow verification.
`repo_mirror_push` is implemented but the full flow (create repo → add push mirror → verify sync to GitHub) has not been tested manually. Do this before relying on it in production.
+90 -21
View File
@@ -53,14 +53,30 @@ These rules apply to every task across every project, regardless of harness.
logical change (one tool, one fix, one test) with passing tests. Main is always logical change (one tool, one fix, one test) with passing tests. Main is always
deployable. Never create long-lived feature branches. deployable. Never create long-lived feature branches.
**Exception — parallel agents on same repo:** If another agent is known to be **The PR trigger is RISK, not concurrency.** `risk/low` work commits straight to
actively working on the same repo simultaneously, create a short-lived branch main with CI as the gate — that is most work and is unchanged. `risk/medium` and
(`agent/<description>`), finish the task, and merge to main within the same above opens a PR, even solo, even with nobody waiting to review it. This mirrors
session. Do not leave agent branches open between sessions. the tiered merge model in `assessor-loop`
`method/transfer-to-a-regulated-organisation.md` §4.1, so the estate practises
what it sells instead of merging everything on green and claiming otherwise.
**Exception — external contributor or client four-eyes requirement:** Use **Concurrency is NOT a reason to branch.** Measured 2026-08-29 across three real
PR flow only when a human reviewer outside the project is required. Document collisions between parallel sessions: two were not branching problems at all — a
the reason in PROJECT.md. generated artifact written to the same path by both (branching turns that into a
merge conflict on a 33k-line JSON), and uncommitted work in a shared checkout
(a per-session worktree fixes it) — and the third was an ordinary non-fast-forward
push resolved by rebase in seconds. Use a worktree per session on shared repos and
timestamp generated artifacts. Do not reach for a branch.
Branch+PR flow carries documented costs on solo repos: merging auto-closes gated
issues, review comments live on a thread separate from the issue's, and a
merged-and-deleted branch cannot be amended (brain:
`gitea-pr-merge-autoclose-and-review-comment-footguns`). Those are the price of
the `risk/medium`+ lane — paid deliberately, not incurred by accident.
**Exception — external contributor or client four-eyes requirement:** Use PR flow
when a human reviewer outside the project is required. Document the reason in
PROJECT.md.
6. **Close the loop — every substantive task ends with the same ritual.** Shipping 6. **Close the loop — every substantive task ends with the same ritual.** Shipping
the code is not the end of the task; capturing it is. Run this unprompted: the code is not the end of the task; capturing it is. Run this unprompted:
@@ -78,6 +94,26 @@ These rules apply to every task across every project, regardless of harness.
- Surface the brain entries and issue numbers in the closing summary so the - Surface the brain entries and issue numbers in the closing summary so the
trail is auditable. trail is auditable.
**Chat is not a durable store. Never rely on Mathias remembering anything.**
Every gap, caveat, risk, open question and next step must land in one of four
places before the turn ends: **the brain**, **a repo** (code, docs, ADR),
**a tracker issue**, or **a plan file**. A finding stated only in conversation is
lost the moment the session closes — and this estate has repeatedly proved that a
remembered convention is an unreliable control.
This applies hardest to what is tempting to leave as a closing remark: "worth
knowing X isn't enforced yet", "this is a convention, not a gate", "we should
revisit Y". Those are exactly the items that need a locator. If it is worth
saying in a summary, it is worth writing where it can be found again without you.
## CAD sprint patterns
Two CAD sprint archetypes (defs: brain `wiki/homelab/decisions/continuous-agentic-development-cad-concept-2026-06-16`):
- **Inception Sprint** — birth a new repo. Oath-gated close; tag only once every clause holds. Brain: `.../inception-sprint-and-oath`.
- **Sweep Sprint** — clear an existing backlog. Parallel read-only research per issue → sequential one-issue-per-commit execution. Brain: `.../sweep-sprint-and-oath`.
Shared: decisions that are yours, not the agent's, surface as `DECISION NEEDED` (question/options/recommendation/blocks) — ask live if you're present, else comment and wait, never act past it. Live-breaking regressions get `git revert`, never forward-fixed; host-network work skips even read-only research (solo, synchronous, you present). Rule 6's closing ritual only fires once every Oath clause genuinely holds — blocked = descoped-and-tracked, never marked done.
## Default stack ## Default stack
| Layer | Default | Fallback | Last resort | | Layer | Default | Fallback | Last resort |
@@ -88,7 +124,7 @@ These rules apply to every task across every project, regardless of harness.
| Containers | Docker Compose (dev), k3s (prod) | — | — | | Containers | Docker Compose (dev), k3s (prod) | — | — |
| DB | PostgreSQL + sqlc | SQLite | — | | DB | PostgreSQL + sqlc | SQLite | — |
| Search | pgvector (vector), BM25 | Qdrant (when >1M vectors or hybrid retrieval) | — | | Search | pgvector (vector), BM25 | Qdrant (when >1M vectors or hybrid retrieval) | — |
| Logging | slog (structured) | — | — | | Logging | slog (structured) | stdlib `logging` w/ structured `extra=` (or structlog) | — |
| Testing | Table-driven, testify | — | — | | Testing | Table-driven, testify | — | — |
| Agents (Go) | google.golang.org/adk + pkg/litellm adapter | — | — | | Agents (Go) | google.golang.org/adk + pkg/litellm adapter | — | — |
@@ -97,7 +133,12 @@ Exploratory: Rust, Zig — I'll tell you when I want these.
## Code conventions ## Code conventions
- **Go style**: golines, gofumpt, golangci-lint - **Go style**: golines, gofumpt, golangci-lint
- **Errors**: `fmt.Errorf("operation: %w", err)` — never naked, never log-and-return - **Python style** (fallback language): ruff (format+lint, one tool), mypy --strict (non-negotiable,
matches Go's static typing discipline), pytest + pytest-cov (table-driven via
`@pytest.mark.parametrize`), uv (venv+deps+lock, one tool), pydantic-settings (typed env-var config
— same principle as Go's typed structs), src-layout + `pyproject.toml` only (no `setup.py`)
- **Errors**: `fmt.Errorf("operation: %w", err)` — never naked, never log-and-return.
Python: `raise X from e` (exception chaining, same principle) — never bare `except`, never silent `pass`
- **Naming**: stdlib conventions, no stuttering - **Naming**: stdlib conventions, no stuttering
- **Architecture**: prefer stdlib over frameworks, constructor injection, env-var config parsed into typed structs - **Architecture**: prefer stdlib over frameworks, constructor injection, env-var config parsed into typed structs
- **Git**: conventional commits (`feat:`, `fix:`, `chore:`), commit directly to main, - **Git**: conventional commits (`feat:`, `fix:`, `chore:`), commit directly to main,
@@ -135,10 +176,10 @@ Three machines on Tailscale:
|---------|------|-----------| |---------|------|-----------|
| koala | GPU inference, heavy compute | RTX 5070, runs k3s + llama-swap + shared postgres18/pgvector | | koala | GPU inference, heavy compute | RTX 5070, runs k3s + llama-swap + shared postgres18/pgvector |
| iguana | Services, builds | M2 Ultra Mac | | iguana | Services, builds | M2 Ultra Mac |
| flamingo | Daily driver, edge | Mac mini, ~/dev is here | | flamingo | Daily driver, edge | Apple Silicon Mac, ~/dev is here |
- **Model routing**: LiteLLM in front of llama-swap (local) + cloud APIs (when permitted) - **Model routing**: LiteLLM in front of llama-swap (local) + cloud APIs (when permitted)
- **Orchestration**: k3s cluster across all three machines - **Orchestration**: k3s on koala only — single control-plane node, no workers (verified 2026-08-30). iguana and flamingo are on the tailnet but are not cluster members
- **Networking**: Tailscale mesh - **Networking**: Tailscale mesh
## Project landscape ## Project landscape
@@ -234,10 +275,11 @@ When working on a project tagged with a client name:
## Harness-agnostic principles ## Harness-agnostic principles
This context is designed to work with any AI coding tool: Supported harnesses (2026-09-01) — Cursor and Aider were dropped; do not
- Claude Code, Cursor, Aider, Open WebUI, Charmbracelet Mods/Crush re-add adapters for them:
- Pi Coding Agent, Mistral Vibe, Antigravity - Claude Code, Antigravity, OpenCode, Mistral Vibe, Crush, Pi Coding Agent
- Any tool that accepts a system prompt or reads a markdown context file - Open WebUI, Charmbracelet Mods, or any tool that takes a system prompt string
- Any other tool that reads `AGENTS.md`, which is the de facto convention
The canonical source is always `.context/AGENT.md` (root) and `.context/PROJECT.md` (per-project). The canonical source is always `.context/AGENT.md` (root) and `.context/PROJECT.md` (per-project).
Derived files are committed (see *How context propagates* below) so a `git pull` on any host yields full agent context with no setup. Derived files are committed (see *How context propagates* below) so a `git pull` on any host yields full agent context with no setup.
@@ -249,15 +291,29 @@ Canonical sources of truth:
- Project: `<repo>/.context/PROJECT.md` (per-repo) - Project: `<repo>/.context/PROJECT.md` (per-repo)
Derived files (committed, regenerated by `task context:sync`): Derived files (committed, regenerated by `task context:sync`):
- `CLAUDE.md`, `AGENTS.md`, `.cursorrules`, `.aider.conventions.md`, - `CLAUDE.md`, `AGENTS.md`, `.context/system-prompt.txt`
`.context/system-prompt.txt`
Repos still carrying `.cursorrules`, `.aider.conventions.md` or
`.aider.conf.yml` are stale: nothing regenerates them. Delete them the next time
you touch the repo, along with their entries in that repo's `task check`
assertion.
Workflow: Workflow:
1. Edit a canonical file. Run `task context:sync`. Commit canonical and 1. Edit a canonical file. Run `task context:sync`. Commit canonical and
derived together. Push. derived together. Push.
2. On any other host, `git pull` brings both. Claude Code (tree-walking) 2. On any other host, `git pull` brings both. Which file a harness reads does
uses `CLAUDE.md`; Crush / Pi / Antigravity (cwd-only) use `AGENTS.md`; NOT split cleanly by lookup strategy, so do not reason from the strategy:
Cursor uses `.cursorrules`; Aider uses `.aider.conventions.md`. - `CLAUDE.md` — Claude Code (walks up the tree)
- `AGENTS.md` — Crush, Pi, Antigravity (cwd-only), OpenCode (project root,
global at `~/.config/opencode/AGENTS.md`), and Mistral Vibe, which **walks
up** from cwd and takes the first `AGENTS.md` it finds
(user-level `~/.vibe/AGENTS.md`)
- `.context/system-prompt.txt` — Open WebUI, Mods, raw API calls; not read by
any harness, it is for pasting
Vibe tree-walks for `AGENTS.md`, so "tree-walkers use CLAUDE.md" is false.
Each repo's `AGENTS.md` embeds the canonical and is the first one found from
inside that repo, so this works — but by construction, not by luck.
3. `task check` runs `context:sync` then asserts `git status --porcelain` 3. `task check` runs `context:sync` then asserts `git status --porcelain`
is empty over the derived files (catches both modified-tracked drift is empty over the derived files (catches both modified-tracked drift
and missing-untracked adapters). A drift fails the check with a and missing-untracked adapters). A drift fails the check with a
@@ -268,7 +324,20 @@ unconditionally on every host, every harness.
## Engineering Skills ## Engineering Skills
Shared engineering skills live in the **`mathias/skills`** repo (`git.d-ma.be/mathias/skills`). Clone it to `~/dev/skills/` and run `SKILLS_CHECKOUT_DIR="$PWD" bash install.sh` there to wire every skill into your harnesses (Claude Code, Crush, Antigravity, Mistral Vibe) as native, on-demand skills. (Use `install.sh`, not `task install` — the latter is currently broken, skills#7.) Load at task start — not "on demand" but on schedule, before writing code. Browse `~/dev/skills/SKILLS_INDEX.md` for the full list. Shared engineering skills live in the **`mathias/skills`** repo (`git.d-ma.be/mathias/skills`). Load at task start — not "on demand" but on schedule, before writing code. Browse `SKILLS_INDEX.md` for the full list.
**Claude Code** installs the repo as a plugin from the marketplace the same repo hosts. That path also carries the `secret-guard` and `pre-task-ritual` hooks and the brain/gitea/infra MCP servers, which symlinks cannot:
```
/plugin marketplace add https://git.d-ma.be/mathias/skills.git
/plugin install skills@mathias
```
Skills are **namespaced** under Claude Code: load `tdd` as `skills:tdd`. The trigger table below uses bare names — prefix them.
**Crush, Pi and OpenCode** have no plugin system and are wired by `install.sh` as one directory symlink per harness via `~/.agents/skills`. Do not clone the checkout into `~/dev`: that folder is iCloud-shared between the Macs and spans two path roots (`/Users/mathias` vs `/home/mathias` on koala), so a checkout there is a git working tree with two concurrent writers, and any absolute symlink stored in it is broken on one side by construction (skills#23). Leave `SKILLS_CHECKOUT_DIR` at its host-local default.
Check a host with `task doctor` in the skills repo; it also runs automatically at session start via the plugin.
**Skill trigger table — load before starting, not after getting stuck:** **Skill trigger table — load before starting, not after getting stuck:**
-389
View File
@@ -1,389 +0,0 @@
# Cursor rules — auto-generated
# Do not edit. Run: task context:sync
# Agent context — Mathias workspace
<!-- Canonical root context for all AI coding agents.
Lives at: ~/dev/.context/AGENT.md
Applies to every project under ~/dev/ unless overridden.
Run `task context:sync` from ~/dev/ to regenerate harness-specific files.
Project-level context in .context/PROJECT.md layers on top of this. -->
## Who I am
I'm Mathias, a digital product manager and technology consultant based in Sweden.
I build software, research emerging tech, and deliver consulting engagements
for clients under NDA. I work across AI/ML, financial automation, web applications,
and climate/sustainability tech.
## How I work with agents
- I think like a product manager — I care about *why* before *how*
- I want agents to be opinionated and push back, not just execute blindly
- I prefer concise responses; skip ceremony and get to the point
- When I say "build this", I mean production-quality with tests, not a demo
- Ask me before making irreversible changes or adding heavy dependencies
- I work with confidential client data — never send it to cloud APIs unless I explicitly say it's OK
## Behavior rules
These rules apply to every task across every project, regardless of harness.
0. **Pre-task ritual — before ANY implementation (non-negotiable).** Run this before writing a single line:
- **Query the brain** (`brain_query`) for the domain + symptom. If the result changes your approach, surface it before acting. 5 seconds beats 5 hours.
- **Load the relevant skill** — see trigger table in *Engineering Skills* below.
- **Write the failing test first.** Name the test before the function. If the target is untestable (e.g. `main()` wiring), extract the logic into a testable function first. No implementation without a red test.
- **State the observable success criterion** — what specific behavior, output, or passing test proves this is done?
**TDD is non-negotiable.** "Tests pass" is not proof of correctness — only proof the tests ran. Write tests that would catch the bug before writing code that fixes it.
1. **No assumptions.** Don't hide confusion — surface it. Surface tradeoffs explicitly.
Think before coding; if the problem is unclear, ask or state assumptions before acting.
2. **Minimum viable code.** Solve with the smallest change that works. Nothing
speculative, no "while we're here" cleanups, no premature abstractions. Simplicity first.
3. **Surgical changes.** Touch only what the task requires. Leave unrelated code,
files, and formatting alone. Diffs should be small and reviewable.
4. **Goal-driven execution.** Define clear success criteria up front for every task.
Loop — implement, verify, refine — until those criteria are met. Don't claim
completion without evidence (tests pass, command output, observed behavior).
5. **Trunk-Based Development — commit directly to main.** Every commit is one
logical change (one tool, one fix, one test) with passing tests. Main is always
deployable. Never create long-lived feature branches.
**Exception — parallel agents on same repo:** If another agent is known to be
actively working on the same repo simultaneously, create a short-lived branch
(`agent/<description>`), finish the task, and merge to main within the same
session. Do not leave agent branches open between sessions.
**Exception — external contributor or client four-eyes requirement:** Use
PR flow only when a human reviewer outside the project is required. Document
the reason in PROJECT.md.
6. **Close the loop — every substantive task ends with the same ritual.** Shipping
the code is not the end of the task; capturing it is. Run this unprompted:
- **Tag + bump SemVer** on the change (annotated tag; minor for a feature or
new/changed ADR, patch for a fix; docs in the same commit). Check the repo's
actual last tag — stated versions in docs drift stale.
- **Push** main and the tag (CI is the gate).
- **Persist generalizable learnings to the brain** (`brain_write`, wing/hall) —
the reusable patterns and the footguns that would bite anyone again, never
project status. See *Knowledge base — when to write* below.
- **File discovered-but-deferred work as tracker issues** on the project's own
repo — token-budget gaps, recorded ADR limitations, v2 follow-ups. Don't let
"out of scope, recorded" rot in a commit message; make it a ticket with a
source pointer.
- Surface the brain entries and issue numbers in the closing summary so the
trail is auditable.
## Default stack
| Layer | Default | Fallback | Last resort |
|-------|---------|----------|-------------|
| Language | Go | Python | TypeScript, Java, C |
| UI | HTMX + Templ | Server-rendered HTML | React (only if SPA is justified) |
| Build | Task (taskfile.dev) | Make | — |
| Containers | Docker Compose (dev), k3s (prod) | — | — |
| DB | PostgreSQL + sqlc | SQLite | — |
| Search | pgvector (vector), BM25 | Qdrant (when >1M vectors or hybrid retrieval) | — |
| Logging | slog (structured) | — | — |
| Testing | Table-driven, testify | — | — |
| Agents (Go) | google.golang.org/adk + pkg/litellm adapter | — | — |
Exploratory: Rust, Zig — I'll tell you when I want these.
## Code conventions
- **Go style**: golines, gofumpt, golangci-lint
- **Errors**: `fmt.Errorf("operation: %w", err)` — never naked, never log-and-return
- **Naming**: stdlib conventions, no stuttering
- **Architecture**: prefer stdlib over frameworks, constructor injection, env-var config parsed into typed structs
- **Git**: conventional commits (`feat:`, `fix:`, `chore:`), commit directly to main,
one logical change per commit, CI is the quality gate
- **Never**: long-lived feature branches, PRs for solo work, direct push without
passing `task check` locally first
- **Security**: no secrets in code, govulncheck before adding deps, SOPS for encrypted config
- **Dependencies**: prefer stdlib. testify, slog, templ, sqlc, google.golang.org/adk (agent projects only) are pre-approved; anything else needs justification in the commit message
## Secret handling (every harness, every command)
Tool output is persisted: terminal → `~/.claude/projects` transcripts →
claudewatcher → brain/wiki → gitea history. A secret printed once is
searchable forever, and clearing it means rotating the key. So:
1. **Never print, echo, log, or transform a secret to inspect it.** No
`base64`/`xxd`/`cat` of a key, and never pipe a secret through a transform
to defeat `op run`'s output masking (it masks raw values; base64 hides them
from the mask — that exact trick leaked a key on 2026-06-11).
2. **Secrets stay in the subprocess.** Reference them only as env vars consumed
*inside* `op run --env-file ~/.op-env -- <cmd>`. Never place a literal secret
in a command's argv (it lands in the tool call and the transcript).
3. **Existence check without revealing the value:** `[ -n "$X" ] && echo set` —
never `${X:-...}` (returns the value when set) and never echo a substring of it.
4. **Cross-host secrets:** run the secret-consuming command on the host that has
the secret; do not forward a raw key over ssh argv/stdout.
5. If a secret does leak into output, say so immediately and flag it for rotation —
don't bury it.
## Infrastructure
Three machines on Tailscale:
| Machine | Role | Key specs |
|---------|------|-----------|
| koala | GPU inference, heavy compute | RTX 5070, runs k3s + llama-swap + shared postgres18/pgvector |
| iguana | Services, builds | M2 Ultra Mac |
| flamingo | Daily driver, edge | Mac mini, ~/dev is here |
- **Model routing**: LiteLLM in front of llama-swap (local) + cloud APIs (when permitted)
- **Orchestration**: k3s cluster across all three machines
- **Networking**: Tailscale mesh
## Project landscape
All development repos live at `~/dev/` (softlink from `~/Documents/local-dev/`).
Organized in thematic folders:
| Folder | Focus | Count |
|--------|-------|-------|
| `GO/` | Go web frameworks, API integrations, learning projects | ~10 |
| `AI/` | ML research, AI frameworks (FinRL, DSPy, crawl4ai) | ~6 |
| `AGENTS/` | Autonomous agents, coding agents, MCP servers, infra | ~15 |
| `QKX/` | Invoice processing, financial automation, payment systems | ~13 |
| `XT/` | Climate data, sustainability (Klimatkollen, Garbo) | ~2 |
See `~/dev/PROJECT_SUMMARY.md` for detailed descriptions of each project.
### Key active projects
- **super-koala** (`AGENTS/`) — multi-component agent stack with LangGraph, DSPy, MCP
- **azure-tiger** (`QKX/`) — invoice extraction → ISO 20022 payment instructions
- **gocrwl** (`AGENTS/`) — Go web crawler with containerized deployment
- **koala-ai-stack** (`AGENTS/`) — local AI server infrastructure management
- **klimatkollen** (`XT/`) — Swedish municipal climate data platform
## Knowledge base — actively use it
A persistent brain (BM25 search + LLM-synthesised Q&A) survives across sessions,
hosts, and harnesses. It holds 100+ hard-won entries: infra incident postmortems,
Go pitfalls, framework gotchas, design principles, ADRs. **It is not optional
reference material — query it actively, not just when explicitly told.**
### When to query (treat as a reflex)
- **Before** starting a non-trivial task — search for prior art with the symptom
AND the system component ("how did we solve X in Y?"). 5 seconds beats 5 hours.
- **When debugging** — search for the error string, the stack frame, the affected
service. Past you may have already paid this tax.
- **Before adopting** a pattern, library, framework, or model name — check if it
was tried and rejected, or what the integration footguns are.
- **When making architectural decisions** — search for the domain + "ADR" or
"decision" to find prior reasoning before re-deriving it.
- **When a recommendation feels novel** — challenge yourself: "has this been
documented?" The brain often has it.
### When to write
After you discover something that **future-you would forget** and that **isn't
recoverable from the code, git log, or PR description alone**:
- Bugs whose root cause is non-obvious and generalisable beyond this project.
- Framework / library / model-name quirks that bit you and would bite anyone.
- Design principles validated under fire (e.g. "every `_get` needs a `_list`").
- Postmortems for incidents: what broke, why, how diagnosed, what to do next time.
DON'T write project status, sprint progress, PR summaries, or "what I did this
session" — those rot fast and the originals are in git/gitea anyway. Brain
entries that age well are about *why*, *how to avoid*, and *what to do when*.
### How to access (per harness)
| Harness | Query | Write |
|---------|-------|-------|
| **Claude Code, Claude Desktop** | `brain_query` (BM25), `brain_answer` (LLM-synth + sources) MCP tools | `brain_write` MCP tool |
| **Crush, Pi, Antigravity, other MCP-capable** | same MCP server: `ingestion-brain` (via the `mcp__*_brain__*` namespace once authenticated) | same |
| **Anything HTTP-only (curl, scripts)** | `POST https://brain-mcp.d-ma.be/query` with `{"query":"..."}` (auth via `BRAIN_MCP_TOKEN`) | `POST .../write` with `{"content":"...","filename":"..."}` |
| **Browser / human inspection** | `https://git.d-ma.be/mathias/hyperguild` → `knowledge/` and `wiki/` markdown files |
- **Scoping**: defaults to `public` collection; client projects filter to `{client}` + `public`.
- **Routing**: brain_answer's LLM uses berget.ai as primary, iguana ollama as
fallback. Both are configurable in the `supervisor/ingestion-deployment.yaml`
on the koala k3s cluster; don't hardcode local-only model names into the
berget URL (see knowledge entry on namespace mismatches).
### Quick reflex checks
If you find yourself about to say any of these out loud, you owe yourself a brain query first:
- "I think the issue might be..."
- "Let me try X and see..."
- "I'll just write a script to..."
- "This is probably a new bug..."
- "Has anyone done this before?" — *yes, probably, go check.*
## Client work rules
When working on a project tagged with a client name:
1. Never send code, data, or context to cloud APIs — use local models only
2. Never reference other client projects or their data
3. Keep all artifacts within the client's git org / directory
4. Treat everything as confidential unless told otherwise
## Harness-agnostic principles
This context is designed to work with any AI coding tool:
- Claude Code, Cursor, Aider, Open WebUI, Charmbracelet Mods/Crush
- Pi Coding Agent, Mistral Vibe, Antigravity
- Any tool that accepts a system prompt or reads a markdown context file
The canonical source is always `.context/AGENT.md` (root) and `.context/PROJECT.md` (per-project).
Derived files are committed (see *How context propagates* below) so a `git pull` on any host yields full agent context with no setup.
## How context propagates
Canonical sources of truth:
- Universal: `~/dev/.context/AGENT.md` (this file)
- Project: `<repo>/.context/PROJECT.md` (per-repo)
Derived files (committed, regenerated by `task context:sync`):
- `CLAUDE.md`, `AGENTS.md`, `.cursorrules`, `.aider.conventions.md`,
`.context/system-prompt.txt`
Workflow:
1. Edit a canonical file. Run `task context:sync`. Commit canonical and
derived together. Push.
2. On any other host, `git pull` brings both. Claude Code (tree-walking)
uses `CLAUDE.md`; Crush / Pi / Antigravity (cwd-only) use `AGENTS.md`;
Cursor uses `.cursorrules`; Aider uses `.aider.conventions.md`.
3. `task check` runs `context:sync` then asserts `git status --porcelain`
is empty over the derived files (catches both modified-tracked drift
and missing-untracked adapters). A drift fails the check with a
message telling you to stage the regenerated files.
Behavior rules in this file and per-project rules in `PROJECT.md` apply
unconditionally on every host, every harness.
## Engineering Skills
Shared engineering skills live in the **`mathias/skills`** repo (`git.d-ma.be/mathias/skills`). Clone it to `~/dev/skills/` and run `SKILLS_CHECKOUT_DIR="$PWD" bash install.sh` there to wire every skill into your harnesses (Claude Code, Crush, Antigravity, Mistral Vibe) as native, on-demand skills. (Use `install.sh`, not `task install` — the latter is currently broken, skills#7.) Load at task start — not "on demand" but on schedule, before writing code. Browse `~/dev/skills/SKILLS_INDEX.md` for the full list.
**Skill trigger table — load before starting, not after getting stuck:**
| Task type | Load |
|-----------|------|
| Any feature or bug fix | `tdd` |
| Refactor or design | `clean-code` or `solid` |
| Debug | `problem-analysis` |
| Review code or PRs | `code-review` |
| Frame a problem before coding | `problem-analysis` |
---
# Project context
<!-- Canonical project context. Edit this, run `task context:sync`.
Root agent context from ~/dev/.context/AGENT.md is automatically
prepended for harnesses that don't walk the directory tree. -->
## Identity
- **Name**: gitea-mcp
- **Owner**: Mathias
- **Client**: personal
- **Repo**: https://git.d-ma.be/mathias/gitea-mcp
- **Status**: active
## Stack
- **Primary language**: Go
- **UI layer**: HTMX + Templ (when applicable)
- **Fallback languages**: Python, TypeScript (justify in PR if used)
- **Build**: Task (taskfile.dev), not Make
- **Containers**: Docker (compose for dev, k3s for deploy)
- **Target infra**: koala (GPU workloads), iguana (services), flamingo (edge)
## Conventions
### Code style
- Go: follow `golines`, `gofumpt`, `golangci-lint` with project config
- Tests: table-driven, in `_test.go` next to source, `testify` for assertions
- Errors: wrap with `fmt.Errorf("operation: %w", err)`, no naked returns
- Naming: stdlib conventions, no stuttering (`http.Client` not `http.HTTPClient`)
### Architecture preferences
- Prefer standard library over frameworks (net/http over gin/echo)
- Dependency injection via constructor functions, not containers
- Configuration via environment variables, parsed at startup into a typed struct
- Structured logging via `slog`
### Git
- Conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`
- **Trunk-Based Development:** commit directly to main. One logical change per commit.
- Run `task check` locally before every push. CI is the quality gate, not branch protection.
- No feature branches, no PRs for solo/agent work.
- Exception: if a parallel agent session is active on this repo, use a short-lived
`agent/<description>` branch and merge within the same session.
### Security
- No secrets in code, ever — use env vars or SOPS-encrypted files
- Client data never leaves local network unless explicitly cleared
- Dependencies: audit with `govulncheck` before adding
## Knowledge base access
This project can query the shared knowledge base via MCP or HTTP:
- **MCP endpoint**: `mcp://localhost:3100/knowledge`
- **HTTP fallback**: `http://localhost:3100/api/v1/search`
- **Scoping**: queries are filtered to collection `personal` + `public`
## Behavior rules
These rules apply to every task in this project, regardless of harness.
1. **No assumptions.** Don't hide confusion — surface it. Surface tradeoffs explicitly.
Think before coding; if the problem is unclear, ask or state assumptions before acting.
2. **Minimum viable code.** Solve with the smallest change that works. Nothing
speculative, no "while we're here" cleanups, no premature abstractions. Simplicity first.
3. **Surgical changes.** Touch only what the task requires. Leave unrelated code,
files, and formatting alone. Diffs should be small and reviewable.
4. **Goal-driven execution.** Define clear success criteria up front for every task.
Loop — implement, verify, refine — until those criteria are met. Don't claim
completion without evidence (tests pass, command output, observed behavior).
## Agent instructions
When acting as a coding agent on this project:
1. Read this file and all `SKILL.md` files in `.skills/` before starting work
2. Run `task check` before committing (lint + test + vet)
3. If unsure about a convention, check `DECISIONS.md` or ask
4. Never modify files outside the project root without explicit permission
5. When adding a dependency, explain why in the commit message
6. Commit directly to main. Run `task check` before every push. Never create
feature branches unless a parallel agent is simultaneously active on this repo.
7. For client projects: never send code or context to cloud APIs — use local models via LiteLLM
## Current state — v0.2.5 (2026-05-17)
All v0.2 work is complete and deployed. No active sprint.
### What shipped
| Tag | PR | Tools / fixes |
|-----|----|---------------|
| v0.2.2 | #21 | repo_create, repo_update, repo_mirror_push |
| v0.2.3 | #22 | repo_tree, repo_topics_update, file_read dir fix |
| v0.2.4 | #23 | issue_get, release_create, repo_delete |
| v0.2.5 | #26 | repo_update archived+template, create_project_from_template template_name, pr_files_diff loop fix |
Current main: `e31fd3f`. CI green. Deployed via Flux.
### Next up
1. **`hyperguild new-project` v1** — primary next target.
See brain node `adr-new-project-gitea-first-github-mirror` for full flow spec.
2. **Issue #19** — end-to-end mirror flow verification.
`repo_mirror_push` is implemented but the full flow (create repo → add push mirror → verify sync to GitHub) has not been tested manually. Do this before relying on it in production.
+90 -21
View File
@@ -48,14 +48,30 @@ These rules apply to every task across every project, regardless of harness.
logical change (one tool, one fix, one test) with passing tests. Main is always logical change (one tool, one fix, one test) with passing tests. Main is always
deployable. Never create long-lived feature branches. deployable. Never create long-lived feature branches.
**Exception — parallel agents on same repo:** If another agent is known to be **The PR trigger is RISK, not concurrency.** `risk/low` work commits straight to
actively working on the same repo simultaneously, create a short-lived branch main with CI as the gate — that is most work and is unchanged. `risk/medium` and
(`agent/<description>`), finish the task, and merge to main within the same above opens a PR, even solo, even with nobody waiting to review it. This mirrors
session. Do not leave agent branches open between sessions. the tiered merge model in `assessor-loop`
`method/transfer-to-a-regulated-organisation.md` §4.1, so the estate practises
what it sells instead of merging everything on green and claiming otherwise.
**Exception — external contributor or client four-eyes requirement:** Use **Concurrency is NOT a reason to branch.** Measured 2026-08-29 across three real
PR flow only when a human reviewer outside the project is required. Document collisions between parallel sessions: two were not branching problems at all — a
the reason in PROJECT.md. generated artifact written to the same path by both (branching turns that into a
merge conflict on a 33k-line JSON), and uncommitted work in a shared checkout
(a per-session worktree fixes it) — and the third was an ordinary non-fast-forward
push resolved by rebase in seconds. Use a worktree per session on shared repos and
timestamp generated artifacts. Do not reach for a branch.
Branch+PR flow carries documented costs on solo repos: merging auto-closes gated
issues, review comments live on a thread separate from the issue's, and a
merged-and-deleted branch cannot be amended (brain:
`gitea-pr-merge-autoclose-and-review-comment-footguns`). Those are the price of
the `risk/medium`+ lane — paid deliberately, not incurred by accident.
**Exception — external contributor or client four-eyes requirement:** Use PR flow
when a human reviewer outside the project is required. Document the reason in
PROJECT.md.
6. **Close the loop — every substantive task ends with the same ritual.** Shipping 6. **Close the loop — every substantive task ends with the same ritual.** Shipping
the code is not the end of the task; capturing it is. Run this unprompted: the code is not the end of the task; capturing it is. Run this unprompted:
@@ -73,6 +89,26 @@ These rules apply to every task across every project, regardless of harness.
- Surface the brain entries and issue numbers in the closing summary so the - Surface the brain entries and issue numbers in the closing summary so the
trail is auditable. trail is auditable.
**Chat is not a durable store. Never rely on Mathias remembering anything.**
Every gap, caveat, risk, open question and next step must land in one of four
places before the turn ends: **the brain**, **a repo** (code, docs, ADR),
**a tracker issue**, or **a plan file**. A finding stated only in conversation is
lost the moment the session closes — and this estate has repeatedly proved that a
remembered convention is an unreliable control.
This applies hardest to what is tempting to leave as a closing remark: "worth
knowing X isn't enforced yet", "this is a convention, not a gate", "we should
revisit Y". Those are exactly the items that need a locator. If it is worth
saying in a summary, it is worth writing where it can be found again without you.
## CAD sprint patterns
Two CAD sprint archetypes (defs: brain `wiki/homelab/decisions/continuous-agentic-development-cad-concept-2026-06-16`):
- **Inception Sprint** — birth a new repo. Oath-gated close; tag only once every clause holds. Brain: `.../inception-sprint-and-oath`.
- **Sweep Sprint** — clear an existing backlog. Parallel read-only research per issue → sequential one-issue-per-commit execution. Brain: `.../sweep-sprint-and-oath`.
Shared: decisions that are yours, not the agent's, surface as `DECISION NEEDED` (question/options/recommendation/blocks) — ask live if you're present, else comment and wait, never act past it. Live-breaking regressions get `git revert`, never forward-fixed; host-network work skips even read-only research (solo, synchronous, you present). Rule 6's closing ritual only fires once every Oath clause genuinely holds — blocked = descoped-and-tracked, never marked done.
## Default stack ## Default stack
| Layer | Default | Fallback | Last resort | | Layer | Default | Fallback | Last resort |
@@ -83,7 +119,7 @@ These rules apply to every task across every project, regardless of harness.
| Containers | Docker Compose (dev), k3s (prod) | — | — | | Containers | Docker Compose (dev), k3s (prod) | — | — |
| DB | PostgreSQL + sqlc | SQLite | — | | DB | PostgreSQL + sqlc | SQLite | — |
| Search | pgvector (vector), BM25 | Qdrant (when >1M vectors or hybrid retrieval) | — | | Search | pgvector (vector), BM25 | Qdrant (when >1M vectors or hybrid retrieval) | — |
| Logging | slog (structured) | — | — | | Logging | slog (structured) | stdlib `logging` w/ structured `extra=` (or structlog) | — |
| Testing | Table-driven, testify | — | — | | Testing | Table-driven, testify | — | — |
| Agents (Go) | google.golang.org/adk + pkg/litellm adapter | — | — | | Agents (Go) | google.golang.org/adk + pkg/litellm adapter | — | — |
@@ -92,7 +128,12 @@ Exploratory: Rust, Zig — I'll tell you when I want these.
## Code conventions ## Code conventions
- **Go style**: golines, gofumpt, golangci-lint - **Go style**: golines, gofumpt, golangci-lint
- **Errors**: `fmt.Errorf("operation: %w", err)` — never naked, never log-and-return - **Python style** (fallback language): ruff (format+lint, one tool), mypy --strict (non-negotiable,
matches Go's static typing discipline), pytest + pytest-cov (table-driven via
`@pytest.mark.parametrize`), uv (venv+deps+lock, one tool), pydantic-settings (typed env-var config
— same principle as Go's typed structs), src-layout + `pyproject.toml` only (no `setup.py`)
- **Errors**: `fmt.Errorf("operation: %w", err)` — never naked, never log-and-return.
Python: `raise X from e` (exception chaining, same principle) — never bare `except`, never silent `pass`
- **Naming**: stdlib conventions, no stuttering - **Naming**: stdlib conventions, no stuttering
- **Architecture**: prefer stdlib over frameworks, constructor injection, env-var config parsed into typed structs - **Architecture**: prefer stdlib over frameworks, constructor injection, env-var config parsed into typed structs
- **Git**: conventional commits (`feat:`, `fix:`, `chore:`), commit directly to main, - **Git**: conventional commits (`feat:`, `fix:`, `chore:`), commit directly to main,
@@ -130,10 +171,10 @@ Three machines on Tailscale:
|---------|------|-----------| |---------|------|-----------|
| koala | GPU inference, heavy compute | RTX 5070, runs k3s + llama-swap + shared postgres18/pgvector | | koala | GPU inference, heavy compute | RTX 5070, runs k3s + llama-swap + shared postgres18/pgvector |
| iguana | Services, builds | M2 Ultra Mac | | iguana | Services, builds | M2 Ultra Mac |
| flamingo | Daily driver, edge | Mac mini, ~/dev is here | | flamingo | Daily driver, edge | Apple Silicon Mac, ~/dev is here |
- **Model routing**: LiteLLM in front of llama-swap (local) + cloud APIs (when permitted) - **Model routing**: LiteLLM in front of llama-swap (local) + cloud APIs (when permitted)
- **Orchestration**: k3s cluster across all three machines - **Orchestration**: k3s on koala only — single control-plane node, no workers (verified 2026-08-30). iguana and flamingo are on the tailnet but are not cluster members
- **Networking**: Tailscale mesh - **Networking**: Tailscale mesh
## Project landscape ## Project landscape
@@ -229,10 +270,11 @@ When working on a project tagged with a client name:
## Harness-agnostic principles ## Harness-agnostic principles
This context is designed to work with any AI coding tool: Supported harnesses (2026-09-01) — Cursor and Aider were dropped; do not
- Claude Code, Cursor, Aider, Open WebUI, Charmbracelet Mods/Crush re-add adapters for them:
- Pi Coding Agent, Mistral Vibe, Antigravity - Claude Code, Antigravity, OpenCode, Mistral Vibe, Crush, Pi Coding Agent
- Any tool that accepts a system prompt or reads a markdown context file - Open WebUI, Charmbracelet Mods, or any tool that takes a system prompt string
- Any other tool that reads `AGENTS.md`, which is the de facto convention
The canonical source is always `.context/AGENT.md` (root) and `.context/PROJECT.md` (per-project). The canonical source is always `.context/AGENT.md` (root) and `.context/PROJECT.md` (per-project).
Derived files are committed (see *How context propagates* below) so a `git pull` on any host yields full agent context with no setup. Derived files are committed (see *How context propagates* below) so a `git pull` on any host yields full agent context with no setup.
@@ -244,15 +286,29 @@ Canonical sources of truth:
- Project: `<repo>/.context/PROJECT.md` (per-repo) - Project: `<repo>/.context/PROJECT.md` (per-repo)
Derived files (committed, regenerated by `task context:sync`): Derived files (committed, regenerated by `task context:sync`):
- `CLAUDE.md`, `AGENTS.md`, `.cursorrules`, `.aider.conventions.md`, - `CLAUDE.md`, `AGENTS.md`, `.context/system-prompt.txt`
`.context/system-prompt.txt`
Repos still carrying `.cursorrules`, `.aider.conventions.md` or
`.aider.conf.yml` are stale: nothing regenerates them. Delete them the next time
you touch the repo, along with their entries in that repo's `task check`
assertion.
Workflow: Workflow:
1. Edit a canonical file. Run `task context:sync`. Commit canonical and 1. Edit a canonical file. Run `task context:sync`. Commit canonical and
derived together. Push. derived together. Push.
2. On any other host, `git pull` brings both. Claude Code (tree-walking) 2. On any other host, `git pull` brings both. Which file a harness reads does
uses `CLAUDE.md`; Crush / Pi / Antigravity (cwd-only) use `AGENTS.md`; NOT split cleanly by lookup strategy, so do not reason from the strategy:
Cursor uses `.cursorrules`; Aider uses `.aider.conventions.md`. - `CLAUDE.md` — Claude Code (walks up the tree)
- `AGENTS.md` — Crush, Pi, Antigravity (cwd-only), OpenCode (project root,
global at `~/.config/opencode/AGENTS.md`), and Mistral Vibe, which **walks
up** from cwd and takes the first `AGENTS.md` it finds
(user-level `~/.vibe/AGENTS.md`)
- `.context/system-prompt.txt` — Open WebUI, Mods, raw API calls; not read by
any harness, it is for pasting
Vibe tree-walks for `AGENTS.md`, so "tree-walkers use CLAUDE.md" is false.
Each repo's `AGENTS.md` embeds the canonical and is the first one found from
inside that repo, so this works — but by construction, not by luck.
3. `task check` runs `context:sync` then asserts `git status --porcelain` 3. `task check` runs `context:sync` then asserts `git status --porcelain`
is empty over the derived files (catches both modified-tracked drift is empty over the derived files (catches both modified-tracked drift
and missing-untracked adapters). A drift fails the check with a and missing-untracked adapters). A drift fails the check with a
@@ -263,7 +319,20 @@ unconditionally on every host, every harness.
## Engineering Skills ## Engineering Skills
Shared engineering skills live in the **`mathias/skills`** repo (`git.d-ma.be/mathias/skills`). Clone it to `~/dev/skills/` and run `SKILLS_CHECKOUT_DIR="$PWD" bash install.sh` there to wire every skill into your harnesses (Claude Code, Crush, Antigravity, Mistral Vibe) as native, on-demand skills. (Use `install.sh`, not `task install` — the latter is currently broken, skills#7.) Load at task start — not "on demand" but on schedule, before writing code. Browse `~/dev/skills/SKILLS_INDEX.md` for the full list. Shared engineering skills live in the **`mathias/skills`** repo (`git.d-ma.be/mathias/skills`). Load at task start — not "on demand" but on schedule, before writing code. Browse `SKILLS_INDEX.md` for the full list.
**Claude Code** installs the repo as a plugin from the marketplace the same repo hosts. That path also carries the `secret-guard` and `pre-task-ritual` hooks and the brain/gitea/infra MCP servers, which symlinks cannot:
```
/plugin marketplace add https://git.d-ma.be/mathias/skills.git
/plugin install skills@mathias
```
Skills are **namespaced** under Claude Code: load `tdd` as `skills:tdd`. The trigger table below uses bare names — prefix them.
**Crush, Pi and OpenCode** have no plugin system and are wired by `install.sh` as one directory symlink per harness via `~/.agents/skills`. Do not clone the checkout into `~/dev`: that folder is iCloud-shared between the Macs and spans two path roots (`/Users/mathias` vs `/home/mathias` on koala), so a checkout there is a git working tree with two concurrent writers, and any absolute symlink stored in it is broken on one side by construction (skills#23). Leave `SKILLS_CHECKOUT_DIR` at its host-local default.
Check a host with `task doctor` in the skills repo; it also runs automatically at session start via the plugin.
**Skill trigger table — load before starting, not after getting stuck:** **Skill trigger table — load before starting, not after getting stuck:**
+2 -4
View File
@@ -27,12 +27,12 @@ tasks:
echo "✓ context sync: skipped in CI" echo "✓ context sync: skipped in CI"
else else
bash scripts/context-sync.sh bash scripts/context-sync.sh
drift=$(git status --porcelain -- AGENTS.md CLAUDE.md .cursorrules .aider.conventions.md .context/system-prompt.txt 2>/dev/null) drift=$(git status --porcelain -- AGENTS.md CLAUDE.md .context/system-prompt.txt 2>/dev/null)
if [ -n "$drift" ]; then if [ -n "$drift" ]; then
echo "ERROR: derived adapters drifted from canonical context." >&2 echo "ERROR: derived adapters drifted from canonical context." >&2
echo "$drift" >&2 echo "$drift" >&2
echo "" >&2 echo "" >&2
echo "Run: git add AGENTS.md CLAUDE.md .cursorrules .aider.conventions.md .context/system-prompt.txt" >&2 echo "Run: git add AGENTS.md CLAUDE.md .context/system-prompt.txt" >&2
echo " git commit -m 'chore: re-sync context adapters'" >&2 echo " git commit -m 'chore: re-sync context adapters'" >&2
exit 1 exit 1
fi fi
@@ -58,5 +58,3 @@ tasks:
cmds: [bash scripts/context-sync.sh claude] cmds: [bash scripts/context-sync.sh claude]
context:sync:agents: context:sync:agents:
cmds: [bash scripts/context-sync.sh agents] cmds: [bash scripts/context-sync.sh agents]
context:sync:cursor:
cmds: [bash scripts/context-sync.sh cursor]
+5 -28
View File
@@ -5,6 +5,11 @@
# For Claude Code: generates project-only CLAUDE.md (it inherits root via tree walk) # For Claude Code: generates project-only CLAUDE.md (it inherits root via tree walk)
# For everything else: concatenates root AGENT.md + project PROJECT.md # For everything else: concatenates root AGENT.md + project PROJECT.md
# #
# Cursor and Aider adapters removed 2026-09-01 — neither is a supported harness
# any more. Supported: Claude Code, Antigravity, OpenCode, Mistral Vibe, Crush,
# Pi. All read CLAUDE.md or AGENTS.md; system-prompt.txt remains for Open WebUI,
# Mods and raw API use.
#
# Usage: ./scripts/context-sync.sh [--force] [adapter...] # Usage: ./scripts/context-sync.sh [--force] [adapter...]
# Task: task context:sync # Task: task context:sync
# #
@@ -95,30 +100,6 @@ generate_agents() {
echo " → AGENTS.md (root + project; Crush, Pi, Antigravity)" echo " → AGENTS.md (root + project; Crush, Pi, Antigravity)"
} }
# ── Cursor ───────────────────────────────────────────────────
generate_cursor() {
{
echo "# Cursor rules — auto-generated"
echo "# Do not edit. Run: task context:sync"
echo ""
root_block
cat "$PROJECT_FILE"
} > .cursorrules
echo " → .cursorrules (root + project)"
}
# ── Aider ────────────────────────────────────────────────────
generate_aider() {
{ root_block; cat "$PROJECT_FILE"; } > .aider.conventions.md
if [ ! -f .aider.conf.yml ]; then
cat > .aider.conf.yml << 'YAML'
read: .aider.conventions.md
auto-commits: false
YAML
fi
echo " → .aider.conventions.md (root + project)"
}
# ── Generic system prompt (Open WebUI, Mods, etc.) ────────── # ── Generic system prompt (Open WebUI, Mods, etc.) ──────────
generate_system_prompt() { generate_system_prompt() {
{ {
@@ -180,8 +161,6 @@ echo "Syncing project context from $PROJECT_FILE..."
if [ ${#ADAPTERS[@]} -eq 0 ]; then if [ ${#ADAPTERS[@]} -eq 0 ]; then
generate_claude generate_claude
generate_agents generate_agents
generate_cursor
generate_aider
generate_system_prompt generate_system_prompt
generate_mcp generate_mcp
else else
@@ -189,8 +168,6 @@ else
case "$adapter" in case "$adapter" in
claude) generate_claude ;; claude) generate_claude ;;
agents) generate_agents ;; agents) generate_agents ;;
cursor) generate_cursor ;;
aider) generate_aider ;;
prompt|system|openwebui|owui|generic) generate_system_prompt ;; prompt|system|openwebui|owui|generic) generate_system_prompt ;;
mcp) generate_mcp ;; mcp) generate_mcp ;;
*) echo "Unknown adapter: $adapter" >&2; exit 1 ;; *) echo "Unknown adapter: $adapter" >&2; exit 1 ;;