Skip to content
Merged
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
35 changes: 33 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,38 @@ is simply a separate distribution kbagent no longer manages.
- `plugins/kbagent/.claude-plugin/plugin.json` must match. Run `make version-sync` (or `python scripts/sync_version.py`) to update it.
- The pre-commit hook and CI automatically check version consistency.

**When bumping the version**: edit `pyproject.toml`, add a changelog entry to `src/keboola_agent_cli/changelog.py`, then run `make version-sync`. Do not edit `__init__.py` or `plugin.json` manually. CI enforces changelog completeness via `make changelog-check`.
### Version bumps happen ONLY in a dedicated release PR

**Feature/fix PRs MUST NOT bump the version and MUST NOT add a `changelog.py`
entry.** PRs are routinely developed in parallel (separate AI sessions, one per
issue); when each of them bumps `pyproject.toml`, every merge is a version
conflict and the merge order silently renumbers releases. The historical
`KNOWN_UNRELEASED` list in `scripts/generate_changelog.py` -- 18 versions whose
content shipped with no release notes -- is exactly what that pattern produces.

The flow is instead:

1. **Feature PR**: merges to `main` with NO change to `pyproject.toml`
`version` and NO `changelog.py` entry. When the PR documents version-gated
behavior (gotchas.md, commands-reference.md, this file), it tags it with
the literal placeholder **`vNEXT`** -- `(since vNEXT)` / `vNEXT+`.
`make version-gate-check` ignores the placeholder (it only matches numeric
versions) but rejects a guessed numeric version with no changelog entry,
so a numeric future tag cannot even pass CI.
2. **Release PR** (its only job): bump `pyproject.toml`, review everything
merged since the last release (`git log v<last>..origin/main`), write ONE
changelog entry covering all of it (one bullet per logical change, with
`(#PR)` references), replace every `vNEXT` placeholder with the real
version, run `make version-sync`. Merge, then tag -- the release pipeline
renders the GitHub release notes from the changelog entry
(`scripts/gen_release_notes.py`). Full checklist: `CONTRIBUTING.md` >
"Releasing a new version".

**Inside the release PR**: edit `pyproject.toml`, add the changelog entry to `src/keboola_agent_cli/changelog.py`, then run `make version-sync`. Do not edit `__init__.py` or `plugin.json` manually. `make changelog-check` (release-time, needs `gh`) enforces changelog completeness in both directions.

The one exception is a **beta/pre-release** (below): there the bump deliberately
rides the feature branch, because the pre-release tag and GitHub Release are cut
from that branch -- the branch temporarily *is* the release PR.

### Beta / pre-release versions (since 0.43.3)

Expand Down Expand Up @@ -237,7 +268,7 @@ Full author checklist: see `CONTRIBUTING.md` > "Releasing a beta (pre-release) v
- `plugins/kbagent/agents/keboola-expert.md` -- **highest risk** (Rule 6 VERSION GATE, tool selection matrix, inline gotchas)
- `plugins/kbagent/skills/kbagent/SKILL.md` -- description triggers and workflow links (the auto-generated table is CI-checked, the rest is not)
- `plugins/kbagent/skills/kbagent/references/commands-reference.md`
- `plugins/kbagent/skills/kbagent/references/gotchas.md` (every new gotcha **MUST** be tagged with `(since vX.Y.Z)`)
- `plugins/kbagent/skills/kbagent/references/gotchas.md` (every new gotcha **MUST** be tagged with a version -- `(since vNEXT)` in a feature PR, replaced with the real `(since vX.Y.Z)` by the release PR)
- `plugins/kbagent/skills/kbagent/references/<topic>-workflow.md` (e.g. `semantic-layer-workflow.md`, `workspace-workflow.md`, `sync-workflow.md`)

Forgetting any of these does not fail tests or lint -- it ships an AI agent that quietly recommends commands that do not exist on the user's installed kbagent version, or refuses commands that do. Treat the change as **not done** until every applicable file has been updated.
Expand Down
93 changes: 70 additions & 23 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ before the PR is mergeable.
- [ ] **§3 Inline Gotchas** when behavior changed in a way the agent will get wrong by default (e.g. dev-branch auto-materialization, native column-type whitelisting).
- [ ] **`plugins/kbagent/skills/kbagent/SKILL.md`** non-table portions -- update the `description:` trigger keywords when introducing a new topic area (so description-matching auto-triggers the skill); add a workflow row to the bottom table if you created a new `references/<topic>-workflow.md`.
- [ ] **`plugins/kbagent/skills/kbagent/references/commands-reference.md`** -- add the new command bullet under the appropriate section. Hand-maintained, NOT auto-generated. (Yes, this partly duplicates the auto-generated SKILL.md table -- the reference carries denser per-command notes, the table is the at-a-glance picker.)
- [ ] **`plugins/kbagent/skills/kbagent/references/gotchas.md`** -- if the command's behavior is non-obvious, add an entry tagged with `(since vX.Y.Z)`. The version tag is **non-optional**; gotchas without versions are how AI agents end up recommending behavior that does not exist on older kbagent installs.
- [ ] **`plugins/kbagent/skills/kbagent/references/gotchas.md`** -- if the command's behavior is non-obvious, add an entry tagged with a version. In a feature PR that version is not known yet (the PR does not bump the version), so tag with the literal placeholder `(since vNEXT)` -- the release PR replaces every `vNEXT` with the version actually being released. Never guess a numeric version: `make version-gate-check` (per-PR CI) rejects any `(since vX.Y.Z)` whose version has no `changelog.py` entry. The version tag is **non-optional**; gotchas without versions are how AI agents end up recommending behavior that does not exist on older kbagent installs.
- [ ] **`plugins/kbagent/skills/kbagent/references/<topic>-workflow.md`** -- create a new file if the command introduces a new workflow or topic area (existing examples: `workspace-workflow.md`, `branch-workflow.md`, `sync-workflow.md`, `storage-files-workflow.md`, `storage-types-workflow.md`). Single-command additions go into an existing workflow file.
- [ ] **`plugins/kbagent/.claude-plugin/CLAUDE.md`** -- only update when the high-level delegation strategy changes (e.g. new "when NOT to delegate" cases). Most command additions do not touch this.
- [ ] **`plugins/kbagent/commands/keboola.md`** -- only update if the `/keboola` slash-command UX changes. Most command additions do not touch this.
Expand Down Expand Up @@ -454,8 +454,8 @@ release checklist below.

| File | When to update | CI catches drift? |
|------|----------------|-------------------|
| `pyproject.toml` (`version`) | Every release | -- (single source of truth) |
| `src/keboola_agent_cli/changelog.py` | Every release | YES (`make changelog-check`, both directions -- every release has an entry AND every entry has a release) |
| `pyproject.toml` (`version`) | Every release -- in the dedicated release PR ONLY, never a feature PR | -- (single source of truth) |
| `src/keboola_agent_cli/changelog.py` | Every release -- in the dedicated release PR ONLY, never a feature PR | YES (`make changelog-check`, both directions -- every release has an entry AND every entry has a release) |
| `src/keboola_agent_cli/commands/context.py` (`AGENT_CONTEXT`) | Adding/removing/renaming commands; significant flag changes | NO |
| `src/keboola_agent_cli/server/routers/<group>.py` | Adding/removing/renaming commands -- `kbagent serve` mirrors the CLI 1:1 for external consumers (Web UI, scheduled agents, third-party apps). Skip only for terminal-only / kbagent-infrastructure commands; document skip in PR | NO -- callers get HTTP 404 instead of "command works in CLI but not via API" silent gap |
| `CLAUDE.md` (`## All CLI Commands`) | Adding/removing/renaming commands | NO |
Expand All @@ -466,7 +466,7 @@ release checklist below.
| `plugins/kbagent/skills/kbagent/SKILL.md` -- table | Auto-generated by `make skill-gen` | YES (`make skill-check`; pre-commit auto-stages) |
| `plugins/kbagent/skills/kbagent/SKILL.md` -- description / rules / workflow links | New topic area in `description` triggers; new workflow file added to bottom table | NO |
| `plugins/kbagent/skills/kbagent/references/commands-reference.md` | Adding/removing/renaming commands; flag changes | NO |
| `plugins/kbagent/skills/kbagent/references/gotchas.md` | New non-obvious behavior -- always tag with `(since vX.Y.Z)` | PARTLY (`make version-gate-check` proves the tagged version EXISTS; whether it is the RIGHT one still needs review) |
| `plugins/kbagent/skills/kbagent/references/gotchas.md` | New non-obvious behavior -- always tag with a version (`(since vNEXT)` in feature PRs; the release PR rewrites it to `(since vX.Y.Z)`) | PARTLY (`make version-gate-check` proves a numeric tag names a released version; `vNEXT` leftovers are caught by the release checklist grep, not CI) |
| `plugins/kbagent/skills/kbagent/references/<topic>-workflow.md` | New workflow / topic area introduced | NO |
| `plugins/kbagent/skills/<sibling-skill>/` (e.g. `kbagent-promotion-pipeline`) | Adding a **sibling skill** -- a self-contained skill directory next to `kbagent/`, used when the topic ships executable `scripts/` + tests or needs its own `description` triggers rather than being one more `references/*.md`. Must ALSO be linked from `kbagent/SKILL.md`'s bottom table, otherwise an agent already inside the `kbagent` skill can never discover it | NO -- `make skill-check` only regenerates `kbagent/SKILL.md` and never looks at sibling skills |

Expand All @@ -479,6 +479,12 @@ to catch this before the change ships.

- **No `Co-Authored-By`** lines in commit messages
- **No AI attribution footers** in PR descriptions
- **No version bumps in feature PRs** -- `pyproject.toml`'s `version` and
`src/keboola_agent_cli/changelog.py` are touched ONLY by a dedicated release
PR (see [Releasing a new version](#releasing-a-new-version)). Parallel PRs
each bumping the version collide on merge and silently renumber releases.
Tag any new version-gated documentation with the `vNEXT` placeholder
(`(since vNEXT)`); the release PR replaces it with the real version.
- **Conventional commits**: `feat:`, `fix:`, `chore:`, `docs:`, `test:`, `refactor:`
- **One logical change per commit** -- don't mix unrelated fixes
- **Pre-commit hook must pass** -- `ruff check` + `ruff format --check`. Install via `make hooks`
Expand Down Expand Up @@ -573,10 +579,26 @@ ask you to address it before merge.

## Releasing a new version

A "release" is whenever you bump `pyproject.toml`'s version. Tag a feature
branch, walk this checklist end-to-end, then merge to `main`. The point of
steps 5-8 is that **CI will not catch you** if you skip them; they are the
manual safety net for the silent-drift risks summarized in the
A release is a **dedicated release PR** -- the only place `pyproject.toml`'s
`version` and `changelog.py` are ever touched. Feature PRs merge to `main`
without any version change; the release PR then batches **everything merged
since the last release** into one version bump, one changelog entry, and one
set of release notes.

> **Why not bump in each feature PR?** PRs are developed in parallel (often
> one AI session per issue). When each bumps the version, every merge is a
> `pyproject.toml`/`changelog.py` conflict, and resolving the conflicts by
> merging all of them renumbers releases after the fact. The
> `KNOWN_UNRELEASED` list in `scripts/generate_changelog.py` -- 18 versions
> whose content shipped silently, folded into a later release's wheel with no
> release notes -- is the accumulated damage of exactly that pattern.

Open the release PR from its own branch, walk this checklist end-to-end,
merge it, then tag the resulting `main` commit (the release pipeline renders
the GitHub release notes from the changelog entry via
`scripts/gen_release_notes.py`). The point of steps 7-10 is that **CI will not
catch you** if you skip them; they are the manual safety net for the
silent-drift risks summarized in the
[Plugin synchronization map](#plugin-synchronization-map) above.

> **Want to ship a beta first?** You can. PEP 440 pre-release versions
Expand All @@ -586,31 +608,56 @@ manual safety net for the silent-drift risks summarized in the
> [Releasing a beta (pre-release) version](#releasing-a-beta-pre-release-version)
> below for the workflow.

1. **Edit `pyproject.toml`** -- bump `version = "X.Y.Z"`. Single source of truth; everything else derives from it.
2. **Add a changelog entry** to `src/keboola_agent_cli/changelog.py` -- one entry per release, no exceptions. CI fails (`make changelog-check`) if this is missing. Author it as the file's docstring describes: **one logical change per bullet** (split the release into several list items rather than one mega-paragraph), each starting with a recognised prefix (`BREAKING:`, `New:`, `Fix:`, `Change:`, `Note:`, `Security:`, ...) and leading with a self-contained first sentence. `kbagent changelog` shows only that first sentence per version by default (the rest is revealed by `--full`), so a buried headline or a single wall-of-text bullet reads as an unscannable blob.
3. **Run `make version-sync`** -- propagates the new version to `plugins/kbagent/.claude-plugin/plugin.json`. The pre-commit hook does this automatically on `git commit`, but running it explicitly lets you eyeball the diff.
4. **Run `make skill-gen`** -- regenerates the decision table in `SKILL.md`. Idempotent if no commands changed since the previous release.
5. **Manually review `plugins/kbagent/agents/keboola-expert.md`**:
1. **Collect the raw material** -- find the last released tag
(`gh release list --limit 1` or `git describe --tags --abbrev=0`), then list
every PR merged since it:
```bash
git log v<last>..origin/main --oneline --first-parent
```
(or `gh pr list --state merged --base main --search "merged:><last-release-date>"`).
Those merged PRs are **exactly** the scope of the release: the changelog
entry and the release notes must cover each of them, and nothing else.
2. **Edit `pyproject.toml`** -- bump `version = "X.Y.Z"`. Single source of truth; everything else derives from it. This is the release PR's defining change -- if you are doing this in a feature PR, stop and read the section intro above.
3. **Add a changelog entry** to `src/keboola_agent_cli/changelog.py` -- ONE entry for the new version, covering **every PR merged since the last release** (step 1), no exceptions. CI fails (`make changelog-check`) if this is missing. Author it as the file's docstring describes: **one logical change per bullet** (split the release into several list items rather than one mega-paragraph), each starting with a recognised prefix (`BREAKING:`, `New:`, `Fix:`, `Change:`, `Note:`, `Security:`, ...), carrying its `(#PR)` reference, and leading with a self-contained first sentence. `kbagent changelog` shows only that first sentence per version by default (the rest is revealed by `--full`), so a buried headline or a single wall-of-text bullet reads as an unscannable blob.
4. **Replace every `vNEXT` placeholder** left behind by the feature PRs with the version being released, then verify none survive:
```bash
grep -rn "vNEXT" CLAUDE.md docs/ plugins/ src/keboola_agent_cli/commands/context.py
```
The grep MUST come back empty before merging. This is not CI-enforced (the
plugin is served from the repo, so a leftover `(since vNEXT)` ships to
agents as an unusable version gate) -- the release PR is the only place it
can be fixed.
5. **Run `make version-sync`** -- propagates the new version to `plugins/kbagent/.claude-plugin/plugin.json`. The pre-commit hook does this automatically on `git commit`, but running it explicitly lets you eyeball the diff.
6. **Run `make skill-gen`** -- regenerates the decision table in `SKILL.md`. Idempotent if no commands changed since the previous release.
7. **Manually review `plugins/kbagent/agents/keboola-expert.md`**:
- **§1 Rule 6 VERSION GATE examples** -- if any feature this release shipped (or any feature shipped in a previous release that you missed) was previously missing-and-now-present, document it with the right minimum version. Remove stale "since X.Y.Z" mentions that no longer matter to live users.
- **§2 Tool Selection Matrix** -- did you add a new write/destructive command *group* since last release? Is it present with one `First choice / Fallback / NEVER` row (per group, not per command)? Mind the hard 70 000 B prompt budget: trim stale content rather than raising the cap. New commands inside an existing group need no new row.
- **§3 Inline Gotchas** -- new behavior the agent would get wrong by default? Add it.
6. **Manually review `plugins/kbagent/skills/kbagent/references/gotchas.md`** -- every behavior introduced or changed this release that an AI agent would not infer from `--help` should have its own `(since vX.Y.Z)` entry. The version tag is non-optional.
7. **Manually review `CLAUDE.md` `## All CLI Commands`** -- diff against `kbagent --help` output (and against `kbagent context`). Hand-maintained; CI does not catch drift here.
8. **Manually review `plugins/kbagent/skills/kbagent/references/commands-reference.md`** -- same drill. Hand-maintained, no CI coverage.
9. **Run `make check`** -- lint + format + skill freshness + version sync + changelog completeness + error-code enum + full test suite.
10. **Run `make test-e2e`** if you changed any command -- requires `E2E_API_TOKEN` and `E2E_URL`.
11. **Open a PR** -- list every plugin file you touched in the description so reviewers can spot what was missed. Plugin files do not auto-show up in CI failures the way Python files do; reviewers are the second line of defence.
12. **Merge via `gh pr merge`** -- never push directly to `main` (the branch is protected; this would fail anyway).

If any of steps 5-8 reveal "I should have done this in the PR that introduced
8. **Manually review `plugins/kbagent/skills/kbagent/references/gotchas.md`** -- every behavior introduced or changed this release that an AI agent would not infer from `--help` should have its own `(since vX.Y.Z)` entry (freshly rewritten from `vNEXT` in step 4, or added now if a feature PR forgot one). The version tag is non-optional.
9. **Manually review `CLAUDE.md` `## All CLI Commands`** -- diff against `kbagent --help` output (and against `kbagent context`). Hand-maintained; CI does not catch drift here.
10. **Manually review `plugins/kbagent/skills/kbagent/references/commands-reference.md`** -- same drill. Hand-maintained, no CI coverage.
11. **Run `make check`** -- lint + format + skill freshness + version sync + changelog completeness + error-code enum + full test suite.
12. **Run `make test-e2e`** if any command changed since the last release -- requires `E2E_API_TOKEN` and `E2E_URL`.
13. **Open the release PR** -- link the merged PRs it covers (step 1) and list every plugin file you touched in the description so reviewers can spot what was missed. Plugin files do not auto-show up in CI failures the way Python files do; reviewers are the second line of defence.
14. **Merge via `gh pr merge`**, then tag the resulting `main` commit and publish the release -- never push directly to `main` (the branch is protected; this would fail anyway).

If any of steps 7-10 reveal "I should have done this in the PR that introduced
the command, not at release time", **also patch the per-command checklist**
above so the next contributor catches the gap earlier.

### Releasing a beta (pre-release) version

Beta and release-candidate versions follow PEP 440: `X.Y.Zb1`, `X.Y.Zb2`,
`X.Y.Zrc1`, ... -- not the SemVer `-beta.1` form (hatchling and uv require
PEP 440 syntax in `pyproject.toml`'s `version` field). Two gates keep stable
PEP 440 syntax in `pyproject.toml`'s `version` field).

A beta is the **one exception** to "version bumps only in the release PR":
the pre-release tag and GitHub Release are cut from the feature branch itself,
so the bump deliberately rides that branch -- for the duration of the beta,
the feature branch *is* the release PR. `main` stays on the stable channel
until the stable release PR ships the final version.

Two gates keep stable
users safe from accidentally landing on a beta:

1. **Version string itself.** PEP 440 marks any pre-release suffix as such;
Expand Down
Loading