An undocumented tool is, from inside an agent session, indistinguishable from a tool that was never written. The framework shipped 26 git wrappers and named 6 of them in its resident index docs — 23% discoverability, with pr-review.sh among the missing. The observable consequence was an agent obeying Constitution gate 7 as best it could see it, reaching for raw curl, sending GitHub's APPROVE to a Gitea host, and getting HTTP 200 with the review silently filed PENDING. Three times. That is not a discipline failure and no amount of prose fixes it. Four changes, each converting a rule that decayed into a mechanism that cannot: - check-tools-index.sh (new, CI-blocking): every tool in an enforced suite must be named in a resident index doc, and every tool an index names must exist. The git suite is enforced now; other suites report coverage without failing, so the ratchet tightens one reviewed PR at a time instead of landing as one sweep. The enforced list is framework-owned rather than a marker inside operator-owned TOOLS.md — a doc marker would let an operator silence the gate on exactly the host where it matters most. Carries --self-test, because a checker that only ever passes is indistinguishable from one that is not running. - TOOLS-REFERENCE.md: complete 28-entry git index, plus the APPROVED/APPROVE dialect note that explains why pr-review.sh is not a formality. - mosaic-worktree.sh + wrapper-guard.sh (upstreamed): 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 placement conventions on one host. The helper therefore exposes no placement decision — given a branch name, every path is derived from `git worktree list --porcelain`. Worktrees rather than clones because enumerability is the only thing that makes reclaim safe, and reclaim is by evidence (clean tree + no unpushed commits), never by size or age. The guard blocks three mechanically-detectable mistakes and nothing else: a checkout into $HOME, a raw provider-API write to an endpoint that has a wrapper, and the literal APPROVE event. Reads pass untouched. - STANDARDS.md: model tiering as a standard, named by capability class so it survives a model generation. Start cheapest, escalate on evidence, benchmark before demoting a task class, and keep the class->model binding in operator config with the DB-backed config service as the end state. Registering the guard in runtime/claude/settings.json is the point of upstreaming it: ~/.claude/settings.json is a framework-managed copy, so a hand-added hook there is destroyed by the next upgrade. In the template it survives, and it reaches every host instead of one.
341 lines
16 KiB
Markdown
341 lines
16 KiB
Markdown
# 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.
|
|
|
|
```bash
|
|
~/.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.
|
|
|
|
```bash
|
|
~/.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)
|
|
|
|
```bash
|
|
~/.config/mosaic/tools/codex/codex-code-review.sh --uncommitted
|
|
~/.config/mosaic/tools/codex/codex-security-review.sh --uncommitted
|
|
```
|
|
|
|
### Infrastructure — Portainer
|
|
|
|
```bash
|
|
~/.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.
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
~/.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) |
|
|
|
|
```bash
|
|
# 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.
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
~/.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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```json
|
|
"openbrain": {
|
|
"url": "https://<your-openbrain-host>",
|
|
"api_key": "<your-api-key>"
|
|
}
|
|
```
|
|
|
|
**REST API** (any language, any harness):
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
export EXCALIDRAW_GEN_PATH="$HOME/.config/mosaic/tools/excalidraw/excalidraw_gen.py"
|
|
```
|
|
|
|
**Manual registration:**
|
|
|
|
```bash
|
|
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 `trash` over `rm` when available — recoverable beats gone forever
|
|
- Never run destructive commands without explicit instruction
|
|
- Write it down — "mental notes" don't survive session restarts; files do
|