Capture: parse leading type keyword; record master-deploy + Element lessons
capture-note.sh reads an optional leading bug:/feature:/chore:/idea: keyword as the inbox type (default idea, always P2). AGENTS.md: capture mode marked live; two durable lessons — the Update button deploys origin/master so commits must land there, and Element intercepts /capture (the thread is the trigger).
This commit is contained in:
@@ -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
|
`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
|
`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,
|
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} .`
|
`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.)*
|
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
|
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,
|
`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
|
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
|
bot confirms in-thread with the exact inbox line. The item type comes from an optional leading
|
||||||
just drops the prefix. Thread roots are minted by `seed-capture-threads.py`.
|
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 <text>` fallback
|
||||||
|
needs a "Send as message" / `//capture` dance — fine as a code path, not the daily UX (2026-06-16).
|
||||||
|
|
||||||
## Sovereignty constraint
|
## Sovereignty constraint
|
||||||
|
|
||||||
@@ -232,30 +239,27 @@ once" is not done.
|
|||||||
(`~/matrix-bridge`, a Gitea clone tracking `master`): host networking, `restart: unless-stopped`,
|
(`~/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
|
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
|
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 /
|
- **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.
|
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.
|
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 <text>` in any
|
- **Capture mode (D13) LIVE 2026-06-16 — proven on 1 room, N=3 pending.** A reply in a room's
|
||||||
room and per-room capture threads both log to `standards/INBOX.md` via `capture-note.sh`; bot
|
capture thread logs to `standards/INBOX.md` via `capture-note.sh` and confirms in-thread. All 11
|
||||||
confirms in-thread. All 11 rooms + all-projects already have their `capture_thread` roots seeded
|
rooms + all-projects have seeded `capture_thread` roots (IDs in the Mac's `config.toml`). A
|
||||||
and their IDs are in the Mac's `config.toml`. **To go live:** (1) push code + `git`-deploy on the
|
leading keyword sets the type — `bug:` / `feature:` / `chore:` / `idea:` (etc.); no keyword →
|
||||||
Spark (Update button); (2) refresh the Spark's `config.toml` — it's gitignored, so the Update
|
`idea`; priority is always `P2` (set the real one at `/triage`). **Element note:** the typed
|
||||||
button does *not* carry it: on the Spark run
|
`/capture` fallback is mostly dead — Element grabs any `/`-message as a client command ("Unknown
|
||||||
`scp mac-bridge:/Users/macpro/Projects/matrix-bridge/config.toml ~/matrix-bridge/config.toml`
|
Command"); the no-slash **thread** is the path (`//capture …` forces a literal send if ever
|
||||||
(or hand-add `capture_launcher` + the `capture_thread` lines), then Restart. Until the Spark has
|
needed). Deploying capture-style code? See the master-deploy gotcha under Commands → Deploy.
|
||||||
both the new code *and* config, **don't reply in the capture threads** (the old bot would treat a
|
|
||||||
thread reply as a launch).
|
|
||||||
- **Optional / triggered next moves:**
|
- **Optional / triggered next moves:**
|
||||||
- Badge reflects container liveness only, not Synapse connectivity — add a Docker `HEALTHCHECK`
|
- 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.
|
(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
|
- 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.
|
flag to `ask-claude.sh` if/when hit, not preemptively.
|
||||||
- Capture defaults every line to `[idea][P2]`; add inline `[type]`/`[Pn]` parsing to
|
- Capture priority is always `P2`; add a priority keyword/token to `capture-note.sh` if setting
|
||||||
`capture-note.sh` if reclassifying at `/triage` gets tedious.
|
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.
|
- 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
|
- **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.
|
Infra facts) — flag it if that account is ever rebuilt.
|
||||||
- **Repo:** `master` == `phase-1`. Uncommitted: capture mode (`src/bot.py`, `scripts/capture-note.sh`,
|
- **Repo:** single branch `master` (the vestigial `phase-1` was deleted 2026-06-16; capture mode was
|
||||||
`scripts/seed-capture-threads.py`, `config.example.toml`, `AGENTS.md`) + gitignored `config.toml`.
|
briefly stranded on it — see Deploy). Clean, pushed to Gitea. No test suite (pre-existing).
|
||||||
No test suite (pre-existing).
|
|
||||||
|
|||||||
+23
-4
@@ -7,14 +7,16 @@
|
|||||||
# target repo) drains it like any other captured item.
|
# 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
|
# 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
|
# Gitea. The "smarts" (priority, repo-routing, phrasing) stay at /triage, where the human is —
|
||||||
# human is — and routing message text through a frontier model would break the sovereignty
|
# routing message text through a frontier model would break the sovereignty boundary (D13/D8).
|
||||||
# boundary (D13/D8). Defaults every capture to a raw [idea][P2]; /triage reclassifies.
|
# 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
|
# 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.
|
# skips — the same seam as launch-claude.sh / ask-claude.sh.
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
setopt extended_glob # for whitespace-run trimming below
|
||||||
|
|
||||||
standards="$HOME/Projects/standards"
|
standards="$HOME/Projects/standards"
|
||||||
inbox="$standards/INBOX.md"
|
inbox="$standards/INBOX.md"
|
||||||
@@ -32,7 +34,24 @@ if [[ ! -f "$inbox" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
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"
|
print -r -- "$line" >> "$inbox"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user