diff --git a/AGENTS.md b/AGENTS.md index e33e5b6..e5d56fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,7 +71,11 @@ deterministic, no Claude call (capture mode, D13). `git fetch origin && git reset --hard origin/master && docker compose up -d --build` (run as `modelo` from `~/matrix-bridge`). You normally don't run this by hand — the **Update** button on the Spark Control dashboard (Phase 3) runs exactly this and streams the output: push to Gitea, - then click Update. *(Fallback if Gitea is ever unreachable: scp the files from the Mac — + then click Update. **Commit to `master`, not a side branch** — Update pulls `origin/master`, so a + commit only on another branch deploys *stale* code with no error (cost a debugging round on + 2026-06-16: capture mode was pushed to `phase-1` while Update kept pulling the old `master`). + Also: `config.toml` is **gitignored**, so Update does *not* carry config changes — refresh it on + the Spark separately (`scp mac-bridge:…/config.toml ~/matrix-bridge/config.toml`) before Update. *(Fallback if Gitea is ever unreachable: scp the files from the Mac — `scp mac-bridge:/Users/macpro/Projects/matrix-bridge/{Dockerfile,docker-compose.yml,docker-entrypoint.sh,requirements.txt,config.toml,.env} .` and `scp -r mac-bridge:/Users/macpro/Projects/matrix-bridge/src .`, then rebuild.)* @@ -172,8 +176,11 @@ Condensed from the scoping workshop. Each: the call, why, what it beat. rewrite"). *Beat:* `claude -p /capture` for the write — a one-line append needs no model, so `capture-note.sh` does it deterministically: no token, nothing leaves the Mac but the git push, and message text never reaches a frontier model (upholds the sovereignty constraint / D8). The - bot confirms in-thread with the exact inbox line; `/capture` is the zero-config path, the thread - just drops the prefix. Thread roots are minted by `seed-capture-threads.py`. + bot confirms in-thread with the exact inbox line. The item type comes from an optional leading + keyword the user types (`bug:` / `feature:` / `chore:` / …; default `idea`, always `P2`). Thread + roots are minted by `seed-capture-threads.py`. *In practice the thread is the only good trigger:* + Element intercepts any `/`-prefixed message as a client command, so the `/capture ` fallback + needs a "Send as message" / `//capture` dance — fine as a code path, not the daily UX (2026-06-16). ## Sovereignty constraint @@ -232,30 +239,27 @@ once" is not done. (`~/matrix-bridge`, a Gitea clone tracking `master`): host networking, `restart: unless-stopped`, read-only mounts of `.env`/`config.toml`/SSH key. Runs as `@agent` in 11 project rooms + an all-projects fan-out room. Interactive (plain msg → phone) and ask (`?`-prefix → answer posted - back; D12) both proven. Phase 2: owner-confirmed N=3 routing. + back; D12) both proven at N=3; capture (D13) is live (see below). Phase 2: owner-confirmed routing. - **Phase 3 (Spark Control) shipped 2026-06-16 in v0.21.0:** status badge + Update / Restart / Stop-Start / Logs tile; the Spark's dir is now a Gitea clone and deploy = the Update button. Detail in ROADMAP + `docs/spark-control-integration.md`; no matrix-bridge code change. -- **Capture mode (D13) built 2026-06-16 — pending deploy, not yet N=3.** `/capture ` in any - room and per-room capture threads both log to `standards/INBOX.md` via `capture-note.sh`; bot - confirms in-thread. All 11 rooms + all-projects already have their `capture_thread` roots seeded - and their IDs are in the Mac's `config.toml`. **To go live:** (1) push code + `git`-deploy on the - Spark (Update button); (2) refresh the Spark's `config.toml` — it's gitignored, so the Update - button does *not* carry it: on the Spark run - `scp mac-bridge:/Users/macpro/Projects/matrix-bridge/config.toml ~/matrix-bridge/config.toml` - (or hand-add `capture_launcher` + the `capture_thread` lines), then Restart. Until the Spark has - both the new code *and* config, **don't reply in the capture threads** (the old bot would treat a - thread reply as a launch). +- **Capture mode (D13) LIVE 2026-06-16 — proven on 1 room, N=3 pending.** A reply in a room's + capture thread logs to `standards/INBOX.md` via `capture-note.sh` and confirms in-thread. All 11 + rooms + all-projects have seeded `capture_thread` roots (IDs in the Mac's `config.toml`). A + leading keyword sets the type — `bug:` / `feature:` / `chore:` / `idea:` (etc.); no keyword → + `idea`; priority is always `P2` (set the real one at `/triage`). **Element note:** the typed + `/capture` fallback is mostly dead — Element grabs any `/`-message as a client command ("Unknown + Command"); the no-slash **thread** is the path (`//capture …` forces a literal send if ever + needed). Deploying capture-style code? See the master-deploy gotcha under Commands → Deploy. - **Optional / triggered next moves:** - Badge reflects container liveness only, not Synapse connectivity — add a Docker `HEALTHCHECK` (bot-side liveness signal → read `{{.State.Health.Status}}`) when "running but silent" bites. - A `?`-ask in a repo `claude` has never opened may stall on the folder-trust gate — add a trust flag to `ask-claude.sh` if/when hit, not preemptively. - - Capture defaults every line to `[idea][P2]`; add inline `[type]`/`[Pn]` parsing to - `capture-note.sh` if reclassifying at `/triage` gets tedious. + - Capture priority is always `P2`; add a priority keyword/token to `capture-note.sh` if setting + it at `/triage` gets tedious. Old `phase-0` branch still exists — delete if it bothers you. - Phase 4+ (intent-routing brain D8, thread continuity) — see ROADMAP; not scoped. - **Watch:** the Update button depends on modelo's Gitea ssh-config pin (`IdentitiesOnly yes`, see Infra facts) — flag it if that account is ever rebuilt. -- **Repo:** `master` == `phase-1`. Uncommitted: capture mode (`src/bot.py`, `scripts/capture-note.sh`, - `scripts/seed-capture-threads.py`, `config.example.toml`, `AGENTS.md`) + gitignored `config.toml`. - No test suite (pre-existing). +- **Repo:** single branch `master` (the vestigial `phase-1` was deleted 2026-06-16; capture mode was + briefly stranded on it — see Deploy). Clean, pushed to Gitea. No test suite (pre-existing). diff --git a/scripts/capture-note.sh b/scripts/capture-note.sh index 175a9e7..0038fae 100755 --- a/scripts/capture-note.sh +++ b/scripts/capture-note.sh @@ -7,14 +7,16 @@ # target repo) drains it like any other captured item. # # Deterministic on purpose: no LLM, no token, nothing leaves the Mac except the git push to -# Gitea. The "smarts" (real type/priority, repo-routing, phrasing) stay at /triage, where the -# human is — and routing message text through a frontier model would break the sovereignty -# boundary (D13/D8). Defaults every capture to a raw [idea][P2]; /triage reclassifies. +# Gitea. The "smarts" (priority, repo-routing, phrasing) stay at /triage, where the human is — +# routing message text through a frontier model would break the sovereignty boundary (D13/D8). +# Type comes from an optional leading keyword the user types ("bug: ...", "feature: ..."); +# anything without one defaults to a raw [idea]. Priority is always P2; /triage sets the real one. # # Why a login shell (-l): git config / PATH live in ~/.zprofile, which a non-login SSH shell # skips — the same seam as launch-claude.sh / ask-claude.sh. set -e +setopt extended_glob # for whitespace-run trimming below standards="$HOME/Projects/standards" inbox="$standards/INBOX.md" @@ -32,7 +34,24 @@ if [[ ! -f "$inbox" ]]; then exit 1 fi -line="- [ ] ($project) [idea][P2] $note — via matrix, $(date +%F)" +# Optional leading type keyword ("bug: ...", "feature: ...", etc.) → that type; default idea. +# Only fires when the text before the first colon is exactly one known keyword, so a note that +# merely contains a colon ("ratio is 3:1") is left untouched. Types: standards/guides/capture.md. +type="idea" +if [[ "$note" == *:* ]]; then + cand="${(L)note%%:*}" # lowercase the text before the first colon + cand="${cand##[[:space:]]#}" # trim surrounding whitespace + cand="${cand%%[[:space:]]#}" + case "$cand" in + bug|feature|idea|chore|skill|agent|project) + type="$cand" + note="${note#*:}" # drop "keyword:" + note="${note##[[:space:]]#}" # trim leading whitespace + ;; + esac +fi + +line="- [ ] ($project) [$type][P2] $note — via matrix, $(date +%F)" print -r -- "$line" >> "$inbox"