Metadata-Version: 2.4
Name: briar-cli
Version: 1.1.6
Summary: Terminal client for the Briar agent-orchestration API.
Project-URL: Homepage, https://usebriar.com
Project-URL: Source, https://github.com/iklobato/briar-cli
Project-URL: Issues, https://github.com/iklobato/briar-cli/issues
Author: Briar
License-Expression: MIT
License-File: LICENSE
Keywords: agents,briar,cli,workflow
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development
Requires-Python: >=3.10
Requires-Dist: anthropic<1,>=0.40
Requires-Dist: atlassian-python-api<4,>=3.41
Requires-Dist: boto3<2,>=1.34
Requires-Dist: httpx<1,>=0.27
Requires-Dist: jinja2<4,>=3.1
Requires-Dist: psycopg[binary]<4,>=3.1
Requires-Dist: pydantic<3,>=2.5
Requires-Dist: pygithub<3,>=2.5
Requires-Dist: pytz
Requires-Dist: pyyaml<7,>=6.0
Requires-Dist: rich<14,>=13
Requires-Dist: schedule<2,>=1.2
Provides-Extra: all
Requires-Dist: azure-identity<2,>=1.15; extra == 'all'
Requires-Dist: azure-mgmt-appcontainers<4,>=3.0; extra == 'all'
Requires-Dist: azure-mgmt-loganalytics<14,>=12.0; extra == 'all'
Requires-Dist: azure-mgmt-rdbms<11,>=10.1; extra == 'all'
Requires-Dist: azure-mgmt-servicebus<9,>=8.2; extra == 'all'
Requires-Dist: azure-mgmt-subscription<4,>=3.1; extra == 'all'
Requires-Dist: google-api-python-client<3,>=2.100; extra == 'all'
Requires-Dist: google-auth<3,>=2.20; extra == 'all'
Requires-Dist: google-cloud-logging<4,>=3.8; extra == 'all'
Requires-Dist: google-cloud-pubsub<3,>=2.18; extra == 'all'
Requires-Dist: google-cloud-run<1,>=0.10; extra == 'all'
Requires-Dist: google-generativeai<1,>=0.7; extra == 'all'
Requires-Dist: hvac<3,>=2.0; extra == 'all'
Requires-Dist: infisicalsdk<2,>=1.0; extra == 'all'
Requires-Dist: openai<2,>=1.40; extra == 'all'
Provides-Extra: azure
Requires-Dist: azure-identity<2,>=1.15; extra == 'azure'
Requires-Dist: azure-mgmt-appcontainers<4,>=3.0; extra == 'azure'
Requires-Dist: azure-mgmt-loganalytics<14,>=12.0; extra == 'azure'
Requires-Dist: azure-mgmt-rdbms<11,>=10.1; extra == 'azure'
Requires-Dist: azure-mgmt-servicebus<9,>=8.2; extra == 'azure'
Requires-Dist: azure-mgmt-subscription<4,>=3.1; extra == 'azure'
Provides-Extra: dev
Requires-Dist: black<26,>=25; extra == 'dev'
Requires-Dist: mypy<2,>=1.13; extra == 'dev'
Provides-Extra: gcp
Requires-Dist: google-api-python-client<3,>=2.100; extra == 'gcp'
Requires-Dist: google-auth<3,>=2.20; extra == 'gcp'
Requires-Dist: google-cloud-logging<4,>=3.8; extra == 'gcp'
Requires-Dist: google-cloud-pubsub<3,>=2.18; extra == 'gcp'
Requires-Dist: google-cloud-run<1,>=0.10; extra == 'gcp'
Provides-Extra: gemini
Requires-Dist: google-generativeai<1,>=0.7; extra == 'gemini'
Provides-Extra: infisical
Requires-Dist: infisicalsdk<2,>=1.0; extra == 'infisical'
Provides-Extra: openai
Requires-Dist: openai<2,>=1.40; extra == 'openai'
Provides-Extra: test
Provides-Extra: vault
Requires-Dist: hvac<3,>=2.0; extra == 'vault'
Description-Content-Type: text/markdown

# briar — local extraction + scheduling CLI

Python CLI that mines live state from external systems (GitHub,
Bitbucket, AWS, GCP, Azure, Jira, Linear, Fireflies, …), schedules
per-company extraction in-process, and runs autonomous LLM-driven
agents against the resulting knowledge.

Everything runs locally — no `api.usebriar.com` service, no remote
workspace. Each command shells out to the external APIs directly
(via PyGithub, atlassian-python-api, boto3, anthropic, etc.) and
writes its output to local markdown files or a Postgres knowledge
store.

```
briar version
briar-cli 1.1.1
```

---

## Install

From PyPI (recommended):

```bash
pip install briar-cli
briar version
```

From source (for development):

```bash
git clone git@github.com:iklobato/briar-cli.git
cd briar-cli
python3.12 -m venv .venv
source .venv/bin/activate
pip install -e .
briar version
```

Optional pip extras — install only what you'll actually use. Each
adapter fails loudly if its SDK is missing, with the right install
command in the error message:

```bash
pip install 'briar-cli[openai]'         # OpenAI LLM
pip install 'briar-cli[gemini]'         # Google Gemini LLM
pip install 'briar-cli[vault]'          # HashiCorp Vault credential store
pip install 'briar-cli[gcp]'            # GCP cloud provider
pip install 'briar-cli[azure]'          # Azure cloud provider
pip install 'briar-cli[infisical]'      # Infisical credential bootstrap
pip install 'briar-cli[all]'            # everything above
```

Replace `pip install` with `pip install -e` and quote the source-tree
path (`-e '.[openai]'`) when working from a local checkout.

Base install always works for: GitHub + Bitbucket extractors, AWS
infra, Jira/Linear/GitHub-Issues/Bitbucket-Issues trackers,
Fireflies meeting transcripts, Anthropic LLM, AWS Bedrock LLM,
all 6 message writers (jira-comment, jira-transition, slack-channel,
telegram-chat, github-pr-comment, bitbucket-pr-comment), all 4
notification sinks, file + postgres knowledge stores, AWS Secrets
Manager / SSM credential stores.

Python 3.10+. Tested through 3.12.

---

## Commands

```
briar version
briar extract       — one-shot extraction
briar runbook       — scheduled extraction (extract / sweep / serve)
briar agent         — autonomous LLM-driven flows (prfix / implement)
briar plan          — sequenced implementation plans from a tracker board (build / show / next / advance / list / clear)
briar scaffold      — emit JSON config bundles for downstream tools
briar context       — read/write local markdown blobs
briar dashboard     — read-only HTML status page
briar auth          — interactive credential acquisition (login / logout / refresh / list / status)
briar secrets       — credential coverage (doctor / bootstrap)
briar journal       — inspect decision-journal sessions (list / show / export)
```

**Global flags** (apply to every subcommand):

| Flag | Purpose | Default |
|---|---|---|
| `--format {table,json,yaml,csv,quiet}` | output format | `table` |
| `--verbose` / `-v` | DEBUG-level logging | INFO |

**Global env vars:**

| Env var | Effect |
|---|---|
| `BRIAR_VERBOSE=1` | same as `--verbose` |
| `BRIAR_LIB_DEBUG=1` | also surface third-party loggers (httpx, boto3) |
| `BRIAR_DATABASE_URL` | switch the default knowledge store from `file` to `postgres` — also the final fallback DSN when no per-company override is set |
| `BRIAR_{COMPANY}_DATABASE_URL` | per-company Postgres DSN. Auto-detected when the YAML has no `knowledge.config.dsn_env`. Hyphens in company keys are uppercased + replaced with `_` (e.g. `widget-co` → `BRIAR_WIDGET_CO_DATABASE_URL`). |
| `<custom>` (when YAML sets `knowledge.config.dsn_env: MY_PG`) | reads the named env var as the DSN — fully explicit override |
| `BRIAR_NOTIFY_SINKS=telegram,slack` | scheduler failure alerts |
| `BRIAR_JOURNAL=off` | disable the decision journal entirely (default: on) |
| `BRIAR_JOURNAL_STORE={file}` | system-of-record backend (default `file`) |
| `BRIAR_JOURNAL_SINKS=file` | comma-separated publish sinks (default `file`, writes a markdown summary per session) |
| `BRIAR_JOURNAL_ROOT=./journal` | filesystem root for the file store + file sink |
| `BRIAR_DEFAULT_STORE={envfile,infisical,vault,aws-secretsmanager,ssm}` | default `--store` for `briar auth login`. When set, credentials acquired interactively land here without `--store` on every invocation. |
| `BRIAR_SECRETS_FILE=/path/to/secrets.env` | override the secrets file path. Resolution order: this env var → `/etc/briar/secrets.env` (if exists) → `$XDG_CONFIG_HOME/briar/secrets.env` (or `~/.config/briar/secrets.env`) |
| `INFISICAL_CLIENT_ID` / `_SECRET` / `_PROJECT_ID` (+ optional `_ENV`, `_HOST`) | Infisical machine-identity. Drives both bootstrap (auto-hydrate at startup) AND `InfisicalStore` (`--store infisical` writes). Acquire interactively via `briar auth login infisical`. |
| `JIRA_{COMPANY}_AUTH_KIND={token,session}` | force a Jira auth strategy. Default = auto-detect (session wins when a session-token env var is set) |
| `JIRA_{COMPANY}_EMAIL` + `JIRA_{COMPANY}_TOKEN` | token-auth credentials (Atlassian-recommended) |
| `JIRA_{COMPANY}_SESSION_TOKEN` / `JIRA_{COMPANY}_TENANT_SESSION_TOKEN` | session-auth credentials (browser-extracted cookies). Either one alone is sufficient. |
| `JIRA_{COMPANY}_XSRF_TOKEN` / `JIRA_{COMPANY}_USER_AGENT` | optional session-auth extras |
| `FIREFLIES_{COMPANY}_API_KEY` | Fireflies.ai personal API key — drives both the scheduled `meeting-digest` extractor and the JIT `meeting-context` extractor. Acquire from your Fireflies workspace dashboard. |

---

## `briar version`

Prints client version. Takes no arguments.

```bash
briar version
# briar-cli 1.1.1
```

---

## `briar extract` — one-shot extraction

Run one or more extractors against external sources and write the
result to a knowledge blob.

```
briar extract --company <name> [--include <extractor>] ...
              [--storage {file,postgres}] [--root <dir>]
              [--provider {github,bitbucket}]
              [--tracker {jira,github-issues,bitbucket-issues,linear}]
              [--cloud {aws,gcp,azure}]
              [--meeting {fireflies}]
              [extractor-specific flags]
```

**Required:**
- `--company` — drives the markdown title + the blob name

**Pick which extractors to run** with `--include` (repeatable; default = all available):

| Extractor | What it mines | Backed by |
|---|---|---|
| `pr-archaeology` | merged-PR patterns, top reviewers | repo |
| `active-work` | open PRs across configured repos | repo |
| `github-deployments` | environments, deployments, CI runs | repo |
| `codebase-conventions` | language, test runner, linter, migration tool | repo |
| `reviewer-profile` | per-reviewer comment cadence + sample asks | repo |
| `code-hotspots` | files that change together (co-change clusters) | repo |
| `active-tickets` | open tickets per project | tracker |
| `ticket-archaeology` | closed-ticket patterns, top assignees | tracker |
| `aws-infra` | cloud resources (compute, databases, queues, logs) | cloud |
| `meeting-digest` | recent meetings: summaries + action items | meeting |

**Extractor-specific flags** (only relevant when the matching `--include` is set):

| Flag | Used by | Notes |
|---|---|---|
| `--pr-repo <slug>` | `pr-archaeology` | repeatable; `owner/repo` |
| `--pr-max <N>` | `pr-archaeology` | default 100 |
| `--pr-authors-allow` / `--pr-authors-block` | `pr-archaeology` | allow ∩ ¬block |
| `--pr-assignees-allow` / `--pr-assignees-block` | `pr-archaeology` | |
| `--active-repo <slug>` | `active-work` | repeatable |
| `--active-authors-allow` / `--active-authors-block` | `active-work` | filter open PRs |
| `--deploy-repo <slug>` | `github-deployments` | repeatable |
| `--conventions-repo <slug>` | `codebase-conventions` | repeatable |
| `--reviewer-repo <slug>` | `reviewer-profile` | repeatable |
| `--reviewer-pr-sample <N>` | `reviewer-profile` | default 20 |
| `--reviewer-top-n <N>` | `reviewer-profile` | default 5 |
| `--hotspots-repo <slug>` | `code-hotspots` | repeatable |
| `--hotspots-since-days <N>` | `code-hotspots` | default 30 |
| `--hotspots-max-commits <N>` | `code-hotspots` | default 100 |
| `--hotspots-top-n <N>` | `code-hotspots` | default 10 |
| `--ticket-project <key>` | `active-tickets` | repeatable; Jira project / Linear team key / `owner/repo` for GH+BB Issues |
| `--ticket-archaeology-project <key>` | `ticket-archaeology` | repeatable |
| `--ticket-max <N>` | `ticket-archaeology` | default 100 |
| `--aws-extract-region <region>` | `aws-infra` | default `us-east-1` |
| `--aws-extract-service <svc>` | `aws-infra` | one of `ecs lambda logs rds sqs`; repeatable |
| `--aws-extract-profile <name>` | `aws-infra` | local AWS profile; falls back to per-company env vars |
| `--meeting {fireflies}` | `meeting-digest` (and `meeting-context` JIT) | default `fireflies` |
| `--meeting-since-days <N>` | `meeting-digest` | how many days back to scan; default 7 |
| `--meeting-max <N>` | `meeting-digest` | cap on meetings in the digest; default 25 |
| `--meeting-attendee-allow <email>` | `meeting-digest` | repeatable; only include meetings whose attendees overlap. Empty = no filter |

**Storage flags** (apply to every extraction):

| Flag | Purpose |
|---|---|
| `--storage {file,postgres}` | default `file` |
| `--blob-name <name>` | default `knowledge:<company>` |
| `--root <dir>` | file-store root (default `./knowledge`) |
| `--out-json <path>` | parallel JSON output (empty = skip) |

**Examples:**

```bash
# Just PR archaeology against one GitHub repo
briar extract --company acme \
    --include pr-archaeology \
    --pr-repo acme-co/acme-app --pr-max 50

# PRs + AWS infra in one shot, filter to team members only
briar extract --company acme \
    --include pr-archaeology --include aws-infra \
    --pr-repo acme-co/acme-app \
    --pr-authors-allow alice --pr-authors-allow bob \
    --aws-extract-region us-east-1 \
    --aws-extract-service ecs --aws-extract-service rds

# Bitbucket repo + Jira tickets
briar extract --company acme \
    --provider bitbucket --tracker jira \
    --include pr-archaeology --include active-tickets \
    --pr-repo acme/api \
    --ticket-project ACME

# Hotspots against a GitHub repo, 60-day window
briar extract --company acme \
    --include code-hotspots \
    --hotspots-repo acme-co/acme-app \
    --hotspots-since-days 60

# Write to Postgres instead of files
BRIAR_DATABASE_URL=postgresql://... briar extract --company acme \
    --include active-work --active-repo acme-co/acme-app \
    --storage postgres

# Pull last 14 days of Fireflies meeting summaries for an attendee list
FIREFLIES_ACME_API_KEY=ff_xxx briar extract --company acme \
    --include meeting-digest \
    --meeting fireflies \
    --meeting-since-days 14 --meeting-max 50 \
    --meeting-attendee-allow alice@acme.com \
    --meeting-attendee-allow bob@acme.com
```

---

## `briar runbook` — scheduled extraction

Three subcommands. All take a YAML file or a directory of YAMLs.
See [`examples/all_features.yaml`](examples/all_features.yaml) for
the comprehensive multi-company reference; [`examples/multi_company.yaml`](examples/multi_company.yaml)
is the lighter-touch tutorial.

### `briar runbook extract <file.yaml>`

Walks a runbook YAML's `schedules:` once and writes per-company
knowledge files. Exits after one pass.

| Flag | Purpose |
|---|---|
| `--task <name>` | run only the schedule whose `task:` field matches |

```bash
# Run everything in one runbook
briar runbook extract examples/all_features.yaml

# Run only the `prfix` task across every company in the runbook
briar runbook extract examples/all_features.yaml --task prfix
```

### `briar runbook sweep <directory>`

Runs `extract` for every `*.yaml` in the directory. One-shot.

```bash
briar runbook sweep examples/
```

### `briar runbook serve <directory>`

Long-running scheduler. Registers every `(company, task)` from every
YAML in the directory and runs the schedule loop forever. This is
what runs persistently on the droplet.

| Flag | Purpose |
|---|---|
| `--tick <seconds>` | scheduler tick interval (default 1) |

```bash
briar runbook serve examples/

# Tighter polling for low-cadence schedules
briar runbook serve examples/ --tick 5
```

---

## `briar agent` — autonomous LLM flows

Two ops. Each clones a worktree, fetches JIT context for the specific
ticket/PR, and drives an LLM tool-use loop until completion.

### `briar agent prfix`

Address unresolved review comments + failing CI on one PR.

| Flag | Required | Purpose |
|---|---|---|
| `--company <name>` | ✓ | matches a runbook YAML |
| `--owner <name>` | ✓ | repo owner |
| `--repo <name>` | ✓ | repo name |
| `--pr <N>` | ✓ | PR number |
| `--branch <name>` | ✓ | PR head branch |
| `--store {file,postgres}` | | knowledge store |
| `--knowledge <dir>` | | file-store root |
| `--runbook <yaml>` | | binds the `send_message` tool to the company's `messages:` block |
| `--dry-run` | | print rendered prompt + tool list, skip LLM call |
| `--model <name>` | | override Anthropic model |
| `--max-iter <N>` | | iteration ceiling |
| `--git-user-name` / `--git-user-email` | | commit identity. Per-field resolution: CLI flag > YAML `companies.<name>.git_identity.{name,email}` (when `--runbook` is set) > hardcoded `iklobato` default. |
| `--keep-worktree` | | leave `/tmp/...` after run |
| `--meeting {fireflies}` | | meeting provider (default `fireflies`); requires `FIREFLIES_{c}_API_KEY` |
| `--meeting-key <id>` | | splice ONE specific meeting's full transcript into the agent prompt |
| `--meeting-query <text>` | | keyword search across transcripts. When omitted, defaults to `owner/repo#pr` so meetings that mentioned the PR surface automatically |
| `--meeting-top-k <N>` | | max meetings to fetch in search mode (default 3) |
| `--meeting-max-bytes <N>` | | per-meeting transcript byte cap (default 50 000) |

```bash
briar agent prfix \
    --company acme --owner acme-co --repo acme-app \
    --pr 42 --branch fix-typo \
    --runbook examples/all_features.yaml

# Validate the rendered prompt without spending tokens
briar agent prfix \
    --company acme --owner acme-co --repo acme-app \
    --pr 42 --branch fix-typo \
    --dry-run

# Pin a specific meeting transcript into the agent's context
# (use when a reviewer's comment cites "as discussed Thursday")
briar agent prfix \
    --company acme --owner acme-co --repo acme-app \
    --pr 42 --branch fix-typo \
    --runbook examples/all_features.yaml \
    --meeting-key 01HABCDEF...
```

### `briar agent implement`

Implement one ticket end-to-end: clones default branch, fetches
ticket-context, agent branches + commits + opens a draft PR.

| Flag | Required | Purpose |
|---|---|---|
| `--company <name>` | ✓ | |
| `--owner <name>` | ✓ | repo owner / Bitbucket workspace |
| `--repo <name>` | ✓ | |
| `--ticket-project <key>` | ✓ | Jira `PROJ` / Linear team / `owner/repo` for GH+BB Issues |
| `--ticket-key <key>` | ✓ | `PROJ-123` / `#42` / `ENG-7` |
| `--tracker {jira,github-issues,bitbucket-issues,linear}` | | default `jira` |
| `--provider {github,bitbucket}` | | default `github` |
| `--runbook <yaml>` | | binds `send_message` tool |
| `--dry-run` | | print rendered prompt, skip LLM call |
| `--store` / `--knowledge` / `--model` / `--max-iter` | | as above |
| `--git-user-name` / `--git-user-email` / `--keep-worktree` | | as above |
| `--meeting {fireflies}` | | meeting provider (default `fireflies`); requires `FIREFLIES_{c}_API_KEY` |
| `--meeting-key <id>` | | splice ONE specific meeting's full transcript into the agent prompt |
| `--meeting-query <text>` | | keyword search across transcripts. When omitted, defaults to the ticket key so meetings that mentioned `ACME-123` surface automatically |
| `--meeting-top-k <N>` | | max meetings to fetch in search mode (default 3) |
| `--meeting-max-bytes <N>` | | per-meeting transcript byte cap (default 50 000) |

```bash
briar agent implement \
    --company acme --owner acme-co --repo acme-app \
    --ticket-project ACME --ticket-key ACME-42 \
    --tracker jira \
    --runbook examples/all_features.yaml

# Bitbucket repo, Linear tickets
briar agent implement \
    --company bitspark --owner bitspark --repo api \
    --ticket-project ENG --ticket-key ENG-7 \
    --provider bitbucket --tracker linear \
    --runbook examples/all_features.yaml

# Override the auto-derived meeting query (default = the ticket key)
# when the standup discussed the topic by feature name, not key
briar agent implement \
    --company acme --owner acme-co --repo acme-app \
    --ticket-project ACME --ticket-key ACME-42 \
    --runbook examples/all_features.yaml \
    --meeting-query "oauth refresh token rollout"
```

---

## `briar plan` — sequenced implementation plans

Take a tracker board (Jira board or GitHub Projects v2), pull every
card, synthesise per-card scope / out-of-scope / risks / dependencies,
topologically sort them, and persist the result as a markdown +
JSON blob in the chosen `KnowledgeStore`. The implementer flow
(`briar agent implement`) and any operator can then ask `briar plan
next` for the next pending card whose dependencies are all done.

A plan is stored under `plan:<name>` in whichever store you pick —
local file (default) or postgres. The blob is human-readable markdown
with the canonical JSON payload in a fenced block at the end, so the
file backend doubles as a review surface.

### URL shapes

| Form | Example | Reader |
|---|---|---|
| Jira board URL | `https://servicos-owlid-e-noctuaid.atlassian.net/jira/software/projects/KAN/boards/34` | `jira` |
| Jira short form | `jira:KAN` | `jira` |
| GitHub Projects v2 (org) | `https://github.com/orgs/precisetargetlabs/projects/34` | `github-project` |
| GitHub Projects v2 (user) | `https://github.com/users/iklobato/projects/2` | `github-project` |

Adding another tracker (Linear, Trello, …) is one module under
`src/briar/plan/_boards/` plus one entry in the registry — the
`build` subcommand has no per-vendor branching.

### Common flags (every subcommand)

| Flag | Default | Purpose |
|---|---|---|
| `--store {file,postgres}` | `file` | KnowledgeStore backend used to persist + reload the plan |
| `--root <dir>` | `./knowledge` | File-store root (only when `--store=file`) |
| `--company <name>` | `""` | Used by the postgres store for DSN resolution and by tracker providers for per-company credentials (e.g. `JIRA_{COMPANY}_*`) |

### `briar plan build <board>`

Fetch the board, enrich each card, sort by deps, and (optionally)
chain branches in cascade mode.

| Flag | Required | Default | Purpose |
|---|---|---|---|
| `board` | ✓ | — | Board URL or short form (see table above) |
| `--name <slug>` | | derived from URL | Plan name (becomes blob `plan:<name>`) |
| `--cascade` | | off | Chain branches: each card's `branch_parent` is its latest dep's branch instead of `--default-branch` |
| `--default-branch <name>` | | `main` | Branch the first (and every non-cascade) card branches from |
| `--max-cards <N>` | | 50 | Cap on cards pulled from the board |
| `--llm {anthropic,openai,gemini,bedrock}` | | `""` | LLM provider for per-card synthesis. Empty = heuristics only. When unavailable (no creds), silently falls back to heuristics |
| `--model <name>` | | provider default | Override the LLM's default model |
| `--with-knowledge` | | off | Splice the company's existing `knowledge:<company>` + `active-tickets:<company>` + `active-work:<company>` blobs into each card's synthesis context |
| `--print` | | off | After building, print the markdown plan to stdout |
| `--dry-run` | | off | Build the plan but do NOT persist it. Implies `--print` |

```bash
# Jira board, heuristic synthesis only, cascade so each card branches off the last
briar plan build \
    https://servicos-owlid-e-noctuaid.atlassian.net/jira/software/projects/KAN/boards/34 \
    --name owlid-q3 --company owlid --cascade

# GitHub Projects v2, LLM synthesis with the company's knowledge spliced in
briar plan build \
    https://github.com/orgs/precisetargetlabs/projects/34 \
    --name pt-roadmap --company precisetarget \
    --llm anthropic --with-knowledge --cascade

# Persistent postgres-backed plan
BRIAR_DATABASE_URL=postgresql://... briar plan build \
    jira:ACME --name acme-impl --company acme \
    --store postgres --cascade

# One-off, no persistence — print the synthesised markdown and exit
briar plan build jira:ENG --name preview --dry-run
```

### `briar plan show <name>`

Print the stored plan's markdown body to stdout (header + ordered
cards + raw JSON payload). No extra flags beyond the common store
flags.

```bash
briar plan show owlid-q3
briar plan show pt-roadmap --store postgres --company precisetarget
```

### `briar plan next <name>`

Print the next pending card whose dependencies are all `done`. Emits
a single record (table or JSON depending on `--format`) so the
implementer agent can pipe it into the next step. Returns the
`status: complete` sentinel when every card is done.

```bash
# What should I implement next?
briar plan next owlid-q3 --format json

# Human-readable
briar plan next owlid-q3
```

The card record includes `branch_name` + `branch_parent` so the
implementer agent can `git checkout -b <branch_name> origin/<branch_parent>`
without recomputing cascade order.

### `briar plan advance <name>`

Mark a card done (or set any other status) and persist the updated
plan. Defaults to advancing the next pending card so an operator
can loop `briar plan advance && briar plan next` without naming
keys explicitly.

| Flag | Required | Default | Purpose |
|---|---|---|---|
| `--card <key>` | | next pending | Specific card key (e.g. `KAN-7`, `acme/api#42`) |
| `--status {pending,in_progress,done,blocked}` | | `done` | Status to set |

```bash
# I just merged the first card
briar plan advance owlid-q3

# Mark a specific card in_progress (the implementer agent picked it up)
briar plan advance owlid-q3 --card KAN-7 --status in_progress

# A card got blocked on an external dep
briar plan advance owlid-q3 --card KAN-9 --status blocked
```

### `briar plan list`

Enumerate stored plans (blob name only). Same store flags as above.

```bash
briar plan list
briar plan list --store postgres --company precisetarget
```

### `briar plan clear <name>`

Remove a stored plan. Confirms by default; pass `--yes` to skip.

```bash
briar plan clear preview --yes
```

### What's in a `PlanCard`

Each card the synthesiser emits carries:

| Field | Source |
|---|---|
| `key` | tracker (Jira issue key, GH `owner/repo#N`, draft slug) |
| `title` / `url` | tracker |
| `summary` | LLM ➜ heuristic (first paragraph of the body) |
| `in_scope` / `out_of_scope` / `risks` | LLM ➜ heuristic (parses `## In Scope` / `## Out of Scope` / `## Risks` blocks) |
| `depends_on` | tracker explicit links + body lines (`Depends on KAN-1`, `Blocked by #42`) + LLM judgement (only when the LLM names a real card key on the same board — never invented) |
| `branch_name` | derived (`briar/<key-slug>`) |
| `branch_parent` | `--default-branch`, or — with `--cascade` — the `branch_name` of the latest dependency in topological order |
| `status` | starts `pending`; mutated by `briar plan advance` |
| `sources` | best-effort URLs the card was assembled from |

The LLM pass is strictly optional and degrades gracefully when no
provider is configured (or the configured provider returns an
unparseable response). The heuristic pass always runs second to
guarantee deterministic defaults.

### Cascade semantics

Without cascade, every card's `branch_parent` is `--default-branch`
(usually `main` or `dev`). With `--cascade`, card B that depends on
A gets `branch_parent = briar/a` so PRs stack instead of competing
for `main`. When a card has multiple dependencies, the parent is the
one that appears latest in the topological order — i.e. the most
recently merged ancestor at implementation time.

```
       --cascade off                       --cascade on
       ────────────────────                ──────────────────────
       main ←── briar/a                    main ←── briar/a
       main ←── briar/b                              ↑
       main ←── briar/c                              briar/b
                                                     ↑
                                                     briar/c
```

---

## `briar scaffold` — JSON config bundles for downstream tools

Emits a JSON bundle that a downstream orchestrator can consume. Two
templates today.

### `briar scaffold implementation`

Issue → plan → human approval → implement / comment.

| Flag | Required | Purpose |
|---|---|---|
| `--prefix <name>` | ✓ | prepended to every resource key |
| `--source {github,bitbucket,jira,aws,sentry}` | | repeatable; selects which sources contribute |
| `--archetype <name>` | | default `engineer`; one of `engineer`, `pr-fixer`, `pr-ci-fixer`, `pr-conflict-resolver`, `triager` |
| `--shape <name>` | | default `plan-approve-act`; one of `plan-approve-act`, `one-shot`, `triage` |
| `--trigger-kind <name>` | | default `github_webhook`; one of `github_webhook`, `bitbucket_webhook`, `schedule_cron`, `manual` |
| `--owner` / `--repo` | when `--source github` | GitHub identity |
| `--bitbucket-workspace` / `--bitbucket-repo` | when `--source bitbucket` | Bitbucket identity |
| `--jira-project` / `--jira-jql` | when `--source jira` | project key (repeatable) + optional JQL filter |
| `--aws-role-arn` / `--aws-external-id` / `--aws-region` / `--aws-services` | when `--source aws` | STS AssumeRole binding + which services to gather |
| `--sentry-org` / `--sentry-project` | when `--source sentry` | org slug + project slug (repeatable; at least one) |
| `--sentry-environment` / `--sentry-level` / `--sentry-query` | with `--source sentry` | optional filters: env list, severity list (`fatal`/`error`/`warning`/`info`/`debug`), Sentry search syntax |
| `--auth-mode {oauth,pat}` | | default `oauth`. Sentry ignores this and always requires a PAT (`--sentry-secret-id`); OAuth not yet supported. |
| `--github-secret-id` / `--bitbucket-secret-id` / `--jira-secret-id` / `--sentry-secret-id` | with `--auth-mode pat` (Sentry: always) | secret UUID holding the source's token |
| `--company <name>` | | splice the company's extracted knowledge into the agent's system_prompt |
| `--model <name>` / `--llm-provider-key <key>` | | LLM defaults baked into the bundle |
| `--out <path>` | | write to file (default: stdout) |

```bash
# GitHub source, OAuth, draft PR after plan-approve flow
briar scaffold implementation \
    --prefix acme-impl \
    --source github \
    --owner iklobato --repo lightapi

# Bitbucket source, app-password auth, hourly cron
briar scaffold implementation \
    --prefix acme-impl \
    --source bitbucket \
    --bitbucket-workspace acme --bitbucket-repo widgets \
    --auth-mode pat --bitbucket-secret-id <uuid> \
    --trigger-kind schedule_cron --schedule "0 * * * *"

# Sentry source (PAT-only), 15-min poll, triage flow.
# Sentry contributes 4 action tools: sentry.{comment_on_issue,resolve_issue,
# assign_issue,ignore_issue}. The triager archetype keeps only the
# comment tool; engineer (default) keeps all four.
briar scaffold implementation \
    --prefix acme-onerror \
    --source sentry \
    --sentry-org acme --sentry-project backend --sentry-project worker \
    --sentry-environment prod --sentry-level error --sentry-level fatal \
    --sentry-secret-id <uuid> \
    --shape triage --archetype triager \
    --trigger-kind schedule_cron --schedule "*/15 * * * *"

# Multi-source (GitHub + Jira + AWS), one-shot agent
briar scaffold implementation \
    --prefix acme-hourly \
    --source github --source jira --source aws \
    --owner iklobato --repo lightapi \
    --shape one-shot --out acme-hourly.json
```

> **Source families.** Each source declares one of two families. `tracker`
> sources (`github`, `bitbucket`, `jira`, `sentry`) read items and contribute
> mutation tools (comment / resolve / assign / open-PR / commit / …). `cloud`
> sources (`aws`) are read-only context — the agent inspects resource state
> but doesn't write through tools. The archetype's `tool_filter` is a
> substring match against `implementation_ref`, so naming verbs consistently
> across sources (e.g. `comment_on_issue`) makes archetype filters compose
> uniformly.

### `briar scaffold pr-fixes`

PR review-comment sweep (no human gate). Same flags as `implementation`
but archetype defaults to `pr-fixer` and shape to `one-shot`.

```bash
briar scaffold pr-fixes \
    --prefix acme-prfix \
    --source github \
    --owner iklobato --repo lightapi \
    --trigger-kind schedule_cron --schedule "0 * * * *"
```

---

## `briar context` — local markdown CRUD

Read/write named blobs in the knowledge store. Same store the
extractors write to. Blob names follow `category:identifier`.

**Common flags:**

| Flag | Purpose |
|---|---|
| `--store {file,postgres}` | default `file` |
| `--root <dir>` | file-store root (default `./knowledge`) |

### `briar context put <name>`

| Flag | Purpose |
|---|---|
| `--content <text>` | inline content; pass `-` to read from stdin |
| `--from-file <path>` | read from file |
| `--category <name>` | override the derived category prefix |

```bash
briar context put knowledge:acme --from-file knowledge/acme.md
briar context put memory:reviewer-iklobato --content "Focuses on typing rigor"
briar context put lessons:python-typing --content - < lessons/typing.md
```

### `briar context get <name>`

Prints the markdown body to stdout. No flags.

```bash
briar context get knowledge:acme
briar context get memory:reviewer-iklobato
```

### `briar context list`

| Flag | Purpose |
|---|---|
| `--prefix <s>` | filter to blobs whose name starts with `<s>` |

```bash
briar context list
briar context list --prefix lessons:
```

### `briar context delete <name>`

| Flag | Purpose |
|---|---|
| `--yes` | skip confirmation |

```bash
briar context delete memory:stale --yes
```

### `briar context categories`

Prints distinct category prefixes. No flags.

```bash
briar context categories
```

---

## `briar dashboard` — read-only HTML status page

Runs an HTTP server with the system status across 24 collector
sections. GET-only by construction.

| Flag | Default | Purpose |
|---|---|---|
| `--host <ip>` | `0.0.0.0` | bind address |
| `--port <n>` | `8080` | bind port |
| `--examples <dir>` | `./examples` | runbook YAML directory |
| `--knowledge-store {file,postgres}` | postgres if `BRIAR_DATABASE_URL` else file | |
| `--knowledge <dir>` | `./knowledge` | file-store root |
| `--log-file <path>` | `/var/log/briar/scheduler.log` | scheduler log to tail |
| `--disk-path <path>` | `/` | which mount to size-watch |
| `--repo-path <dir>` | `.` | git repo to show deploy status from |
| `--secrets-file <path>` | `/etc/briar/secrets.env` | for the secrets-name (no values) panel |
| `--du-path <dir>` | (repeatable) | extra directories to track disk usage on |
| `--once` | — | render once and exit (smoke test) |

```bash
# Standard production invocation
briar dashboard --host 0.0.0.0 --port 8080 \
    --examples ./examples --knowledge ./knowledge --repo-path .

# Postgres-backed knowledge store
BRIAR_DATABASE_URL=postgresql://... briar dashboard --host 127.0.0.1

# Smoke test — render the HTML once and exit
briar dashboard --once > /tmp/dashboard.html
```

---

## `briar auth` — interactive credential acquisition

Five subcommands. The thing-you're-logging-into is the **positional
target** (like `gh auth login`, `vault login`, `op signin`). `--store`
controls *where the resulting credentials are persisted* and defaults
to `$BRIAR_DEFAULT_STORE` then to `envfile`.

### Targets (registered acquirers)

| Target | Flow | Writes (per company) |
|---|---|---|
| `github-pat` | Paste a Personal Access Token | `GITHUB_TOKEN` |
| `github-device` | OAuth device flow (needs `BRIAR_GITHUB_CLIENT_ID`) | `GITHUB_TOKEN` |
| `bitbucket-app-password` | Paste workspace + username + app password | `BITBUCKET_{c}_WORKSPACE` / `_USERNAME` / `_APP_PASSWORD` |
| `aws-static` | Paste static IAM access key | `AWS_{c}_ACCESS_KEY_ID` / `_SECRET_ACCESS_KEY` / `_REGION` |
| `aws-sso` | IAM Identity Center OIDC device-code flow → STS vend | `AWS_{c}_*` (+ records expiry) |
| `jira-token` | Paste API token | `JIRA_{c}_URL` / `_EMAIL` / `_TOKEN` / `_AUTH_KIND=token` |
| `jira-session` | DevTools cookie extraction walkthrough | `JIRA_{c}_URL` / `_TENANT_SESSION_TOKEN` / `_AUTH_KIND=session` |
| `linear-api-key` | Paste personal API key | `LINEAR_{c}_TOKEN` |
| `infisical` | Bootstrap — paste machine-identity creds. Always persists to envfile regardless of `--store`. | `INFISICAL_CLIENT_ID` / `_CLIENT_SECRET` / `_PROJECT_ID` / `_ENV` / `_HOST` |

### Stores (registered persistence backends)

| Store | Notes |
|---|---|
| `envfile` | Resolves to `$BRIAR_SECRETS_FILE` → `/etc/briar/secrets.env` (if exists) → `~/.config/briar/secrets.env`. Atomic replace-in-place. |
| `infisical` | Universal-auth machine identity (configure via `briar auth login infisical` first) |
| `vault` | HashiCorp Vault KV v2 (needs `VAULT_ADDR` + `VAULT_TOKEN`) |
| `aws-secretsmanager` | One secret per name under `briar/` prefix |
| `ssm` | SSM Parameter Store, `SecureString`, `/briar/` prefix |

### `briar auth login <target>`

```
briar auth login <target> [--company <name>] [--store <kind>]
```

| Flag | Purpose |
|---|---|
| `target` (positional, required) | What to log into — one of the targets above |
| `--company <name>` | Per-company namespace (required for vendor targets; ignored for `infisical`) |
| `--store <kind>` | Destination. Default = `$BRIAR_DEFAULT_STORE` or `envfile`. **Ignored** when target is a bootstrap flow (`infisical`) — those always land in envfile, with a warning if you passed something else. |

```bash
# Bootstrap a password manager (one-time, per laptop)
briar auth login infisical
briar auth login vault    # (when VaultLoginFlow lands — placeholder for now)

# Vendor credentials → land in envfile
briar auth login github-pat --company acme
briar auth login aws-sso --company acme

# Vendor credentials → land in Infisical
briar auth login github-pat --company acme --store infisical
briar auth login aws-sso --company acme --store infisical

# Pick a default store once, never re-type --store
export BRIAR_DEFAULT_STORE=infisical
briar auth login jira-session --company acme
```

### `briar auth logout <target>`

Removes every env-var name the target would write. Confirms unless `--yes`.

```bash
briar auth logout aws-sso --company acme --yes
briar auth logout infisical                   # removes the machine identity (forgets the connection)
```

### `briar auth refresh <target>`

Renews short-lived bundles without re-prompting. Paste-based targets (PATs, app passwords, Jira API tokens, Jira session cookies, Infisical machine identity) raise `CredentialExpired` → re-run `login`.

```bash
briar auth refresh aws-sso --company acme   # vends fresh STS creds from cached SSO token
```

### `briar auth list [--store <kind>] [--company <name>]`

Enumerates the credential names held in the chosen store. Names only — never values.

```bash
briar auth list --store envfile
briar auth list --store infisical --company acme
```

### `briar auth status <target>`

Per-key `ok` / `MISS` report for the bundle a target writes. Exits non-zero on any miss.

```bash
briar auth status aws-sso --company acme
briar auth status jira-session --company acme --store infisical
```

---

## `briar secrets` — credential coverage + remote-vault hydrate

Two subcommands.

### `briar secrets doctor`

Walks every runbook YAML's `schedules:` and `messages:` blocks. For
each `(company, extractor, provider)` and `(company, messages,
writer)` tuple, queries the provider/writer's `required_env_vars(company)`
classmethod and reports `ok` / `X MISSING:` per row against the
chosen credential store. Values are never printed.

| Flag | Default | Purpose |
|---|---|---|
| `--examples <dir>` | `./examples` | runbook YAML directory |
| `--store {envfile,aws-secretsmanager,ssm,vault}` | `envfile` | which credential backend to audit against |

```bash
# Default: audit env vars
briar secrets doctor --examples examples/

# Audit against AWS Secrets Manager (paths under /briar/<NAME>)
briar secrets doctor --store aws-secretsmanager

# Audit a single YAML directory
briar secrets doctor --examples examples/
```

Exits non-zero when any row is missing.

### `briar secrets bootstrap`

Fetches secrets from a remote vault and writes them to `os.environ`.
Normally runs automatically at CLI startup via `auto_bootstrap()`;
this subcommand is for testing.

| Flag | Purpose |
|---|---|
| `--kind {infisical}` | force a backend; default is auto-detect via `is_available()` |
| `--dry-run` | run the fetch but DON'T write to env; prints keys that would be set |

```bash
# Auto-detect (runs Infisical if INFISICAL_CLIENT_ID is set)
briar secrets bootstrap

# Dry-run — see what would be hydrated without leaking values
briar secrets bootstrap --dry-run

# Force one backend
briar secrets bootstrap --kind infisical
```

Operator-supplied env vars take precedence over the vault — already-set
keys are preserved (reported as `skipped`).

---

## `briar journal` — decision journal

Every instrumented command records a *session* — a tree of decisions
(which source, which archetype, which trigger, which tools survived the
archetype filter, …) tagged with rationale and the alternatives that
were on the table. Sessions persist to a configurable *store* (system
of record) and fan out to one-or-more *sinks* (publish destinations).

Today the only instrumented command is `briar scaffold`, and the only
store + sink are file-backed. Adding NotionSink / SlackSink to surface
sessions to a team channel is one new module each (Open/Closed —
`JournalSink` ABC + one tuple entry in `briar.journal.sinks`).

### Subcommands

```bash
# Enumerate recent sessions (newest first)
briar journal list [--command scaffold.] [--limit 50]

# Pretty-print one session as markdown
briar journal show <session-id>

# Export one session to a file or stdout
briar journal export <session-id> [--format {markdown,json}] [--out PATH]
```

`--store` and `--root` flags accept the same conventions as the rest
of the CLI (`--store file`, `--root ./journal`). Defaults match the
env-var configuration so an invocation with no flags reads from the
same place the recording side wrote to.

### What gets recorded

Each session captures: session id, command label (`scaffold.implementation`),
target (`acme/widgets`), start + end timestamps, and an ordered list
of `DecisionEvent`s. Each event has:

- `choice` — stable dotted slug (`scaffold.sources`, `scaffold.archetype`, …)
- `value` — what was selected (`["github", "jira"]`, `"engineer"`, …)
- `rationale` — one-sentence why
- `alternatives` — what else was on the menu
- `artifacts` — optional key→value bag (file paths, urls, ids)

### Where it goes

```
./journal/                          ← BRIAR_JOURNAL_ROOT
├── sessions/<YYYY-MM-DD>/<id>.json  ← system of record (FileJournalStore)
└── published/<id>.md                ← human-readable summary (FileSink)
```

The store is queryable (`briar journal list / show / export`); the
sink is a "leave it on disk for the next reader" artifact, ideal for
pasting into a PR description.

### Design

Two abstractions, deliberately separate (Single Responsibility):

| Concern | Pattern | Where |
|---|---|---|
| **Store** — system of record, queryable | Strategy + Registry | `briar/journal/store/` — `JournalStore` ABC, `FileJournalStore`, registered via `build_registry` |
| **Sinks** — publish fan-out, format owned per-destination | Adapter + Registry | `briar/journal/sinks/` — `JournalSink` ABC, `FileSink`, registered via `build_registry` |
| **Lifecycle** — open / record / close | Context manager + Null Object | `briar/journal/_journal.py` — `Journal` façade, `_NoOpJournal` default, `session(...)` context manager |
| **Recording** — what callers do | Façade function | `briar.journal.record(choice, value=..., rationale=...)` — one-liner per decision; goes through the active journal |

Instrumenting a new command = wrap it in `with session(...)` at the
boundary and call `record(...)` at each decision point. The composer's
existing branching is the natural surface — see
`src/briar/iac/scaffold/_composer.py` for the reference pattern.

---

## How the pieces fit together

Three command families, three concerns. Each diagram shows what a
command reads, what it invokes, and what comes out — so you can
predict the blast radius of a change.

### `briar runbook serve <dir>` — the long-running scheduler

```
            ┌──────────────────────────────────────────┐
            │ briar runbook serve runbooks/ --tick 5  │
            └─────────────────────┬────────────────────┘
                                  │
        reads at startup          │           reads at every fire
   ┌──────────────────────┐       │       ┌────────────────────────┐
   │ runbooks/*.yaml     │       │       │ /etc/briar/secrets.env │
   │  (CompanyEntry +     │◄──────┴──────►│  per-fire env vars     │
   │   ScheduleEntry)     │               │  (GITHUB_TOKEN,        │
   └──────────────────────┘               │   JIRA_*, AWS_*, ...)  │
                                          └────────────────────────┘
                                  │
                                  ▼
                     ┌─────────────────────────┐
                     │  scheduler loop (tick)  │
                     │  • registers cron-ish   │
                     │    jobs per ScheduleEntry│
                     │  • fires due jobs       │
                     └────────────┬────────────┘
                                  │
                                  ▼
                     ┌─────────────────────────┐
                     │ RunbookExecutor.extract │
                     └────────────┬────────────┘
                                  │
            ┌─────────────────────┼─────────────────────┐
            ▼                     ▼                     ▼
   ┌────────────────┐  ┌────────────────────┐  ┌──────────────────┐
   │ EXTRACTORS[..] │  │ KnowledgeComposer  │  │ make_store(...)  │
   │  for each      │──▶  .markdown(...)    │──▶  .put_if_changed │
   │  ExtractEntry  │  │ assembles sections │  │   (md5 compare-  │
   │  in schedule   │  │ into one blob      │  │    and-set)      │
   └───────┬────────┘  └────────────────────┘  └────────┬─────────┘
           │                                            │
           ▼                                            ▼
   ┌────────────────┐                          ┌──────────────────┐
   │ provider_class │                          │ KnowledgeStore   │
   │   _for(args)   │                          │  • StoreFile     │
   │  ┌────────────┐│                          │  • StorePostgres │
   │  │ Repository ││                          └──────────────────┘
   │  │ Tracker    ││                                   │
   │  │ Cloud      ││                                   ▼
   │  └────────────┘│                          DO managed PG / files
   └────────────────┘                          ./knowledge/*.md
```

A change in `runbooks/*.yaml` is picked up on the **next** schedule
fire because the executor re-loads the YAML on every iteration. Code
changes need a scheduler restart (the `briar` Python process caches
imported modules).

### `briar runbook extract <file.yaml>` — one-shot

```
   briar runbook extract runbooks/acme.yaml [--task tickets]
                              │
                              ▼
                  Same executor path as `serve`,
                  but runs once and exits.
                  --task filters which ScheduleEntry to fire.
```

Useful for manual smoke tests against a specific task (e.g.
verifying Jira session auth before letting the scheduler run for
24h on its own cadence).

### `briar agent prfix` / `briar agent implement` — autonomous LLM

```
       ┌─────────────────────────────────────────────────────┐
       │ briar agent prfix --company acme                  │
       │   --owner acme-co --repo acme-app              │
       │   --pr 42 --branch feature/x                        │
       │   --runbook runbooks/acme.yaml                   │
       └────────────────────────┬────────────────────────────┘
                                │
        ┌───────────────────────┼────────────────────────┐
        ▼                       ▼                        ▼
   ┌──────────┐         ┌────────────┐         ┌─────────────────┐
   │ secrets  │         │ runbooks/ │         │ KnowledgeStore  │
   │ .env     │         │ acme.yaml│         │ .get("knowledge:│
   │ • GITHUB │         │ • messages │         │      acme")   │
   │ • JIRA_* │         │ • git_id   │         │  (previously    │
   │ • CLAUDE │         └─────┬──────┘         │  written by     │
   └────┬─────┘               │                │  serve)         │
        │                     ▼                └────────┬────────┘
        │           ┌─────────────────┐                 │
        │           │_resolve_git_id  │                 │
        │           │  (CLI > YAML >  │                 │
        │           │   default)      │                 │
        │           └────────┬────────┘                 │
        │                    │                          │
        ▼                    ▼                          ▼
   ┌──────────────────────────────────────────────────────────┐
   │ RepoCloner.clone(branch) → /tmp/<worktree>               │
   │   • sets user.name + user.email on the clone             │
   └────────────────────────────┬─────────────────────────────┘
                                │
                                ▼
   ┌──────────────────────────────────────────────────────────┐
   │ FetchPrContext (JIT extractor: reads PR + review thread) │
   └────────────────────────────┬─────────────────────────────┘
                                │
                                ▼
   ┌──────────────────────────────────────────────────────────┐
   │ AgentRunner (Anthropic API + tool-use loop)              │
   │   tools available:                                       │
   │     • bash, read_file, write_file, edit_file             │
   │     • send_message ←──┐                                  │
   └────────────┬──────────│──────────────────────────────────┘
                │          │
                │          │ resolved via messages: block:
                │          │   handle → MessageWriter
                │          │     ├── jira-comment / jira-transition
                │          │     │     (uses JiraAuthStrategy)
                │          │     ├── github-pr-comment
                │          │     ├── bitbucket-pr-comment
                │          │     ├── slack-channel
                │          │     └── telegram-chat
                │          │
                ▼
       commits + push via the same RepositoryProvider
       used in the scheduler — closes the loop.
```

`briar agent implement` is the same shape, replacing
`FetchPrContext` with `FetchTicketContext` (which reads from the
TrackerProvider for the company's chosen tracker).

Both `prfix` and `implement` *also* fire `FetchMeetingContext` when
`--meeting-key` or `--meeting-query` resolves to something — for
`implement` the default query is the ticket key; for `prfix` it's
`owner/repo#pr`. Reads from the `MeetingProvider` registry
(`extract/_meetings/`, today: `fireflies`). Spliced into the agent's
system prompt alongside the ticket/PR context so decisions captured
in standups land in the code path that touches them.

### `briar plan build` / `briar plan next` — sequenced implementation

```
   ┌─────────────────────────────────────────────────────┐
   │ briar plan build <board> --name X --cascade        │
   │   [--llm anthropic] [--with-knowledge]             │
   └────────────────────────┬────────────────────────────┘
                            │
        ┌───────────────────┼───────────────────────────┐
        ▼                   ▼                           ▼
  ┌────────────┐    ┌────────────────┐         ┌────────────────┐
  │ secrets    │    │ BoardReader    │         │ KnowledgeStore │
  │ .env       │    │   .matches/    │         │  (existing     │
  │ • GITHUB   │    │   .parse/      │         │   knowledge:X  │
  │ • JIRA_*   │    │   .fetch       │         │   blobs spliced│
  │ • CLAUDE   │    │ ├── JiraBoard  │         │   in when      │
  └────┬───────┘    │ └── GhProjectV2│         │  --with-knowledge)│
       │            └────────┬───────┘         └────────┬───────┘
       ▼                     ▼                          ▼
  ┌──────────────────────────────────────────────────────────┐
  │ CardSynthesiser (Composite: LLM → Heuristic)             │
  │   LLM pass:    summary, scope, out-of-scope, risks       │
  │   Heuristic:   parses ## In Scope / Depends on lines     │
  └────────────────────────────┬─────────────────────────────┘
                               │
                               ▼
  ┌──────────────────────────────────────────────────────────┐
  │ topological_sort + apply_cascade                         │
  │   • Kahn's algorithm (stable, cycle-detecting)           │
  │   • branch_parent = default-branch                       │
  │     OR (in cascade) latest dep's branch_name             │
  └────────────────────────────┬─────────────────────────────┘
                               │
                               ▼
       save_plan(store, plan)   ──▶ plan:<name> blob
                               │
                               ▼
       briar plan next <name>   ──▶ first card whose deps are all done
       briar plan advance       ──▶ updates status, persists, repeat
```

The implementer agent (`briar agent implement`) is the natural
downstream consumer: `briar plan next --format json | jq …` yields
the `key` to pass as `--ticket-key`, the `branch_name` to use as
the feature branch, and the `branch_parent` to clone from. Cascade
mode is what lets a long sequence of dependent tickets be shipped
without each card sitting on `main` until the previous merges.

### DSN resolution — `knowledge.store: postgres`

```
KnowledgeBinding (from YAML)
   │
   │   knowledge:
   │     store: postgres
   │     config:
   │       dsn_env: BRIAR_KB_DATABASE_URL   ← explicit
   │
   ▼
StoreBinding(company="acme", config={...})
   │
   ▼
StorePostgres.from_binding(binding):
   ┌──────────────────────────────────────────────────────────┐
   │ 1. binding.config["dsn_env"]      → ${BRIAR_KB_DATABASE_URL}
   │ 2. BRIAR_{COMPANY}_DATABASE_URL   → ${BRIAR_ACME_DATABASE_URL}
   │ 3. BRIAR_DATABASE_URL             → ${BRIAR_DATABASE_URL}
   │ 4. CliError naming all 3 keys tried, in order
   └──────────────────────────────────────────────────────────┘
   │
   ▼
returns psycopg-backed StorePostgres instance
```

The first non-empty env var wins. Every downstream caller
(`scheduler`, `briar context`, `briar agent`'s `KnowledgeStore.get`)
goes through the same factory — no parallel resolution paths.

### Jira auth strategy chain

```
JiraTracker(company="acme")
   │
   ▼
JiraAuthRegistry.autodetect(company="acme"):
   ┌──────────────────────────────────────────────────────────┐
   │ 1. JIRA_{COMPANY}_AUTH_KIND env (explicit override)      │
   │     "token"   → JiraTokenAuth                            │
   │     "session" → JiraSessionAuth                          │
   │ 2. JIRA_{COMPANY}_SESSION_TOKEN OR _TENANT_SESSION_TOKEN │
   │    set        → JiraSessionAuth                          │
   │ 3. fallback   → JiraTokenAuth                            │
   └──────────────────────────────────────────────────────────┘
   │
   ▼
strategy.configure(company, base_url)
   │
   ├── token   →  {username, password}        ── HTTP Basic
   │
   └── session →  {session: requests.Session(
                    cookies={cloud.session.token, tenant.session.token,
                             atlassian.xsrf.token},
                    headers={Origin, Referer, User-Agent,
                             sec-ch-ua-*, sec-fetch-*,
                             X-Atlassian-Token: no-check})}
                  ── browser-mimicking
   │
   ▼
atlassian.Jira(url=..., cloud=True, **kwargs)
```

The same `JiraTracker` is used by both the scheduler's
`active-tickets` / `ticket-archaeology` extractors AND the agent's
`jira-comment` / `jira-transition` message writers — so a working
session auth for one path is a working session auth for the other.

### `briar auth login <target>` — acquisition + persistence

```
                briar auth login <target> [--company X] [--store Y]
                                  │
              positional target   │   --store decides persistence
                  resolves to     │   IF the target's policy is EXTERNAL
                  ▼               │   (forced to envfile if BOOTSTRAP_LOCAL)
          ┌─────────────────┐     │
          │ AcquirerRegistry│     │
          │  .make(target)  │     │
          └────────┬────────┘     │
                   │              │
                   ▼              │
          ┌─────────────────┐     │
          │ acquirer.acquire│     │
          │  (company,      │     │
          │   prompt)       │     │
          └────────┬────────┘     │
                   │              │
                   ▼              │
              Credentials         │
            (provider_kind,       │
             entries dict,        │
             expires_at)          │
                   │              │
                   ▼              │
        ┌───────────────────┐     │
        │ _effective_store  │◄────┘
        │ honours policy:   │
        │  EXTERNAL → as-is │
        │  BOOTSTRAP_LOCAL  │
        │   → "envfile"     │
        └─────────┬─────────┘
                  │
                  ▼
        ┌──────────────────────┐
        │ CredentialStore      │
        │  .write(name, value) │
        │   for each entry     │
        │                      │
        │ Backends:            │
        │  • EnvFileStore      │
        │  • InfisicalStore    │
        │  • VaultStore        │
        │  • AwsSecretsMgr     │
        │  • SsmParameterStore │
        └──────────────────────┘
```

The target's `destination_policy` ClassVar splits two flavours:
- **EXTERNAL** (default) — vendor credentials (GitHub, AWS, Jira,
  Linear, Bitbucket). The operator picks `--store` freely.
- **BOOTSTRAP_LOCAL** — the credentials *describe how to reach a
  store* (Infisical machine identity; future `VaultLoginFlow`).
  Must persist to envfile or the bootstrap is unrecoverable
  (chicken-and-egg). The CLI logs a warning if `--store` is ignored.

Adding a new acquirer = one class + one registry entry. Adding a
new store = one class + one registry entry. Adding a new
destination policy (rare — e.g. "must persist to keychain") = one
enum value + one branch in `_effective_store_kind`.

### What invalidates what

| You changed... | Restart needed | Effect |
|---|---|---|
| `runbooks/*.yaml` | no (next fire) | scheduler re-reads on every tick |
| `/etc/briar/secrets.env` | yes | scheduler holds env in process memory |
| `src/briar/` (editable install) | yes | imported modules are cached |
| Postgres `briar_knowledge` table | no | scheduler reads fresh on each fire |
| Jira session-token cookie | no — but log it | scheduler reads from env at startup; restart picks up rotation |

---

## Releases

Releases are fully automated. Every push to `main` triggers
[`.github/workflows/release.yml`](.github/workflows/release.yml), which:

1. Bumps the patch version in `pyproject.toml` (1.1.1 → 1.1.2 → …).
2. Builds the sdist + wheel with `uv build`.
3. Publishes to PyPI via `pypa/gh-action-pypi-publish` using the
   `PYPI_API_TOKEN` repo secret.
4. Commits the bump as `chore(release): vX.Y.Z` and pushes a matching
   `vX.Y.Z` tag back to `main`.

The workflow guards against its own bump commit re-triggering itself by
skipping when `github.event.head_commit.message` starts with
`chore(release):`. `concurrency: release` serializes overlapping merges.

No manual `git tag` or `twine upload` step. To publish a release without
a merge, use the workflow's `workflow_dispatch` trigger from the Actions
tab.

---

## Examples + further reading

- `runbooks/` (gitignored — keep your real runbooks here so they
  don't leak into the public repo). Recommended pattern: one YAML
  per company, all with `knowledge.config.dsn_env: BRIAR_KB_DATABASE_URL`
  so they share a managed-Postgres knowledge store with row-level
  partitioning by the `company` column. Real-company planning docs
  (e.g. project roadmaps) belong here too.
- [`examples/all_features.yaml`](examples/all_features.yaml) — every
  abstraction × provider × writer combination across 4 companies.
  Schema reference for `knowledge.config`, `messages:`, `git_identity:`,
  and the Jira auth-strategy selector.
- [`examples/multi_company.yaml`](examples/multi_company.yaml) +
  [`.env.example`](examples/multi_company.env.example) — 3-company
  tutorial without the `messages:` block.
- [`IMPLEMENTATION_PLAN.md`](IMPLEMENTATION_PLAN.md) — per-provider
  credential acquisition guide (incl. Jira API token AND
  browser-session-cookie paths)
- [`DEPLOY_EC2.md`](DEPLOY_EC2.md) — systemd deployment recipe
- [`ARCHITECTURE.md`](ARCHITECTURE.md) +
  [`ARCHITECTURE_DEEP.md`](ARCHITECTURE_DEEP.md) — abstraction
  inventory + SOLID audit
