Skip to content
Draft
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
},
"metadata": {
"description": "Deploy and manage apps on Deploio (nine.ch PaaS) — skills for deployment, management, debugging, backing services, and CI/CD.",
"version": "1.1.0"
"version": "1.5.0"
},
"plugins": [
{
"name": "deploio",
"source": "./",
"description": "Five skills for deploying and managing Deploio apps: first-time deploy, day-to-day management, debugging, backing service provisioning, and CI/CD pipeline setup.",
"version": "1.1.0",
"version": "1.5.0",
"license": "MIT",
"author": {
"name": "renuo.ch"
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "deploio",
"version": "1.1.1",
"version": "1.5.0",
"description": "Deploy and manage apps on Deploio (nine.ch PaaS) using nctl. Five skills covering first-time deployment, day-to-day management, debugging, backing service provisioning (PostgreSQL, Redis, S3), and CI/CD setup.",
"license": "MIT",
"author": {
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Version

---

## [1.5.0] — 2026-04-29

### Added
- **`auth_stale` blocker** — gather-context distinguishes a stale JWT (expired/corrupt session token) from never-authenticated and emits a verbatim friendly user-facing message telling them to run `nctl auth login`. New `nctl auth errors` section in `shared/troubleshooting.md`.
- **`SubagentStart` nctl version probe** — when the deploio-cli agent is spawned (i.e. just before any nctl call), a hook checks that nctl is installed and meets the plugin's required version (1.16.0). If missing or outdated, the user sees an install/upgrade advisory before any work begins.
- **`RELEASING.md` checklist** — documents the four files that must move together on release (plugin.json, marketplace.json with its two version fields, changed SKILL.md metadata blocks, CHANGELOG.md), with a verification shell block to catch drift before commit.

### Changed
- **Project scoping** — every nctl call from executor and monitor agents now carries `--project=<full-project>`. The agent no longer runs `nctl auth set-project`, which mutated the user's global kubeconfig and silently broke other concurrent shells using the same nctl config. The executor spec drops the legacy `project_suffix` field and the standalone `org` field — `--project=<full-project>` is authoritative.
- **Destructive-command guard now blocks every `nctl auth` subcommand except `whoami`.** The skills prompt the user to run `nctl auth login` / `nctl auth set-org` themselves, with an upfront warning that those commands disrupt any other concurrent session sharing the same nctl config. Both install paths use the same shell-based hard block (the prompt-based variant `hooks/guard-destructive.md` was removed).
- **Hook wiring converged on `hooks/hooks.json` as the single source of truth.** Both hooks (destructive-guard, nctl-probe) declared in one place. Marketplace installs read it directly; flat installs merge the substituted block into `settings.json` via jq, preserving any user-owned hooks. Agent frontmatter no longer carries an inline `hooks:` block.
- **Hook path resolution** uses `${CLAUDE_PLUGIN_ROOT}/hooks/...` in source. Marketplace installs resolve the env var; flat installs substitute the absolute path so global installs no longer lose the guard outside the source repo.
- **`nctl --version`** (works on old and new nctl) replaces `nctl version` (fails on releases below 1.12) in gather-context.

### Fixed
- `install.sh` exit code on local source — the cleanup trap returned 1 under `set -e` when `LOCAL_SRC` was set, breaking CI and scripted installs even on success.
- `install.sh` jq merge is genuinely idempotent and surfaces failures: temp files cleaned up via trap, jq errors abort the install rather than silently leaving the file unchanged with a misleading success message.

---

## [1.1.0] — 2026-03-23

### Added
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,4 @@ Nine docs: [docs.nine.ch](https://docs.nine.ch)

## Versioning

Bump `"version"` in `.claude-plugin/plugin.json` and push to `main` the GitHub Actions workflow tags and releases automatically.
See `RELEASING.md` at the repo root for the full checklist. Four files move together: `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json` (two places — `metadata.version` and `plugins[0].version`), each changed skill's `SKILL.md` `metadata.version`, and a new `CHANGELOG.md` entry. Pushing to `main` after the bump tags + releases automatically.
4 changes: 1 addition & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ Cross-skill troubleshooting patterns go in `skills/shared/troubleshooting.md`.

## Releasing a new version

Bump `"version"` in `.claude-plugin/plugin.json`, commit, and push to `main`. The GitHub Actions workflow creates the git tag and GitHub release automatically.

Version format: `MAJOR.MINOR.PATCH` following [semver](https://semver.org).
See [RELEASING.md](RELEASING.md) for the full checklist. Short version: four files (`plugin.json`, `marketplace.json`, the changed `SKILL.md`s, and `CHANGELOG.md`) move together; pushing to `main` triggers the autorelease.

## Reporting issues

Expand Down
92 changes: 92 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Releasing

Cutting a release means moving four files in lockstep, then pushing to `main`. The GitHub Actions workflow at `.github/workflows/` watches `.claude-plugin/plugin.json` and tags + releases automatically once it lands on `main` — but only the *plugin* version. Everything else (marketplace listing, changelog, per-skill metadata) is your responsibility.

This document is the checklist. Run through it top-to-bottom every time.

## When to release

- Bug fix, security patch, doc-only change → **PATCH** (`1.2.0` → `1.2.1`)
- New skill, new hook, new command, new feature, additive behavior change → **MINOR** (`1.2.1` → `1.3.0`)
- Removed / renamed skill, breaking change to an executor agent's task spec, hook event surface change that breaks downstream automation → **MAJOR** (`1.x.y` → `2.0.0`)

The agent ↔ skill executor spec (e.g. `task: deploy` fields) is an internal contract — bump MINOR when its shape changes, since the plugin still works end-to-end. Reserve MAJOR for things a *user* would feel.

## All versions move together

The plugin, marketplace metadata, marketplace plugin entry, and every skill share the **same version number**. On every release, bump all of them — even skills whose contents didn't change in this cycle.

This is a deliberate policy. Per-skill versions used to be decoupled from the plugin (each bumped only when that skill's contents changed), but the seven version numbers across the repo could end up several minor releases apart, making it impossible to tell at a glance which release a user was on without reading every metadata block. Aligned versioning trades skill-level history fidelity for one obvious answer to "which release is this".

If the plugin is ever split into separately-installable units, decoupling will need to come back. Until then: one number, everywhere.

## The four files

| File | What to update |
|---|---|
| `.claude-plugin/plugin.json` | `"version"` (one occurrence) |
| `.claude-plugin/marketplace.json` | `"version"` in **two** places: `metadata.version` and `plugins[0].version` — both must match |
| `skills/<name>/SKILL.md` | `metadata.version` in **every skill** — all five move together with the plugin |
| `CHANGELOG.md` | New entry at the top (under the header), dated today, with `### Added` / `### Changed` / `### Fixed` sections matching what's in the diff |

## Step-by-step

1. **Decide the version.** Read the diff since the last tag (`git log $(git describe --tags --abbrev=0)..HEAD --oneline`) and pick PATCH / MINOR / MAJOR per the table above. Pick the next number from the current `plugin.json` value, not from `marketplace.json` (the source of truth is `plugin.json` since the autorelease watches it).

2. **Edit the four files** in the order they're listed above. For each, save and check the change is the *only* version-string change in that file (`git diff <file>`).

3. **Run the verification block** (below). It checks the three plugin-level versions agree and that CHANGELOG names the new version. Fix anything it reports before continuing.

4. **Run the project's existing tests / installs** if the changes touched anything user-facing. At minimum:
- `bash -n install.sh && bash -n uninstall.sh` (syntax check the install scripts)
- A round-trip flat install into a scratch dir followed by uninstall, to confirm the install scripts still work
- For hook changes, a manual smoke test of each hook script (`echo '{"tool_input":{...}}' | bash hooks/<name>.sh`)

5. **Commit** with subject `chore(release): <version>` and a body that mirrors the CHANGELOG entry (or just summarizes it).

6. **Push to `main`.** The GitHub Actions workflow tags and creates the GitHub release. Watch the run pass before considering the release shipped.

## Verification block

Run this from the repo root before committing — it surfaces drift between the four files:

```bash
PV=$(jq -r '.version' .claude-plugin/plugin.json)
MM=$(jq -r '.metadata.version' .claude-plugin/marketplace.json)
MP=$(jq -r '.plugins[0].version' .claude-plugin/marketplace.json)
CL=$(grep -m1 -oE '\[[0-9]+\.[0-9]+\.[0-9]+\]' CHANGELOG.md | tr -d '[]')

printf "plugin.json: %s\n" "$PV"
printf "marketplace.json metadata: %s\n" "$MM"
printf "marketplace.json plugins: %s\n" "$MP"
printf "CHANGELOG.md (top entry): %s\n" "$CL"

OK=true
[ "$PV" = "$MM" ] && [ "$PV" = "$MP" ] && [ "$PV" = "$CL" ] || OK=false

echo
echo "Skill versions:"
for f in skills/*/SKILL.md; do
v=$(awk '/^metadata:/{m=1; next} m && /^ version:/{print $2; exit}' "$f")
marker=""; [ "$v" = "$PV" ] || { marker=" (mismatch)"; OK=false; }
printf " %-32s %s%s\n" "$(basename "$(dirname "$f")")" "$v" "$marker"
done

echo
if $OK; then
echo "OK — all versions agree on $PV"
else
echo "DRIFT — fix before committing"
exit 1
fi
```

## After push

- Check the GitHub Actions run on the merge commit. If it fails, the tag isn't created — fix forward.
- Confirm the new tag exists: `git fetch --tags && git tag -l '<version>'`.
- The marketplace registry picks up the new `marketplace.json` on its next sync. There's no separate publish step.

## If you need to roll back

Don't delete the tag — push a follow-up release with the fix. Tags are immutable contracts; deleting one breaks anyone who already installed at that version.
Loading