Round two of the same independent review. Three findings, all real, and the first two share a root cause: the guard was reading command TEXT as though it were a command. 1. Splitting the endpoint token itself defeats fragment matching outright — `a=/api/v1/repos/o/r/iss; b=ues/1/comments` leaves no fragment contiguous. Round one fixed one spelling of this and the reviewer produced the general form immediately. It is not winnable by more fragments: the endpoint does not exist until the shell expands it, and this hook runs first. So the guard stops pretending to read it. A write whose URL contains an expansion, on a visibly forge-shaped command, is now BLOCKED as unreadable — because "I could not find an endpoint" must not mean "there is no endpoint". Opaque URLs that are not forge-shaped (webhooks, artifact stores) still pass. 2. The broadened body detection false-blocked ordinary work: `grep -R "curl -d https://.../issues" docs/`, `echo "curl -d ..." > note.txt`, printing an example from python. Talking about a call is not making one, and this is the direction that actually kills a control — an over-blocking hook gets turned off, and an off hook permits everything. The client must now appear at COMMAND POSITION: line start or after a shell operator, optionally behind VAR=value. In every false positive it sat behind a quote instead. Quotes are deliberately NOT stripped before matching; real calls quote their URLs. 3. `wt_precious()` aborted `cmd_rm` under `set -euo pipefail`: `grep -v` exits 1 when it filters everything out, which is exactly the disposable-only case, so a SAFE worktree failed to remove with no message. Fixed, and the same defect was latent one step upstream in `wt_dirty()`, where `head -200` SIGPIPEs git on any worktree with 201 changed files. The cap is gone — counting is cheap and the cap only ever truncated output that is no longer printed. Seven new fixtures pin all of it, in both directions. 27/27.
17 KiB
Machine-Level Tool Reference
Centralized reference for tools, credentials, and CLI patterns available across all projects.
Project-specific tooling belongs in the project's AGENTS.md, not here.
All tool suites are located at ~/.config/mosaic/tools/.
Tool Suites
Git Wrappers (Use First)
Mosaic wrappers at ~/.config/mosaic/tools/git/*.sh handle platform detection and edge cases. Always use these before raw CLI commands.
This index is complete and is kept complete mechanically: tools/quality/scripts/check-tools-index.sh
fails CI when a wrapper ships without an entry here, or when an entry here names a wrapper that no
longer exists. A wrapper missing from this list is, from inside an agent session, indistinguishable
from a wrapper that was never written — which is how the APPROVE/APPROVED incident below happened.
Every command takes --help. All of them accept --login <account> to pin the acting identity;
supply it explicitly on any host where the provider CLI's default account is an admin.
| Issues | |
|---|---|
issue-create.sh |
Create an issue (Gitea or GitHub) |
issue-view.sh |
Show one issue |
issue-list.sh |
List issues |
issue-edit.sh |
Edit title/body/labels/milestone |
issue-comment.sh |
Add a comment |
issue-assign.sh |
Assign or unassign |
issue-close.sh |
Close an issue |
issue-reopen.sh |
Reopen a closed issue |
| Pull requests | |
|---|---|
pr-create.sh |
Open a pull request |
pr-view.sh |
Show one PR |
pr-list.sh |
List PRs |
pr-diff.sh |
Fetch a PR's diff |
pr-metadata.sh |
PR metadata as JSON (head SHA, base, state, mergeability) |
pr-review.sh |
Place a review verdict — see the dialect note below |
pr-ci-wait.sh |
Block until the PR's CI reaches a terminal state |
pr-merge.sh |
Merge a PR |
pr-close.sh |
Close a PR without merging |
| Milestones | |
|---|---|
milestone-create.sh |
Create a milestone |
milestone-list.sh |
List milestones |
milestone-close.sh |
Close a milestone |
| Gates and guards | |
|---|---|
ci-queue-wait.sh |
CI queue guard — required before push/merge (see below) |
push-guard.sh |
Refuse verifications that pass for the wrong reason (e.g. green against an unpushed tree) |
mutate-push-guard.sh |
Regenerate the guard's mutation-coverage table from measurement, so the table cannot drift from the guard |
verify-clean-clone.sh |
Prove the committed artifact runs, from a clean clone — not the working tree |
| Context | |
|---|---|
detect-platform.sh |
Resolve the provider (Gitea vs GitHub) for the current repo; every other wrapper uses it |
lane-brief.sh |
Live dispatch brief for a repo "lane" (milestone/label) straight from the provider |
| Workspace | |
|---|---|
mosaic-worktree.sh |
Create/list/remove git worktrees — the only supported way; see below |
wrapper-guard.sh |
PreToolUse hook that enforces the two rules above; not called by hand |
Workspace placement is derived, not chosen. mosaic-worktree.sh new <branch> takes a branch
name and nothing else. Every path comes out of git worktree list --porcelain — main worktree,
repo name, parent dir, then <parent>/<repo>-worktrees/<branch-slug>. There is no placement flag
because a decision an agent has to make is a decision that drifts: the rule "big work goes on a work
filesystem" already existed in prose and 255 GB accumulated in $HOME across 842 directories
anyway, under five simultaneous conventions on a single host.
~/.config/mosaic/tools/git/mosaic-worktree.sh new <branch> [--from <base>]
~/.config/mosaic/tools/git/mosaic-worktree.sh path <branch> # derived path, no side effect
~/.config/mosaic/tools/git/mosaic-worktree.sh list # this repo's worktrees + state
~/.config/mosaic/tools/git/mosaic-worktree.sh rm <branch> # removal is part of the task
~/.config/mosaic/tools/git/mosaic-worktree.sh gc [--apply] # reclaim clean + fully-pushed ones
Worktrees rather than clones, because git worktree list makes every checkout enumerable — a bare
clone dropped somewhere on disk can never be safely reclaimed, so it is never reclaimed. rm and
gc decide by evidence, never by size or age: a worktree is reclaimable only when
git status --porcelain is empty and git rev-list --count HEAD --not --remotes is 0. Anything
else is preserved and reported. --force exists and is yours to type deliberately.
wrapper-guard.sh is registered as a Claude Code PreToolUse hook on Bash (see
runtime/claude/settings.json). It blocks exactly three things and lets everything else through:
a git clone/git worktree add targeting $HOME; a raw provider-API write to an endpoint that
already has a wrapper above (reads are untouched — they are how you gather evidence); and the
literal "event": "APPROVE". For a genuine gap no wrapper can express, prefix
MOSAIC_WRAPPER_OVERRIDE=1. Reaching for the override twice for the same call means the wrapper has
a missing flag — extend the wrapper.
~/.config/mosaic/tools/git/issue-create.sh --help
~/.config/mosaic/tools/git/pr-review.sh --pr 42 --event APPROVED --body "..."
# CI queue guard (required before push/merge; defaults to the checked-out branch)
~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge
Review dialect — the reason pr-review.sh is not optional. Gitea's approve event is
APPROVED; GitHub's is APPROVE. Send GitHub's spelling to a Gitea host and it answers HTTP
200, files the review as PENDING, and then rejects the submit with 422 review stay pending — the
verdict looks placed and is not. (REQUEST_CHANGES is spelled identically on both, so only the
approve path carries the trap.) pr-review.sh sends the correct token for the detected provider.
Whatever you use, re-read GET /pulls/{n}/reviews and assert the state before reporting a verdict
placed.
The guard exits nonzero for any provider-asserted non-green, missing, or malformed CI state. If credentials or the provider are unavailable, it emits CANNOT_ASSERT and writes a JSONL audit record. Push degrades to exit 0 so recovery work is not bricked; merge holds with retryable exit 75 until the provider recovers, then self-clears without manual reset. Neither outcome is evidence that CI was clear. pr-merge.sh automatically inspects the exact PR head repository and full commit SHA rather than its main base; this also handles fork PRs without branch-name ambiguity. Pass --expect-head <approved-full-sha> to bind a commit-specific review or merge-gate verdict; Gitea uses atomic head_commit_id and GitHub uses --match-head-commit.
Code Review (Codex)
~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted
~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted
Infrastructure — Portainer
~/.config/mosaic/tools/portainer/stack-status.sh -n <stack-name>
~/.config/mosaic/tools/portainer/stack-redeploy.sh -n <stack-name>
~/.config/mosaic/tools/portainer/stack-list.sh
~/.config/mosaic/tools/portainer/endpoint-list.sh
Infrastructure — Coolify (DEPRECATED)
Coolify has been superseded by Portainer Docker Swarm in this stack. Tools remain for reference but should not be used for new deployments.
# DEPRECATED — do not use for new deployments
~/.config/mosaic/tools/coolify/project-list.sh
~/.config/mosaic/tools/coolify/service-list.sh
~/.config/mosaic/tools/coolify/service-status.sh -u <uuid>
~/.config/mosaic/tools/coolify/deploy.sh -u <uuid>
~/.config/mosaic/tools/coolify/env-set.sh -u <uuid> -k KEY -v VALUE
Identity — Authentik
~/.config/mosaic/tools/authentik/user-list.sh
~/.config/mosaic/tools/authentik/user-create.sh -u <username> -n <name> -e <email>
~/.config/mosaic/tools/authentik/group-list.sh
~/.config/mosaic/tools/authentik/app-list.sh
~/.config/mosaic/tools/authentik/flow-list.sh
~/.config/mosaic/tools/authentik/admin-status.sh
CI/CD — Woodpecker
Multi-instance support: -a <instance> selects a named instance. Omit -a to use the default from woodpecker.default in credentials.json.
| Instance | URL | Serves |
|---|---|---|
mosaic (default) |
ci.mosaicstack.dev | Mosaic repos (git.mosaicstack.dev) |
usc |
ci.uscllc.com | USC repos (git.uscllc.com) |
# List recent pipelines
~/.config/mosaic/tools/woodpecker/pipeline-list.sh [-r owner/repo] [-a instance]
# Check latest or specific pipeline status
~/.config/mosaic/tools/woodpecker/pipeline-status.sh [-r owner/repo] [-n number] [-a instance]
# Trigger a build
~/.config/mosaic/tools/woodpecker/pipeline-trigger.sh [-r owner/repo] [-b branch] [-a instance]
Instance selection rule: match -a to the git remote host of the target repo. If the repo is on git.uscllc.com, use -a usc. If on git.mosaicstack.dev, use -a mosaic (or omit, since it's the default).
DNS — Cloudflare
Multi-instance support: -a <instance> selects a named instance (e.g. personal, work). Omit -a to use the default from cloudflare.default in credentials.json.
# List zones (domains)
~/.config/mosaic/tools/cloudflare/zone-list.sh [-a instance]
# List DNS records (zone by name or ID)
~/.config/mosaic/tools/cloudflare/record-list.sh -z <zone> [-a instance] [-t type] [-n name]
# Create DNS record
~/.config/mosaic/tools/cloudflare/record-create.sh -z <zone> -t <type> -n <name> -c <content> [-a instance] [-p] [-l ttl] [-P priority]
# Update DNS record
~/.config/mosaic/tools/cloudflare/record-update.sh -z <zone> -r <record-id> -t <type> -n <name> -c <content> [-a instance] [-p] [-l ttl]
# Delete DNS record
~/.config/mosaic/tools/cloudflare/record-delete.sh -z <zone> -r <record-id> [-a instance]
IT Service — GLPI
~/.config/mosaic/tools/glpi/ticket-list.sh
~/.config/mosaic/tools/glpi/ticket-create.sh -t <title> -c <content>
~/.config/mosaic/tools/glpi/computer-list.sh
~/.config/mosaic/tools/glpi/user-list.sh
Health Check
# Check all configured services
~/.config/mosaic/tools/health/stack-health.sh
# Check a specific service
~/.config/mosaic/tools/health/stack-health.sh -s portainer
# JSON output for automation
~/.config/mosaic/tools/health/stack-health.sh -f json
Shared Credential Loader
# Source in any script to load service credentials
source ~/.config/mosaic/tools/_lib/credentials.sh
load_credentials <service-name>
# Supported: portainer, coolify, authentik, glpi, github, gitea-mosaicstack, gitea-usc, woodpecker, cloudflare, turbo-cache, openbrain
OpenBrain — Semantic Memory (PRIMARY)
Self-hosted semantic brain backed by pgvector. Primary shared memory layer for all agents across all sessions and harnesses. Stores and retrieves decisions, context, and observations via semantic search.
Credentials: load_credentials openbrain → exports OPENBRAIN_URL, OPENBRAIN_TOKEN
Configure in your credentials.json:
"openbrain": {
"url": "https://<your-openbrain-host>",
"api_key": "<your-api-key>"
}
REST API (any language, any harness):
source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials openbrain
# Search by meaning
curl -s -X POST -H "Authorization: Bearer $OPENBRAIN_TOKEN" -H "Content-Type: application/json" \
-d '{"query": "your search", "limit": 5}' "$OPENBRAIN_URL/v1/search"
# Capture a thought
curl -s -X POST -H "Authorization: Bearer $OPENBRAIN_TOKEN" -H "Content-Type: application/json" \
-d '{"content": "...", "source": "agent-name", "metadata": {}}' "$OPENBRAIN_URL/v1/thoughts"
# Recent activity
curl -s -H "Authorization: Bearer $OPENBRAIN_TOKEN" "$OPENBRAIN_URL/v1/thoughts/recent?limit=5"
# Stats
curl -s -H "Authorization: Bearer $OPENBRAIN_TOKEN" "$OPENBRAIN_URL/v1/stats"
Python client (if the OpenBrain client is on your PYTHONPATH):
python tools/openbrain_client.py search "topic"
python tools/openbrain_client.py capture "decision or observation" --source agent-name
python tools/openbrain_client.py recent --limit 5
python tools/openbrain_client.py stats
MCP (Claude Code sessions): When connected, mcp__openbrain__capture/search/recent/stats tools are available natively — prefer those over CLI when in a Claude session.
Rule: capture when you LEARN something. Never when you DO something.
| Trigger | Action | Retention |
|---|---|---|
| Session start | search + recent to load prior context |
— |
| Architectural or tooling decision made | Capture with rationale | long or permanent |
| Gotcha or non-obvious behavior discovered | Capture immediately | medium |
| User preference stated or confirmed | Capture | permanent |
| Cross-project pattern identified | Capture | permanent |
| Prior decision superseded | UPDATE existing thought | (keep tier) |
Never capture: task started, commit pushed, PR opened, test results, file edits, CI status.
Full protocol and cleanup tools: ~/.config/mosaic/guides/MEMORY.md
Smart capture wrapper (enforces schema + dedup): ~/.config/mosaic/tools/openbrain/capture.sh
Excalidraw — Diagram Export (MCP)
Headless .excalidraw → SVG export via @excalidraw/excalidraw. Available as MCP tools in Claude Code sessions.
MCP tools (when connected):
| Tool | Input | Output |
|---|---|---|
mcp__excalidraw__excalidraw_to_svg |
elements JSON string + optional app_state |
SVG string |
mcp__excalidraw__excalidraw_file_to_svg |
file_path to .excalidraw |
SVG string + writes .svg alongside |
mcp__excalidraw__list_diagrams |
(none) | Available templates (requires EXCALIDRAW_GEN_PATH) |
mcp__excalidraw__generate_diagram |
name, optional output_path |
Path to generated .excalidraw |
mcp__excalidraw__generate_and_export |
name, optional output_path |
Paths to .excalidraw and .svg |
Diagram generation (list_diagrams, generate_diagram, generate_and_export) requires EXCALIDRAW_GEN_PATH env var pointing to excalidraw_gen.py. Set in environment or shell profile:
export EXCALIDRAW_GEN_PATH="$HOME/.config/mosaic/tools/excalidraw/excalidraw_gen.py"
Manual registration:
mosaic-ensure-excalidraw # install deps + register with Claude
mosaic-ensure-excalidraw --check # verify registration
Git Providers
| Instance | URL | CLI | Purpose |
|---|---|---|---|
| (add your git providers here) |
Credentials
Location: (configure your credential file path)
Loader: source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials <service>
Never expose actual values. Never commit credential files.
CLI Gotchas
(Add platform-specific CLI gotchas as you discover them.)
Safety Defaults
- Prefer
trashoverrmwhen available — recoverable beats gone forever - Never run destructive commands without explicit instruction
- Write it down — "mental notes" don't survive session restarts; files do