From bdfa1e6cf11896ad194fbfb0c0973fd8dc1c5af9 Mon Sep 17 00:00:00 2001 From: mathias Date: Tue, 12 May 2026 15:22:12 +0000 Subject: [PATCH 1/6] feat: add context-sync script --- scripts/context-sync.sh | 201 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 scripts/context-sync.sh diff --git a/scripts/context-sync.sh b/scripts/context-sync.sh new file mode 100644 index 0000000..4f7300e --- /dev/null +++ b/scripts/context-sync.sh @@ -0,0 +1,201 @@ +#!/usr/bin/env bash +# Generates harness-specific context files from .context/PROJECT.md +# Project-level script — run from a project directory. +# +# For Claude Code: generates project-only CLAUDE.md (it inherits root via tree walk) +# For everything else: concatenates root AGENT.md + project PROJECT.md +# +# Usage: ./scripts/context-sync.sh [--force] [adapter...] +# Task: task context:sync +# +# Override root context: ROOT_CONTEXT=~/dev/.context/AGENT.md ./scripts/context-sync.sh + +set -euo pipefail + +# Parse --force flag and collect adapter names separately +FORCE=false +ADAPTERS=() +for _arg in "$@"; do + case "$_arg" in + --force) FORCE=true ;; + *) ADAPTERS+=("$_arg") ;; + esac +done + +PROJECT_FILE=".context/PROJECT.md" + +# Walk up to find root .context/AGENT.md +find_root_context() { + local dir + dir="$(pwd)" + while [ "$dir" != "/" ]; do + dir="$(dirname "$dir")" + if [ -f "$dir/.context/AGENT.md" ]; then + echo "$dir/.context/AGENT.md" + return + fi + done + echo "" +} + +ROOT_CONTEXT="${ROOT_CONTEXT:-$(find_root_context)}" + +if [ ! -f "$PROJECT_FILE" ]; then + echo "Error: $PROJECT_FILE not found. Are you in a project root?" + exit 1 +fi + +# Pre-flight: reject unfilled {{...}} placeholders unless --force +if [ "$FORCE" = false ]; then + _placeholders=$(grep -n '{{[^}]*}}' "$PROJECT_FILE" 2>/dev/null || true) + if [ -n "$_placeholders" ]; then + echo "Error: unfilled placeholders in $PROJECT_FILE:" >&2 + while IFS= read -r _match; do + _lineno="${_match%%:*}" + _content="${_match#*:}" + _token=$(printf '%s' "$_content" | grep -o '{{[^}]*}}' | head -1) + echo " $PROJECT_FILE:$_lineno: unfilled placeholder $_token" >&2 + done <<< "$_placeholders" + echo "" >&2 + echo "Fill these placeholders, then re-run: task context:sync" >&2 + echo "To bypass validation: bash scripts/context-sync.sh --force" >&2 + exit 1 + fi +fi + +if [ -n "$ROOT_CONTEXT" ] && [ -f "$ROOT_CONTEXT" ]; then + echo " Root context: $ROOT_CONTEXT" +else + echo " No root AGENT.md found (project context only)" +fi + +# Emit root context + separator +root_block() { + if [ -n "$ROOT_CONTEXT" ] && [ -f "$ROOT_CONTEXT" ]; then + cat "$ROOT_CONTEXT" + echo "" + echo "---" + echo "" + fi +} + +# ── Claude Code ────────────────────────────────────────────── +# Claude Code walks up the tree — it finds ~/dev/CLAUDE.md automatically. +# Project-level CLAUDE.md only needs project-specific context. +generate_claude() { + cat "$PROJECT_FILE" > CLAUDE.md + echo " → CLAUDE.md (project-only; Claude Code inherits root)" +} + +# ── AGENTS.md (Crush, Pi, Antigravity) ────────────────────── +# These tools read AGENTS.md from cwd but don't walk up. +# Concatenate root + project. +generate_agents() { + { root_block; cat "$PROJECT_FILE"; } > AGENTS.md + 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.) ────────── +generate_system_prompt() { + { + echo "You are a coding assistant working on a specific project." + echo "Follow all conventions from both the root agent context and project context." + echo "" + echo "---" + echo "" + root_block + cat "$PROJECT_FILE" + echo "" + echo "---" + } > .context/system-prompt.txt + echo " → .context/system-prompt.txt (root + project)" +} + +# ── MCP config ─────────────────────────────────────────────── +generate_mcp() { + # Ensure baseline file exists with project-specific knowledge server + if [ ! -f .context/mcp.json ]; then + cat > .context/mcp.json << 'JSON' +{ + "mcpServers": { + "knowledge": { + "url": "http://localhost:3100/mcp", + "description": "Project knowledge base — vector + graph retrieval" + } + } +} +JSON + fi + + # Merge root mcp-servers.json if found alongside root AGENT.md + local root_mcp="" + if [ -n "$ROOT_CONTEXT" ] && [ -f "$ROOT_CONTEXT" ]; then + local candidate + candidate="$(dirname "$ROOT_CONTEXT")/mcp-servers.json" + [ -f "$candidate" ] && root_mcp="$candidate" + fi + + if [ -z "$root_mcp" ]; then + echo " → .context/mcp.json (exists, no root mcp-servers.json found)" + return + fi + + # Root servers take precedence over project entries on key conflict + local root_servers count updated + root_servers=$(jq '.servers' "$root_mcp") + count=$(printf '%s' "$root_servers" | jq 'keys | length') + updated=$(jq --argjson root "$root_servers" \ + '.mcpServers = (.mcpServers + $root)' \ + .context/mcp.json) + printf '%s\n' "$updated" > .context/mcp.json + echo " → .context/mcp.json (merged $count root servers)" +} + +echo "Syncing project context from $PROJECT_FILE..." + +if [ ${#ADAPTERS[@]} -eq 0 ]; then + generate_claude + generate_agents + generate_cursor + generate_aider + generate_system_prompt + generate_mcp +else + for adapter in "${ADAPTERS[@]}"; do + case "$adapter" in + claude) generate_claude ;; + agents) generate_agents ;; + cursor) generate_cursor ;; + aider) generate_aider ;; + prompt|system|openwebui|owui|generic) generate_system_prompt ;; + mcp) generate_mcp ;; + *) echo "Unknown adapter: $adapter" >&2; exit 1 ;; + esac + done +fi + +echo "Done." -- 2.49.1 From 6bc37bc20a368349a51c06b00fc5d2fbbfaf5026 Mon Sep 17 00:00:00 2001 From: mathias Date: Tue, 12 May 2026 15:22:24 +0000 Subject: [PATCH 2/6] feat: add go-patterns skill --- .skills/go-patterns/SKILL.md | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .skills/go-patterns/SKILL.md diff --git a/.skills/go-patterns/SKILL.md b/.skills/go-patterns/SKILL.md new file mode 100644 index 0000000..e423120 --- /dev/null +++ b/.skills/go-patterns/SKILL.md @@ -0,0 +1,42 @@ +--- +name: go-patterns +description: Go project patterns — endpoint checklist, error handling, HTMX responses, dependency policy. Use when writing Go code, adding endpoints, or reviewing Go PRs. +--- + +# Go project patterns + +## New endpoint checklist +1. Define request/response types in `types.go` +2. Write handler in `handlers.go` using `http.HandlerFunc` +3. Add route in `routes.go` +4. Write table-driven test in `handlers_test.go` +5. Run `task check` before committing + +## Error handling pattern +```go +if err != nil { + return fmt.Errorf("descriptiveOperation: %w", err) +} +``` +Never log and return — do one or the other. + +## HTMX response pattern +```go +func (h *Handler) ListItems(w http.ResponseWriter, r *http.Request) { + items, err := h.store.List(r.Context()) + if err != nil { + http.Error(w, "failed to list items", http.StatusInternalServerError) + return + } + if r.Header.Get("HX-Request") == "true" { + h.templates.Render(w, "items/_list", items) + return + } + h.templates.Render(w, "items/index", items) +} +``` + +## Dependency policy +- Prefer stdlib: `net/http`, `encoding/json`, `database/sql` +- Allowed without justification: `testify`, `slog`, `templ`, `sqlc` +- Needs justification in commit message: anything else -- 2.49.1 From aa87f2a65f1a1c0cd1cb5850d6050a6060bdfe56 Mon Sep 17 00:00:00 2001 From: mathias Date: Tue, 12 May 2026 15:22:31 +0000 Subject: [PATCH 3/6] feat: add htmx-patterns skill --- .skills/htmx-patterns/SKILL.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .skills/htmx-patterns/SKILL.md diff --git a/.skills/htmx-patterns/SKILL.md b/.skills/htmx-patterns/SKILL.md new file mode 100644 index 0000000..3c28a68 --- /dev/null +++ b/.skills/htmx-patterns/SKILL.md @@ -0,0 +1,31 @@ +--- +name: htmx-patterns +description: HTMX conventions — default attributes, form patterns, validation errors, hypermedia-first API design. Use when writing HTMX templates or Go handlers that return HTML fragments. +--- + +# HTMX patterns + +## Default attributes +Always include on interactive elements: +- `hx-indicator` for loading states +- `hx-swap="innerHTML"` as default (explicit over implicit) +- `hx-target` pointing to a specific ID, never `this` in production + +## Form pattern +```html +
+ + + ... +
+``` + +## Server-sent validation errors +Return 422 with the error fragment, swap into the form's error container: +```html +hx-target-422="#form-errors" +``` + +## Prefer hypermedia over JSON +If the endpoint returns data for display, return an HTML fragment. +Only use JSON for machine-to-machine APIs or when a non-browser client needs it. -- 2.49.1 From e250cb2d4c6ce6371e39a24d53ee9b40c13ec323 Mon Sep 17 00:00:00 2001 From: mathias Date: Tue, 12 May 2026 15:22:34 +0000 Subject: [PATCH 4/6] feat: add go-patterns context skill --- .context/skills/go-patterns.md | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .context/skills/go-patterns.md diff --git a/.context/skills/go-patterns.md b/.context/skills/go-patterns.md new file mode 100644 index 0000000..cd87180 --- /dev/null +++ b/.context/skills/go-patterns.md @@ -0,0 +1,37 @@ +# Skill: Go project patterns + +## New endpoint checklist +1. Define request/response types in `types.go` +2. Write handler in `handlers.go` using `http.HandlerFunc` +3. Add route in `routes.go` +4. Write table-driven test in `handlers_test.go` +5. Run `task check` before committing + +## Error handling pattern +```go +if err != nil { + return fmt.Errorf("descriptiveOperation: %w", err) +} +``` +Never log and return — do one or the other. + +## HTMX response pattern +```go +func (h *Handler) ListItems(w http.ResponseWriter, r *http.Request) { + items, err := h.store.List(r.Context()) + if err != nil { + http.Error(w, "failed to list items", http.StatusInternalServerError) + return + } + if r.Header.Get("HX-Request") == "true" { + h.templates.Render(w, "items/_list", items) + return + } + h.templates.Render(w, "items/index", items) +} +``` + +## Dependency policy +- Prefer stdlib: `net/http`, `encoding/json`, `database/sql` +- Allowed without justification: `testify`, `slog`, `templ`, `sqlc` +- Needs justification in commit message: anything else -- 2.49.1 From 04088b586fe6aee2c83602c01e06c9a62b5a6479 Mon Sep 17 00:00:00 2001 From: mathias Date: Tue, 12 May 2026 15:22:37 +0000 Subject: [PATCH 5/6] feat: add htmx-patterns context skill --- .context/skills/htmx-patterns.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .context/skills/htmx-patterns.md diff --git a/.context/skills/htmx-patterns.md b/.context/skills/htmx-patterns.md new file mode 100644 index 0000000..fe2f224 --- /dev/null +++ b/.context/skills/htmx-patterns.md @@ -0,0 +1,26 @@ +# Skill: HTMX patterns + +## Default attributes +Always include on interactive elements: +- `hx-indicator` for loading states +- `hx-swap="innerHTML"` as default (explicit over implicit) +- `hx-target` pointing to a specific ID, never `this` in production + +## Form pattern +```html +
+ + + ... +
+``` + +## Server-sent validation errors +Return 422 with the error fragment, swap into the form's error container: +```html +hx-target-422="#form-errors" +``` + +## Prefer hypermedia over JSON +If the endpoint returns data for display, return an HTML fragment. +Only use JSON for machine-to-machine APIs or when a non-browser client needs it. -- 2.49.1 From 855b39e134df4023ed9d8dd65674e65f23d196aa Mon Sep 17 00:00:00 2001 From: mathias Date: Tue, 12 May 2026 15:22:42 +0000 Subject: [PATCH 6/6] feat: add context:sync tasks --- Taskfile.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Taskfile.yml b/Taskfile.yml index 9e94cdd..23e9e31 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -24,3 +24,14 @@ tasks: - golangci-lint run ./... - go vet ./... - go test ./... -race -count=1 + + context:sync: + desc: Regenerate all harness-specific context files + cmds: + - bash scripts/context-sync.sh + context:sync:claude: + cmds: [bash scripts/context-sync.sh claude] + context:sync:agents: + cmds: [bash scripts/context-sync.sh agents] + context:sync:cursor: + cmds: [bash scripts/context-sync.sh cursor] -- 2.49.1