diff --git a/skills/commander/SKILL.md b/skills/commander/SKILL.md index 6a797c4..aac1d12 100644 --- a/skills/commander/SKILL.md +++ b/skills/commander/SKILL.md @@ -53,6 +53,22 @@ if it is money, or a duplicate message, or a half-applied change, the action mus starting and refuse a second attempt. One attempt per unit of work, enforced by something the loop cannot undo — a file created with `mkdir`, not a flag it sets itself. +**What does "new" mean — and this one you must supply, not ask for.** "Watch for a new tag" is not a +condition. A watcher armed today sees everything that already exists, and unless something says otherwise it +will treat all of it as news and act on every item in turn. Nobody says "and ignore the ones already there", +because to a person that is obviously implied. It is not implied to a shell loop. + +So a watcher needs a **baseline**, and there are only two honest kinds: + +- **A record of what has been handled.** Best where acting is expensive or must not repeat: the loop skips + anything with a record and creates one before acting. This also survives a restart, which a remembered + baseline does not. +- **A snapshot taken at arming.** Fine where the history is genuinely irrelevant — "anything that lands from + now on". Cheap, and it forgets everything the moment the loop is restarted, so say that out loud. + +Choose one and say which, in the condition itself. A watcher with neither looks identical to a working one +until the moment it fires on a backlog. + **What does "done" look like to you?** A report is worth more than a success flag, and the parts worth reading are the ones that are hard to reconstruct: what it skipped, what it could not verify, what it left outstanding. Ask for those explicitly or you will get a summary of what went right. diff --git a/skills/deploy/SKILL.md b/skills/deploy/SKILL.md new file mode 100644 index 0000000..9d36756 --- /dev/null +++ b/skills/deploy/SKILL.md @@ -0,0 +1,77 @@ +--- +name: Deploying Officer +description: >- + Use when asked to deploy, release or roll out Officer, to watch for a release and deploy it, or when a + deploy has gone wrong — a host behind, a tag not noticed, a rollout that stopped halfway. Points at the + procedure, and carries the things about THIS fleet that are not in it. +version: 1 +--- + +# The procedure is a file, and it is not here + +Read it: + +``` +~/officer-dev/platform/prompts/deploy-commander.md +``` + +**Not copied into this skill, deliberately.** It names `scripts/deploy-claim.sh`, `docs/servers.md` and the +plan format, all of which live in that repository and change with the code. A second copy here would be +right on the day it was written and wrong on the day somebody edits the first one — and the copy an agent +happens to read would be the stale one. One hop costs a `Read`; a wrong procedure costs a fleet. + +That file carries the whole rollout: what wakes you, what a release is, the per-host order, what to refuse, +and why each rule exists. Everything below is what is true about this fleet and would not belong in it. + +# What a release is here + +A **tag**, `deploy/`, version `yyyy.mm.dd.NN`. Nothing else triggers a deploy — not a commit +message, not a branch, not a push. A tag cannot be typed into prose by accident, which a code word can, and +`git ls-remote --tags` answers "is there one" in a single call without fetching. + +`package.json` must agree with the tag, and `deploy/.md` — the plan — must exist. If either is +untrue, **stop and say so**. A release with no plan is not one you can guess at. + +# One attempt per release, and it is not yours to enforce + +```bash +scripts/deploy-claim.sh claim # exit 0 = yours, exit 1 = do not proceed +scripts/deploy-claim.sh done # after it succeeded +``` + +Exit 1 means somebody already attempted this release, and that answer never changes back. **A failed release +is not retried, it is looked at.** Clearing a claim is a human act. + +This is a script rather than a rule you follow because the watcher loop is rewritten from scratch every time +one arms, and the wrong implementation is the one that looks right: test the `.done` file, which exists only +on SUCCESS. Write that and a failing release spawns a fresh agent every poll, all night, each one costing +money. That is not hypothetical. + +# Fleet facts that cost time to learn + +- **Poll a LOCAL remote.** `origin` on `officer-dev` points at the local gitea. The public hostname resolves + through a reverse proxy where every connection shares one source IP, and rate limiting there refuses SSH + for long stretches — presenting as `Connection closed by ` and "make sure you have the correct access + rights", which is not an auth problem. Cost: a tag pushed at 21:47 was noticed at 23:07. With the local + remote, twenty seconds. +- **Never hard-code a checkout path.** Take it from PM2 — the `officer` process's `pm_cwd`. One host carries a + second, stale checkout, and pulling that one succeeds while changing nothing. +- **The login user is not always the same.** It is per host, in `docs/servers.md`. A wrong username presents + as `Permission denied (publickey)`, which reads as a broken key. +- **`bun run plugins:update` restarts every plugin's sidecar itself.** A report listing only `officer` under + "restarted" understates what moved by an order of magnitude. +- **Skip the host you are running on, entirely.** Restarting `officer` there ends the rollout mid-report. + Name it as skipped, and say whether it is behind or already current — those are different, and only one of + them asks the owner for anything. +- **A dirty tree means TRACKED changes.** `git status --porcelain` counts untracked files; a stray script a + fast-forward could never touch is not a reason to skip a host. Use `--untracked-files=no`. + +# The report is an artefact, the lock is the record + +Write the local record FIRST, then commit and push the report. If the push fails, say so and stop: the +deploy succeeded and its paperwork is pending. Retrying a push into a loop, or treating a failed push as a +failed deploy, are both worse than a missing document. + +Say what you did NOT do. A host skipped, a step you could not run, a verification you had to perform +differently — those are the parts of a report anybody actually needs, and the only ones that are hard to +reconstruct afterwards.