Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
9940d3a
test(sync): add CLI behavior tests for project-selection guards and d…
Matovidlo Jun 9, 2026
9cdcdeb
feat(skill): add kbc->kbagent CI/CD migration skill
Matovidlo Jun 9, 2026
cc44bea
fix(skill): live-verify kbc->kbagent migration mechanics, fix push flag
Matovidlo Aug 6, 2026
bac7f2c
refactor(skill): trim SKILL.md bloat, clarify CI vs local auth
Matovidlo Aug 6, 2026
60fec32
fix(skill): address Copilot findings + preserve per-project directory…
Matovidlo Aug 6, 2026
822469e
fix(skill): fix stale line-number refs + generator edge cases from Co…
Matovidlo Aug 6, 2026
21cb254
fix(skill): install keboola-cli, not the legacy keboola-agent-cli name
Matovidlo Aug 6, 2026
7447170
fix(skill): use full relative path for project alias, not just the le…
Matovidlo Aug 6, 2026
5a7a973
fix(skill): drop stale 0.58.0 version examples, tighten push/diff usa…
Matovidlo Aug 6, 2026
be5c8c6
fix(skill): drop unused main_branch params, harden test isolation
Matovidlo Aug 6, 2026
8ea8946
fix(skill): add required --project to command-mapping.md's pull/push/…
Matovidlo Aug 6, 2026
60df51d
feat(skill): recommend a PAT over a raw Storage token for CI secrets
Matovidlo Aug 7, 2026
d6a33b0
Revert "feat(skill): recommend a PAT over a raw Storage token for CI …
Matovidlo Aug 10, 2026
68e81a5
refactor(skill): tidy migrate_cicd.py per review (dead wrapper, test …
Matovidlo Aug 10, 2026
224597d
fix(skill): sanitize migrate_cicd.py directory names, close shell-inj…
Matovidlo Aug 11, 2026
593071f
fix(skill): close remaining doc drift from padak's delta review
Matovidlo Aug 11, 2026
2efad79
fix(skill): trim SKILL.md description back under Claude Desktop's 102…
Matovidlo Aug 11, 2026
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
290 changes: 290 additions & 0 deletions plugins/kbagent/skills/kbagent-cicd-migration/SKILL.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Choosing a branching model

kbagent supports two ways to map your git repo to Keboola branches. Pick one up
front — it changes how `sync init` is run and what `push` touches.

## The two models

### Model A — Single-branch / production-direct
- Each project directory maps to **one Keboola project's production branch**.
- `.keboola/branch-mapping.json` stays at its default (`null` = production).
- `sync pull` / `sync push` read and write that project's production branch directly.
- Promotion across environments (dev → prod project) is a **git merge** between
project directories/repos, then a `push` to the next project (the demo's L0→L1
shape).

**Choose A when:**
- You're experimenting, or driving a single dev project locally instead of kbc.
- Your "environments" are *separate Keboola projects* (L0/L1), not dev branches.
- You want the simplest mental model and fewest moving parts.

**Trade-off:** a `push` writes straight to the production branch of that project —
there is no isolated staging copy inside Keboola. Review happens in git, not in KBC.

### Model B — Git-branching (Keboola dev-branch isolation)
- `sync init --git-branching` creates `.keboola/branch-mapping.json`.
- Each **git branch** links to a **Keboola development branch** (an isolated server-
side copy) via `kbagent sync branch-link --branch-name <git-branch>`.
- Work on a PR branch → `push` lands in its Keboola dev branch (safe, isolated);
merge to `main` → `push` lands in production.
- `kbagent sync branch-status` shows the mapping; `branch-unlink` detaches.

**Choose B when:**
- Multiple people open PRs against the same project and you want each change tested
in isolation inside Keboola before it hits production.
- You already use Keboola's development-branches feature.
- You want PRs to never write production directly.

**Trade-off:** more lifecycle to manage (create/link/unlink dev branches, clean them
up), and the mapping file is per-clone state.

## Decision shortcut

| Your situation | Model |
|---|---|
| "I just want to manipulate one project with kbagent instead of kbc" | **A** (start here) |
| Separate dev/prod **projects** promoted by git merge (L0/L1) | **A** |
| PR-per-change, multiple contributors, want isolated server-side testing | **B** |
| You rely on Keboola development branches today | **B** |

You can start on **A** and adopt **B** later: run `sync init --git-branching` and
`branch-link` when you actually need per-PR isolation. Moving A→B is additive (it adds
a mapping file); it does not require re-converting the config tree.

## How the model shows up in commands

```bash
# Model A (production-direct) — nothing special:
kbagent sync pull --project <alias> -d <dir>
kbagent sync push --project <alias> -d <dir>

# Model B (git-branching):
kbagent sync init --git-branching --project <alias> -d <dir>
git checkout -b feature/x
kbagent sync branch-link --project <alias> -d <dir> --branch-name feature/x
kbagent sync pull/push --project <alias> -d <dir> # now targets the dev branch
kbagent sync branch-status --project <alias> -d <dir>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# kbc ↔ kbagent command / flag / env mapping

Authoritative mapping used by the migration generator. Verify flags against your
installed `kbagent` version (`kbagent sync pull --help`); the new CLI evolves fast.

> **Verified against:** kbagent v0.80.0, live-verified 2026-08-06 (see
> `SKILL.md`'s "Verified against" note). Dated claims below are point-in-time
> repro notes, not version floors.

## Install

| kbc (old) | kbagent (new) |
|---|---|
| Download Go binary zip from `keboola/keboola-as-code` GitHub release, unzip to `/usr/local/bin/kbc` | `uv tool install keboola-cli==<ver>` (PyPI) or `uv tool install 'git+https://github.com/keboola/cli@<tag>'` |
| `kbc --version` | `kbagent version` |
| Custom `install` composite action | `astral-sh/setup-uv@v7` + one `uv tool install` line |

## Core sync commands

| kbc (old) | kbagent (new) | Notes |
|---|---|---|
| `kbc init -d DIR --allow-target-env` | `rm DIR/.keboola/manifest.json && kbagent sync init --project <alias> --directory DIR` | `--project` is required. For the one-time kbc→kbagent conversion use **plain `init`, not `--adopt-existing`** — adopting a kbc-written manifest inherits kbc's row/companion-config paths verbatim and leaves a permanently dirty `sync status` (root-caused; see SKILL.md and `migration-runbook.md`). kbc and kbagent both write to the same path (`.keboola/manifest.json`), so plain `init` errors "Manifest already exists" until you delete that one file (not the `config.json`/`meta.json` tree next to it) — confirmed live, 2026-08-06. `--adopt-existing` is still correct for re-registering an *already-converted* kbagent-native manifest in ephemeral CI (no kbc data involved at that point). |
| `kbc persist -d DIR` | *(folded into `sync pull`)* | No separate persist step; pull writes manifest + new objects |
| `kbc pull -d DIR --force` | `kbagent sync pull --project <alias> --directory DIR --force` | `--project` (or `--all-projects`) is required; `--force` overrides local-vs-remote conflicts (3-way diff) |
| `kbc push -d DIR` | `kbagent sync push --project <alias> --directory DIR` | Encrypts `#`-secrets fail-closed before write |
| `kbc push -d DIR --force` | `kbagent sync push --project <alias> --directory DIR --force` | Push's `--force` removes remote configs deleted locally (there is no `--allow-delete` flag — same flag name as pull's `--force`, but a different meaning per command) |
| `kbc push --dry-run` / push-dry action | `kbagent sync push --project <alias> --dry-run --directory DIR` | Shows planned changes without writing |
| `kbc diff -d DIR` | `kbagent [--json] sync diff --project <alias> --directory DIR` | `--json` is a **global** option (before `sync`, not after `diff`); gives structured drift for CI gating |
| `kbc status` | `kbagent sync status --directory DIR` | `sync status` reads the local manifest only, no `--project` needed |
| `kbc validate` (JSON-schema) | *(no direct equivalent — gap)* | Use `sync diff` for drift; schema validation is not ported |

## Auth / environment variables

| kbc (old) | kbagent (new) | Notes |
|---|---|---|
| `KBC_STORAGE_API_TOKEN` | `KBC_TOKEN` | Storage API token |
| `KBC_STORAGE_API_HOST` (bare host) | `KBC_STORAGE_API_URL` (full URL) | `connection.keboola.com` → `https://connection.keboola.com` |
| *(implicit)* | `KBAGENT_PROJECT_FROM_ENV=1` | **Required** opt-in so kbagent synthesizes an ephemeral project from the env in CI (no `config.json` on disk). See `constants.py`'s `ENV_PROJECT_FROM_ENV` and `ConfigStore._inject_env_project` |
| `KBC_PROJECT_ID`, `KBC_BRANCH_ID`, `KBC_BRANCHES` | *(from manifest + branch-mapping)* | Project id comes from `.keboola/manifest.json`; branch from `.keboola/branch-mapping.json` |

## Branching

| kbc (old) | kbagent (new) |
|---|---|
| Fixed `KBC_BRANCH_ID` per env; `allowedBranches` in manifest | `.keboola/branch-mapping.json` (git branch → Keboola branch id; `null` = production) managed by `kbagent sync branch-link / branch-unlink / branch-status` |
| Branch dir under repo (`main/`) | Same on-disk layout; mapping decides which Keboola branch a git branch targets |

## Subset of a project

Both CLIs honor manifest-level scoping — no command change needed:

- `allowedBranches: ["<id>"]` — restrict which branches sync.
- `ignoredComponents: ["keboola.foo", ...]` — exclude component types.

`kbagent` parses both (`sync/manifest.py:120`). Additionally, `sync pull` flags
`--no-storage` / `--no-jobs` / `--with-samples` control how much *metadata*
(beyond configs) is pulled — orthogonal to the config subset.

## What has NO clean port (call out to the user)
- `kbc validate` JSON-schema validation.
- `kbc ci workflows` generator itself (this skill replaces it).
- Templates / dbt / CI-scaffold subsystems (`kbc template`, `kbc dbt`) — keep `kbc`
for those; they are out of scope for sync CI/CD.
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# Migration runbook — kbc → kbagent (PR sequence)

> **Verified against:** kbagent v0.80.0, live-verified 2026-08-06 (see
> `SKILL.md`'s "Verified against" note). Dated claims below are point-in-time
> repro notes, not version floors.

The ordered, low-risk way to cut a repo over. This is a **clean cutover**, not a
coexistence: kbc (`config.json`/`meta.json`) and kbagent (`_config.yml`) cannot both
own the same tree (live-verified — see the SKILL.md reality note). Plan it as one
conversion PR plus housekeeping, then everyone re-branches from the new `main`.

## Answer to "can I transition seamlessly to a new branch?"
No co-existence, but yes a controlled cutover:
1. One **conversion PR** flips the whole repo from JSON→YAML + swaps the workflows.
2. Merge it to `main`.
3. **Delete/redo every old branch** — they carry the incompatible kbc layout and can
never cleanly merge into the converted `main`.
4. Everyone branches fresh from the new `main` with the new workflows.

## Pre-flight (do once, before any PR)
- [ ] **Announce a change freeze** on the repo + the Keboola projects for the
conversion window. Any config edit made in the UI between "pull" and "cutover"
becomes drift you'll chase. Keep it short.
- [ ] **Pick a kbagent version** and pin it (`keboola-cli==X.Y.Z` or
`git+...@vX.Y.Z`). Never unpinned on a prod lane.
- [ ] **Set GitHub secrets**: one `KBC_TOKEN_<ALIAS>` per project (see
`references/secrets-setup.md`).
- [ ] **Create GitHub Environments** `dev` + `prod`; add required reviewers to `prod`.
- [ ] **Inventory** with the skill's analyzer (dry-run): confirm every project and the
legacy files it will replace.
`python <skill>/scripts/migrate_cicd.py /path/to/repo`

## PR 1 — Conversion (the big one) → branch `migrate/kbc-to-kbagent`
Do the projects one at a time; **start with a non-prod project (e.g. `dev`/`L0`)**.

**Use plain `sync init` — never `--adopt-existing`.** Root-caused on project 153
(see "Verified against" above): `--adopt-existing` carries kbc's row paths
(`values/...` for `keboola.variables`, `codes/...` for `keboola.shared-code`) and
kbc's `relations`-based companion-config paths straight into the kbagent manifest
without translating them. That permanently leaves phantom `added`/`deleted`
entries in `sync status` (kbagent's own untracked-row scanner only recognizes a
literal `rows/` path segment; kbc's inherited `values/`/`codes/` rows never match
it), and a `sync push` against those phantom "added" rows would call
`create_config` and create duplicate sibling configs, not update the rows they
actually are. Plain `sync init` has none of this: it starts a brand-new empty
manifest and ignores every file it doesn't recognize, so pointing it at a
directory still full of kbc's `config.json`/`meta.json` tree is safe — the
following `sync pull` populates everything fresh through kbagent's own
naming/path logic, with no inherited kbc paths at all. Verified: a plain
`init`+`pull` against the same project reached `sync status` = `0 added, 0
modified, 0 deleted` — genuinely clean, not "a small acceptable residual."

**One required prep step: delete kbc's manifest file first.** kbc and kbagent
write to the identical path, `<DIR>/.keboola/manifest.json`, and plain `sync init`
refuses to run while that file exists (`Error: Manifest already exists at
.../.keboola/manifest.json. Use 'sync pull' to update, 'sync init
--adopt-existing' ..., or delete .keboola/ to reinitialize.`) — reproduced (see
"Verified against" above). This is the ONE file you delete before `init`, not the
`config.json`/`meta.json` config tree — those stay in place and get cleaned up
only after the pull below.

Per project `<DIR>` (with its token in the env):
```bash
export KBAGENT_PROJECT_FROM_ENV=1 KBC_TOKEN=$TOKEN \
KBC_STORAGE_API_URL=https://<stack>
rm <DIR>/.keboola/manifest.json # kbc's manifest -- same path kbagent needs
kbagent sync init --project __env__ --directory <DIR>
kbagent sync pull --project __env__ --directory <DIR> # writes _config.yml
# Drop the orphaned kbc files kbagent does not read:
find <DIR> \( -name config.json -o -name meta.json \) -exec git rm -q {} +
# VERIFY BY BEHAVIOR (must be genuinely empty before you trust the project):
kbagent sync status --directory <DIR>
kbagent sync diff --project __env__ --directory <DIR>
```
Acceptance for each project: `sync status` shows `0 added, 0 modified, 0 deleted`
**and** `sync diff` shows `0 to create, 0 to update, 0 to delete`. Do not accept
"a handful of leftover entries" as normal and push through it — with plain init
there should be none; if there are, stop and diagnose before moving to the next
project or enabling push.

**Also clean up kbc-only type folders — they are left behind whole, not just
emptied of `config.json`/`meta.json`.** kbc's naming has a finer-grained
component-type taxonomy than kbagent's: kbc buckets configs into `extractor/`,
`writer/`, `transformation/`, `application/`, **`processor/`**, **`app/`**
(data apps), `_shared/` (shared code), `variables/`, `schedules/`. kbagent only
recognizes `extractor` / `writer` / `transformation` / `application` and folds
**everything else — processors, data apps, shared code — into a flat `other/`**
(`COMPONENT_TYPE_MAP` in `sync/config_format.py`; kbagent never applies kbc's
dedicated `dataAppConfig` naming template even though the manifest model still
carries the field for read compatibility). After `sync pull` rewrites those
configs under `other/<component_id>/...`, the old `app/`, `processor/`, and
`_shared/` directories are orphaned — but **they are not empty**: besides
`config.json`/`meta.json` (already removed above), kbc also writes
`description.md` and the code body itself (`code.sql`/`code.py`/`code.txt`/
`code.txt` under `_shared/.../codes/...`) into these folders, none of which
kbagent reads either. A `find -empty -delete` is a no-op against them — reproduced
(19 leftover files, zero dirs matched `-empty`). Remove the
whole subtree instead, in the same commit as the `config.json`/`meta.json`
cleanup:
```bash
git rm -rq --ignore-unmatch <DIR>/*/app <DIR>/*/processor <DIR>/*/_shared
```
(adjust the glob to your branch layout — kbc nests these under the branch
directory, e.g. `main/app`, `main/processor`, `main/_shared`). Re-run `sync diff`
after — it should be unaffected (these folders were never manifest-tracked;
deleting them doesn't touch any config kbagent knows about); verified live: diff
stayed "No differences found" before and after removing all 19 leftover files.

Then, still on the same branch:
```bash
# Generate the clean kbagent-native workflows:
python <skill>/scripts/migrate_cicd.py /path/to/repo --write --version X.Y.Z
# Remove the legacy kbc CI (the analyzer listed these):
git rm -r .github/actions/kbc_* .github/workflows/KBC_*.yml # adjust to your repo
git add -A && git commit -m "Migrate kbc -> kbagent: convert configs + workflows"
```

**Review this PR by behavior, not by diff.** The reformat touches hundreds of files;
reading it line-by-line is pointless. Trust:
- the `kbagent-validate` workflow runs on the PR and `sync diff` is clean per project;
- a `sync push --dry-run` (also in validate) reports no changes.

Merge to `main` once validate is green.

## PR 2 — Housekeeping (optional, after merge)
- [ ] Branch protection on `main`; require the `kbagent-validate` check.
- [ ] Tune the pull schedule cron / push approval reviewers.
- [ ] Update the repo README to the new install + commands.
- [ ] Decide the branching model (next section).

## After merge — start over
- [ ] **Close or recreate every open PR** that was based on the kbc layout. They diff
against JSON files that no longer exist; rebasing them is not worth it — redo the
change on a fresh branch from the converted `main`.
- [ ] **Delete stale feature branches** (`git push origin --delete <branch>`).
- [ ] Tell contributors to **re-clone or hard-reset** to the new `main`.
- [ ] First real push: run `kbagent push` (workflow_dispatch) to `dev` first, approve,
verify in the Keboola UI, then to `prod`.

**This whole sequence is an ordinary commit + ordinary PR merge — never a git
history rewrite.** The conversion diff is huge and it's tempting to reach for
`git filter-repo`, an orphan-branch reset, or a force-pushed squash of `main` to
make history "clean." Do not suggest this to a customer: it invalidates every
collaborator's clone and open PR, destroys `git blame`/audit trail across the
*entire* repo (a compliance concern, not just an inconvenience, for regulated
customers), and a coordinated force-push to `main` is exactly what most orgs'
branch-protection rules exist to block. "Close/redo open PRs, delete stale
branches, re-clone" above is already disruptive enough as ordinary git hygiene —
that is the actual answer to "the diff is too big to review," not a rewritten
history.

## Branching model — pick one
See [references/branching-model.md](branching-model.md) for the full decision table
(single-branch vs. git-branching). Migrate to git-branching in PR 2, not PR 1, if
you choose it — it's additive and doesn't require re-converting the config tree.

## Hard guardrails (repeat to the user)
- **Never** `kbagent sync push` (without `--dry-run`) against an adopted-but-not-yet-
pulled kbc tree. In the original 2026-06 live test this reported every config as
"to delete" (136) and `--force` would have wiped the project; re-verified since
(see "Verified against" above, same project) this no longer happens — `sync diff`
now reports untouched configs as `never_fetched`, not `deleted`. Treat the old figure as
a historical regression, not current behavior, but keep the rule: always
`sync pull` and confirm a clean `sync diff` before the first real push, on any
kbagent version.
- **Never** `--allow-plaintext-on-encrypt-failure` in CI.
- **Never `--all-projects` in a migration repo directory.** It's hard-coded to
`<directory>/<alias>/` for every registered project alias and (for pull)
auto-inits a fresh tree there if none exists — confirmed to silently create a
second, unrelated `<alias>/` directory alongside an existing flat manifest.
Always `--project ALIAS --directory DIR` explicit, matching what the generated
CI already does.
- Keep the change freeze until `main` is converted and the first dev push is verified.
Loading