diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1b0db9f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,79 @@ +# AGENTS.md — ~/.omp command center + +System-configuration command center for Frank's fleet: agent config, knowledge base, +small system utilities, and (soon) dotfiles. You — the reader — are probably an agent +running on Dankware with this repo as your working directory. Read +`omp-docs/index.md` before you touch anything. This file is the operating contract; +`omp-docs/` is the knowledge base (OKF v0.1 format: directory of markdown files +with YAML frontmatter, spec at `omp-docs/references/okf-spec.md`). + +## What this repo is + +- The live agent-config directory AND a git repo pushed to Gitea + (`dank/command-center` on the Dankclaw instance). There is no staging copy: + **the checkout is the deployment.** Branch switches swap live config files out + from under the running agent — keep feature branches short-lived, and never end + a work session checked out on anything but `develop` or `main`. +- Contents that belong here: `AGENTS.md`, `omp-docs/` (knowledge base), + `scripts/` (small system utilities), `agent/` + `plugins/` (harness config and + plugin manifests). Everything else under this directory is runtime state and is + gitignored on principle — see `.gitignore`. History must mean "the configuration + changed", not "the agent breathed". + +## Git-flow SDLC (full flow, from day one) + +- Work item = Gitea issue on `dank/command-center` (Gitea at 192.168.0.143:3000, + owner `dank`). Small fixes may go straight to a branch; anything with more than + one moving part gets an issue first. +- Branch naming: `/-`, type ∈ feat|fix|docs|chore. + Release cuts: `release/X.Y.Z` from `develop`; hotfixes: `hotfix/` from `main`. +- **Merge, never rebase**, when updating a branch against a moved main — Frank + dislikes the history rewrite. **Never merge a PR without an explicit OK from Frank.** +- Merge into `develop` with `--no-ff` so feature arcs stay visible in history. +- Releases: cut `release/X.Y.Z` from `develop`, verify, merge to `main`, tag + `vX.Y.Z`, back-merge `main` into `develop`. Tags are the deploy markers every + other machine pulls to. +- Trash convention: never `rm` anything user-authored — move it to `.AGENTTRASH/` + (gitignored). Deletion is a pruning session with Frank present. + +## Secret gate (HARD rule) + +The pre-commit hook (`.githooks/pre-commit`, enabled via +`git config core.hooksPath .githooks`) scans every staged diff for live-secret +shapes (`sk-…`, `ghp_…`, `github_pat_…`, `glpat-…`, `xox…`, `AKIA…`, PEM headers, +JWTs) and blocks the commit. Quarantine list — never tracked, ever: `auth.json`, +`.env`, `trust.json`, `*.pem`, `*.key`, `identity*`. Deliberate override: +`ALLOW_SECRETS=1 git commit …`. Remember why: a secret in a tracked file is a +secret in every agent's context window on every machine that pulls this repo. + +## Machine facts (verify before trusting) + +| box | role | GPU | notes | +|-----|------|-----|-------| +| Dankware | dev laptop, never runs inference | Arc iGPU + RTX 4070 Max-Q (dGPU, non-unified) | 22 threads; mainline llama.cpp at `~/GitPackages/llama.cpp/build/bin/`; this repo lives here at `~/.omp` | +| Dankclaw | bench target, runs the large serving model | Strix Halo APU — **unified memory** | ROCmFPX fork at `~/GitPackages/ROCmFPX/build-qwen38/bin/`; prod service `frank-default-large`, port 7770; ssh: `ssh dankclaw` | +| Dankserver | FreeToken box | RTX 3090 dGPU — non-unified | runs `frank-default-small`; ssh: `ssh dankserver` | + +## Environment gotchas + +- Gitea API needs a token: `GITEA_TOKEN` env (gitignored `.env`). No `tea` CLI + installed anywhere. Remote URLs embed the token locally in `.git/config` — that + file is never pushed; it is not a substitute for the secret gate. +- Passwordless sudo (`sudo -n`) works on Dankclaw and Dankserver for systemctl + verbs only; user units need no sudo at all. +- SSH into both servers is BatchMode-capable from here: + `ssh -o BatchMode=yes dankclaw ''` — read-only probes are + always allowed; state changes on remote boxes are not, without a per-run OK. +- `frank-default-large` on Dankclaw is a **production service** shared with this + repo's operator: never stop, mask, or restart it (or any production service on + any box) without an explicit, current, per-run OK from Frank. + +## File-organization doctrine (why this repo won't rot like ~/.pi did) + +- One concept, one file; directories get an `index.md` listing their contents — + that's the OKF contract, and it is also the anti-rot mechanism: nothing may + appear in a directory that its `index.md` doesn't list. +- Root of the repo is capped: config, docs, scripts, harness dirs. A new + top-level entry requires a commit message that justifies itself. +- Runtime state (dbs, logs, caches, sessions) is gitignored on principle — if you + feel the urge to commit one of those, stop and reread this section. diff --git a/scripts/kb-lint.sh b/scripts/kb-lint.sh new file mode 100755 index 0000000..9fe0e85 --- /dev/null +++ b/scripts/kb-lint.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# kb-lint.sh — OKF v0.1 conformance + link-integrity lint for a knowledge bundle. +# Usage: scripts/kb-lint.sh (default: omp-docs) +# Exits 0 when the KB is conformant; prints one line per violation otherwise. +set -uo pipefail +KB="$(cd "${1:-omp-docs}" 2>/dev/null && pwd)" || { echo "kb-lint: no such dir: ${1:-omp-docs}" >&2; exit 2; } + +python3 - "$KB" <<'PY' +import re, sys +from pathlib import Path +kb = Path(sys.argv[1]) +violations = [] + +for p in sorted(kb.rglob("*.md")): + rel = p.relative_to(kb) + text = p.read_text(errors="replace") + if p.name == "index.md": + # OKF rule: index files are directory listings and carry NO frontmatter. + if text.startswith("---"): + violations.append(f"{rel}: index.md must not have frontmatter (OKF spec)") + continue + if not text.startswith("---"): + violations.append(f"{rel}: missing frontmatter") + continue + end = text.find("\n---", 3) + fm = text[3:end] if end != -1 else "" + if not re.search(r"^type:", fm, re.M): + violations.append(f"{rel}: frontmatter lacks required 'type:'") + +# link integrity: fenced code blocks hold example paths, not live links — strip first. +# Vendored spec is exempt: its example paths are illustrations, not live links. +for p in sorted(kb.rglob("*.md")): + if str(p.relative_to(kb)) == "references/okf-spec.md": + continue + text = re.sub(r"^```[^\n]*$^```[^\n]*$", "", p.read_text(errors="replace"), flags=re.M) + rel = p.relative_to(kb) + for m in re.finditer(r"\]\((?!https?:|#)([^)#\s]+\.md)", text): + target = (p.parent / m.group(1)).resolve() if not m.group(1).startswith("/") else (kb / m.group(1).lstrip("/")) + if not Path(target).is_file(): + violations.append(f"{rel}: broken relative link -> {m.group(1)}") + for m in re.finditer(r"\[\[([^\]|#]+)", text): + # wikilinks resolve by filename anywhere in the KB (Obsidian semantics) + name = m.group(1).strip().rsplit("/", 1)[-1] + if not list(kb.rglob(name + ".md")): + violations.append(f"{rel}: broken wikilink -> [[{m.group(1).strip()}]]") + +for v in violations: + print(v) +print(f"kb-lint: {len(violations)} violation(s)") +sys.exit(1 if violations else 0) +PY