brief

brief CLI — Design Document

brief is a standalone Go CLI binary backed by SQLite, designed as a personal knowledge base for humans and agents alike. Any agent or script that can run a shell command can use it. This document is the complete implementation specification: command surface, output format, data schema, and repository structure.

$ brief --help
usage: brief [-v | --version] [-h | --help] [--db=<path>] <command> [<args>]

These are the available commands:
    get         Query the knowledge base.
    post        Save a new entry to the knowledge base.
    put         Update an existing entry in the knowledge base.
    deprecate   Soft-delete an entry (preferred lifecycle operation).
    delete      Hard-delete an entry. Irreversible. Use deprecate instead.
    category    View available categories.
    tag         View or manage tags.
    config      Set or view brief configuration.
Persistent flags (valid before any subcommand):
    --db=<path>     Path to the SQLite database file.
                    Precedence: --db > $BRIEF_DB > ~/.brief/brief.db

$ brief get --help
usage: brief get "<query>" [<flags>]
       brief get --id=<id>
       brief get --recent
       brief get --recent-updates

    Primary use: natural language query against the knowledge base.
    Returns entries ranked by FTS relevance (rank field).
    Designed for agent use: brief get "what do I know about hooks in VS Code"

    -n, --limit=<n>             Maximum number of results to return. (default: 10)
        --id=<id>               Fetch a single entry by integer ID. Returns full content.
        --category=<cat>        Filter by category.
        --tag=<tag>             Filter by tag (repeatable).
        --language=<lang>       Filter by language hint.
        --recent                Return most recent entries by created_at. (default: 10)
        --recent-updates        Return most recently updated entries by updated_at. (default: 10)
        --full                  Return full content instead of truncated preview.
        --include-deprecated    Include deprecated entries in results.
        --json                  Output as JSON.

    Modes --id, --recent, --recent-updates, and "<query>" are mutually exclusive.
    Filter flags (--category, --tag, --language) are valid with "<query>", --recent,
    and --recent-updates. Combining any filter flag with --id is a usage error (exit 1).

$ brief post --help
usage: brief post --title=<title> [<content>] [<flags>]

    Required:
    -t, --title=<title>     Title of the entry.

    Optional:
        --category=<cat>    Category: note | lesson | decision | preference |
                            workflow | snippet  (default: note)
        --tag=<tag>         Tag(s) to apply (repeatable).
        --language=<lang>   Language hint for code snippets.
        --stdin             Read content from stdin.

    [<content>] and --stdin are mutually exclusive.
    If neither [<content>] nor --stdin is provided, this is a usage error (exit 1).
    content is required by schema — it cannot be empty or omitted.

$ brief put --help
usage: brief put <id> [<content>] [<flags>]

    <id> is the integer ID of the entry to update.

        --title=<title>         Update title.
        --category=<cat>        Update category.
        --tag=<tag>             Replace all tags (repeatable). Replaces existing tags entirely.
        --add-tag=<tag>         Add a tag without affecting existing tags (repeatable).
        --remove-tag=<tag>      Remove a specific tag (repeatable).
        --language=<lang>       Update language hint.
        --undeprecate           Clear deprecated_at, restoring the entry to active.
        --stdin                 Read updated content from stdin.

    [<content>] and --stdin are mutually exclusive.
    At least one of [<content>], --stdin, or an update flag must be provided.
    Running 'brief put <id>' with nothing else is a usage error (exit 1).
    --tag, --add-tag, and --remove-tag may be combined in a single put call.
    When combined, order of operations is: --tag (replace all)--remove-tag--add-tag.
    Use 'brief deprecate <id>' for soft-delete lifecycle operations.

$ brief deprecate --help
usage: brief deprecate <id>

    Soft-delete an entry by setting deprecated_at to now.
    The entry is retained in the database and excluded from get results by default.
    Reversible via: brief put <id> --undeprecate
    This is the standard lifecycle operation for outdated or superseded entries.

$ brief delete --help
usage: brief delete <id> [<flags>]

    Hard-delete an entry. This is irreversible and permanently removes the entry
    from the database. Use 'brief deprecate' instead for normal lifecycle management.

        --force           Skip confirmation prompt.

    In non-TTY contexts (piped input, scripting), --force is required.
    Without --force in a non-TTY context, brief exits 1 with a message directing
    use of --force. No interactive prompt is shown.

$ brief category --help
usage: brief category list [<flags>]

    list                  List all categories with entry counts. Counts exclude
                          deprecated entries.

    'brief category' without 'list' is a usage error (exit 1). There is no implicit alias.
    Categories are a fixed enum — not user-extensible in v1.
    Valid values: note | lesson | decision | preference | workflow | snippet
    Unknown category on post/put is an error; brief will suggest the closest match.
        --json            Output as JSON.

$ brief tag --help
usage: brief tag <subcommand> [<args>]

    list                  List all tags in use with entry counts (global inventory),
                          sorted by frequency (most-used first). Counts exclude
                          deprecated entries.
    rename <old> <new>    Rename a tag across all entries in the knowledge base.
                          If <old> does not exist, exits 3 (not found).
    delete <tag>          Remove a tag from all entries. Silent and non-blocking —
                          never fails if no entries use the tag.

    Tags are free-form — any string is accepted on post/put.
    Use 'brief tag list' for discovery before filtering with brief get --tag=<tag>.
        --json            Output as JSON.

$ brief config --help
usage: brief config <subcommand> [<args>]

    get <key>             Print the value of a config key.
    set <key> <value>     Set a config key.
    list                  List all config keys and values.
        --json            Output as JSON.

    Valid keys:
        default_limit       Default result count for brief get (default: 10).
                            Must be a positive integer; invalid value is exit 1.
        default_category    Default category for brief post (default: note).
                            Must be a valid category enum value; invalid value is exit 1.

    Unknown keys are a usage error (exit 1).

    --json behavior:
        config list --json    returns {"default_limit": <int>, "default_category": "<str>"}
        config get --json     returns {"key": "<key>", "value": <value>}
        config set            no --json flag; always silent on success (exit 0)

    Precedence for default_limit: --limit flag > default_limit config > built-in default (10).
    --recent and --recent-updates honor default_limit when --limit is not provided.

    'brief config get <key>' for a valid but unset key prints the built-in default value.
    It never returns empty output for a valid key.

Output Format: brief get

The default output of brief get is plain text, optimized for agent use as additionalContext in hook scripts. Content is truncated to keep output scannable.

[494] VS Code Copilot Hooks — UserPromptSubmit + PreCompact (2026-03-12)
category: note | tags: hooks, vs code, copilot, UserPromptSubmit, PreCompact
---
Extended the existing hooks setup in jsldvr/notes with two new lifecycle hooks:
UserPromptSubmit and PreCompact. UserPromptSubmit fires on every prompt and emits
a systemMessage instructing the agent to search the knowledge base before responding.
[truncated — use --id=494 for full content]

[441] VS Code Copilot Hooks — Exploration Session (2026-03-04)
category: note | tags: hooks, vs code, copilot, automation, git
---
Explored VS Code Copilot agent hooks. Hooks execute shell commands at deterministic
lifecycle points. Exit 0 → parse stdout. Exit 2 → hard block the operation.
[truncated — use --id=441 for full content]

Design rules

JSON output shape

brief get --json — array of entry objects, even for --id (always an array):

[
  {
    "id": 494,
    "title": "...",
    "content": "...",
    "category": "note",
    "language": null,
    "tags": ["hooks", "go"],
    "created_at": "2026-03-12T00:00:00Z",
    "updated_at": "2026-03-12T00:00:00Z",
    "deprecated_at": null
  }
]

rank is never present in JSON output.

brief tag list --json — array: [{"tag": "hooks", "count": 12}]

brief category list --json — array: [{"category": "note", "count": 42}]

brief config list --json — object (fixed keys map naturally): {"default_limit": 10, "default_category": "note"}

Exit codes

Code Meaning
0 Success (including empty results)
1 Usage error (bad flags, missing required args)
2 Database error (init failure, read/write error)
3 Entry not found (for --id, put, deprecate, delete)

All diagnostic messages go to stderr. All result data goes to stdout.

stdout / stderr rules


Schema

entries
  id            INTEGER   primary key, auto-increment
  title         TEXT      required
  content       TEXT      required
  category      TEXT      required; one of: note | lesson | decision |
                          preference | workflow | snippet
  language      TEXT      nullable; language hint for snippet entries
  created_at    DATETIME  auto-set on insert
  updated_at    DATETIME  auto-set on insert; updated on put
  deprecated_at DATETIME  nullable; set by deprecate, cleared by put --undeprecate

tags
  id            INTEGER   primary key
  entry_id      INTEGER   foreign key → entries.id
  tag           TEXT
  UNIQUE(entry_id, tag)

Full-text search index (FTS5) covers title and content. Ranking uses BM25 with title weighted 10× over content. Results are further ordered by tag match (entries whose tags match query tokens rank higher) and recency decay. When an AND query returns no results, the query is retried with OR automatically.

config
  key           TEXT   primary key; one of: default_limit | default_category
  value         TEXT   not null

Database location and initialization

Default DB path: ~/.brief/brief.db

Override via environment variable: BRIEF_DB=/path/to/brief.db

On first run, brief creates ~/.brief/ and initializes the schema automatically. No manual setup required. Running any command against a missing DB path bootstraps it.

The DB file is a standard SQLite3 file — portable, backupable, and inspectable with any SQLite tool.


Repository Layout

Module: github.com/jsldvr/brief

Standard Go layout — no cleverness. CLI binary only; nothing is exported for external use in v1.

brief/
├── cmd/
│   └── brief/
│       └── main.go          # entry point; calls cli.Execute()
├── internal/
│   ├── cli/
│   │   ├── root.go          # cobra root command; resolves --db / BRIEF_DB; persistent flags
│   │   ├── get.go           # get command and all its flags
│   │   ├── post.go          # post command
│   │   ├── put.go           # put command (--undeprecate, --add-tag, --remove-tag)
│   │   ├── deprecate.go     # deprecate command
│   │   ├── delete.go        # delete command (--force guard)
│   │   ├── tag.go           # tag list / rename / delete subcommands
│   │   ├── category.go      # category list subcommand
│   │   └── config.go        # config get / set / list subcommands
│   ├── db/
│   │   ├── db.go            # open connection; run migrations; bootstrap schema on first run
│   │   ├── entries.go       # CRUD operations and FTS5 search for entries
│   │   ├── tags.go          # tag CRUD; rename across entries; delete with silent cleanup
│   │   └── config.go        # key/value config store (fixed key enumeration enforced here)
│   └── output/
│       ├── text.go          # plain-text formatters (entry summary, full, tag/category lists)
│       └── json.go          # JSON marshal helpers for --json flag
├── go.mod
├── go.sum
├── Makefile                 # targets: build, install, test, lint
└── README.md

Dependencies

Dependency Purpose
github.com/spf13/cobra CLI framework; handles 3-level command tree cleanly
modernc.org/sqlite Pure Go SQLite driver; no CGo, no C toolchain; FTS5 supported
github.com/mattn/go-isatty Detect TTY to require --force on brief delete in non-TTY contexts

Layer rules