Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ on:
push:
branches: [main, develop]
pull_request:
branches: ["*"]
# `**` rather than `*`: in Actions branch filters `*` does not match `/`,
# so a pull request targeting any branch with a slash in its name -- every
# stacked pull request, and anything under a `feat/` or `fix/` prefix --
# ran no checks at all.
branches: ["**"]

env:
CARGO_TERM_COLOR: always
Expand Down
41 changes: 22 additions & 19 deletions docs/audits/agent-compatibility-2026-09.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,21 +72,21 @@ or maintenance cost · **P2** = polish.

### 3.1 Defects to fix

**P0-1 — The Ollama provider cannot work as written.**
**P0-1 — The Ollama provider could not work as written.** ✅ **Fixed.**
`src/ai/mod.rs:160-205` posts to `/api/chat` without `stream: false`. Ollama
defaults to `stream: true`, so the response is newline-delimited JSON events, and
`resp.json::<ResponseBody>()` will fail to deserialize on essentially every call.
Every `--ai-provider ollama` run therefore ends in `LlmError::Parse` and silently
falls back to the default message. Fix: send `stream: false`.

**P0-2 — Ollama JSON mode is a no-op.**
**P0-2 — Ollama JSON mode was a no-op.** ✅ **Fixed.**
`format` is nested inside `options` (`src/ai/mod.rs:174-196`). Ollama reads
`format` as a **top-level** request field, so JSON mode has never taken effect.
Fix: hoist `format` to the request body, and prefer passing the
`AiCommitSuggestion` JSON Schema rather than the string `"json"` — Ollama has
supported schema-constrained decoding since 0.5.

**P0-3 — AI in default (safe) mode is given no signal at all.**
**P0-3 — AI in default (safe) mode was given no signal at all.** ✅ **Fixed.**
`src/cli/commands/group_commit.rs:400-413`: unless `--ai-allow-sensitive` is set,
the user prompt contains only the group name and the default type/short, and
instructs the model to work "without revealing code or filenames". The model is
Expand All @@ -96,20 +96,24 @@ real signal by default — path *shapes* (extension histogram, directory prefixe
add/modify/delete counts, scope mappings from `.committy/config.toml`) — none of
which is file content. Reserve `--ai-allow-sensitive` for actual diff hunks.

**P0-4 — `--ai` is silently ignored in `--mode apply`.**
The AI block lives only in the `"plan"` arm (`group_commit.rs:308-507`); the
`"apply"` arm (`:533+`) rebuilds groups from scratch with no LLM call.
`docs/src/content/docs/reference/ai-flags.mdx` documents
`--mode apply ... --ai --ai-provider ollama` as a working example. Either wire
apply to reuse the planned messages, or reject `--ai` with `--mode apply` and fix
the doc. Silently dropping a flag is the worst of the three options.
**P0-4 — WITHDRAWN. This finding was wrong.** ❌
The original text claimed `--ai` was silently ignored in `--mode apply`. It is
not: the apply arm has its own AI block (`group_commit.rs:586`) with its own
`suggest_commit` calls. The error came from reading a `grep` run through
`awk 'NR>=530'`, whose renumbered output was mistaken for line numbers in the
plan arm. The documented `--mode apply ... --ai` example always worked.

**P1-5 — `--ai-diff-lines-per-file` is a dead flag.**
What is real in that area is **duplication**: `plan` and `apply` carried
byte-identical copies of both the group-building loop and the ~150-line AI
enrichment block. That duplication is what made the misreading possible. Both
are now extracted into `build_groups()` and `ai_user_prompt()` and shared. ✅ **Fixed.**

**P1-5 — `--ai-diff-lines-per-file` was a dead flag.** ✅ **Fixed** (removed).
Declared at `group_commit.rs:143-147` as `_ai_diff_lines_per_file` and never read.
It is documented in `ai-flags.mdx` and accepted on the command line. Remove it, or
implement it as part of P0-3.

**P1-6 — `ai-flags.mdx` overstates what is sent and understates what is not.**
**P1-6 — `ai-flags.mdx` overstated what is sent.** ✅ **Fixed.**
It claims `--ai-allow-sensitive` "may include snippets or diff lines from code" —
no diff is ever sent, in either mode. It also recommends
`--ai-model openrouter/anthropic/claude-3.5-sonnet`, a model generation that is two
Expand Down Expand Up @@ -250,14 +254,13 @@ steal both shorts. A test pins that trade-off.
0. ~~**P1-7**, **E-1**, **E-2**, **E-3**~~ — done: capabilities expanded with
consent metadata, global flag placement, `invalid_usage` envelope, env var
documented. See §3.5.
1. **P0-1, P0-2** — make Ollama actually function.
2. **P0-4, P1-5, P1-6, §3.3 removals** — stop documenting and accepting things
that do not happen.
3. **P0-3** — give the default-safe AI path real, non-sensitive signal.
4. **P1-8, P1-9, P1-10** — cross-tool portability (`AGENTS.md` canonical,
1. ~~**P0-1, P0-2, P0-3**, **P1-5**, **P1-6**~~ — done: Ollama functions,
the safe AI path carries redacted shape, dead flag and docs corrected.
**P0-4 was withdrawn as a false finding.**
2. **P1-8, P1-9, P1-10** — cross-tool portability (`AGENTS.md` canonical,
`.agents/skills/`, spec-complete frontmatter).
5. **P1-12** — manifest and frontmatter tests so the step above cannot regress.
6. **P1-11, P2-13, P2-14, P2-15** — reach, CI cost, consistency, guardrail honesty.
3. **P1-12** — manifest and frontmatter tests so the step above cannot regress.
4. **P1-11, P2-13, P2-14, P2-15** — reach, CI cost, consistency, guardrail honesty.

## 5. Sources

Expand Down
62 changes: 47 additions & 15 deletions docs/src/content/docs/reference/ai-flags.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This page enumerates flags, best practices, and security considerations.

## Enabling AI

Add `--ai` to `group-commit`:
Add `--ai` to `group-commit`. It applies in both `--mode plan` and `--mode apply`:

```bash
committy --non-interactive group-commit --mode plan --output json \
Expand All @@ -29,48 +29,80 @@ committy --non-interactive group-commit --mode plan --output json \
- `--ai-max-tokens <n>` — model tokens cap for responses
- `--ai-temperature <float>` — creativity setting
- `--ai-timeout-ms <ms>` — request timeout
- `--no-ai-json-mode` — do not force JSON-mode (advanced; prefer default)
- `--no-ai-json-mode` — do not request structured output (advanced; prefer default)
- `--ai-system-prompt <text>` — inline system prompt override
- `--ai-system-prompt-file <path>` — load system prompt from file
- `--ai-file-limit <n>` — limit how many files are described to AI
- `--ai-allow-sensitive` — allow sending sensitive file content (off by default)
- `--ai-file-limit <n>` — cap how many paths are listed under `--ai-allow-sensitive`
- `--ai-allow-sensitive` — send real file paths instead of a redacted summary (off by default)

## What is actually sent

__No file content or diff text is ever sent__, in either mode. The difference
between the default and `--ai-allow-sensitive` is paths, not content.

By default Committy sends a redacted *shape* of each group — how many files,
an extension histogram, and the top-level directories involved:

```text
Group: code
Default type: feat
Default short: update source
Change shape (no filenames or content): 7 file(s); types: .rs x6, .toml x1; areas: src, tests
```

Filenames, paths below the first segment, and file contents are all withheld.
This is enough signal for a model to write a sensible subject line without the
provider learning your module, product, or customer names.

With `--ai-allow-sensitive`, the group's file paths are listed instead, capped
by `--ai-file-limit` and elided with `...` beyond it. Use it only when you trust
the provider with your repository layout.

## Security & privacy

- __Sensitive content is not sent__ by default. The tool shares filenames and summarized context.
- Only pass `--ai-allow-sensitive` if required and you trust the provider. This may include snippets or diff lines from code.
- Prefer using an API key via env var name with `--ai-api-key-env` to avoid leaking keys in process lists or logs.
- Use `--ai-file-limit` to reduce context size and exposure.
- Consider on-premise providers (`ollama`) for maximum privacy.
- Prefer the default. The redacted summary is usually sufficient.
- Pass the API key by env var *name* with `--ai-api-key-env` so the secret never
appears in a process list, shell history, or CI log.
- Use `--ai-file-limit` to bound exposure when `--ai-allow-sensitive` is on.
- Consider an on-premise provider (`ollama`) when no data should leave the host.

## Failure handling

- AI suggestions are validated by Committy's linter. If invalid or timed out, Committy falls back to safe default messages per group.
- JSON parsing is strict; malformed outputs are rejected and replaced with defaults.
- AI suggestions are validated by Committy's linter. If a suggestion is invalid
or the request times out, Committy keeps the safe default message for that group
and records the reason in `errors`.
- Structured output is requested by default, and malformed responses are rejected
rather than committed.

## Provider examples

### OpenRouter

```bash
# export your key and reference by env var name
# export your key and reference it by env var name
export OPENROUTER_API_KEY=sk-or-...

committy --non-interactive group-commit --mode plan --output json \
--ai --ai-provider openrouter \
--ai-model openrouter/anthropic/claude-3.5-sonnet \
--ai-model openrouter/anthropic/claude-sonnet-4.5 \
--ai-api-key-env OPENROUTER_API_KEY \
--ai-max-tokens 600 --ai-temperature 0.2
```

JSON mode sends `response_format: {"type": "json_object"}`.

### Ollama (local)

```bash
# ensure Ollama is running locally; default base URL is http://127.0.0.1:11434
# ensure Ollama is running locally; default base URL is http://localhost:11434
# no API key needed for local Ollama

committy --non-interactive group-commit --mode apply --output json \
--ai --ai-provider ollama \
--ai-model llama3.1:8b \
--ai-timeout-ms 30000 --ai-file-limit 64
--ai-timeout-ms 30000
```

Requests to Ollama set `stream: false` and pass a JSON Schema in the top-level
`format` field, so the model's output is constrained to the expected object
rather than merely asked for as JSON.
Loading
Loading