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
2 changes: 2 additions & 0 deletions .agents/skills/release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ Before tagging, verify:
- [ ] The PR-safe `release-security` job generated a schema-valid, sentinel-complete SBOM and accepted the Grype policy with a fresh database
- [ ] No scanner binary, database, generated SBOM/report, credential, or `DO_NOT_COMMIT` content is staged
- [ ] For the first SBOM-enabled release, the canonical Mintlify install/verification page contains the README's SBOM scope, policy, checksum, SLSA, and CycloneDX commands (do not edit the deprecated Astro docs instead)
- [ ] If `scripts/install.sh` (or `install.ps1` / `install.cmd`) changed since the last release, the owner syncs it to S3 by hand so `https://plannotator.ai/install.sh` serves the new copy. Safe updates (from 0.28.33) depend on it: an update checks the binary against the checksums Plannotator fetched only when plannotator.ai serves an `install.sh` that reads `PLANNOTATOR_EXPECTED_SHA256`. Until it does, updates still work exactly as before (an older script ignores the variable), but without that check. Confirm with `curl -fsSL https://plannotator.ai/install.sh | grep -c PLANNOTATOR_EXPECTED_SHA256` (non-zero).
- [ ] The release publishes `plannotator-darwin-arm64.sha256`, `plannotator-darwin-x64.sha256`, `plannotator-linux-arm64.sha256` and `plannotator-linux-x64.sha256` under exactly these names, with the binaries. Never rename, merge (for example into one `checksums.txt`), drop or delay them: every binary from 0.28.33 on downloads these names before it updates, and fails to update without its own (`packages/server/update-prepare.ts` `candidateAssets`; guarded by `update-prepare.test.ts`).

After tagging, verify:
- [ ] Release workflow completed with `release-security`, `attest`, `release`, and `npm-publish` green
Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,19 @@ jobs:
)
chmod 755 "$guard_root/parent"

# Safe updates (step 1): the compiled binary's `update-prepare` (the
# update wrapper's download step) prints exactly the staged dir, and a
# full `plannotator update` reaches it through the wrapper (compiled-
# binary detection) against a loopback mirror. Temp HOME and data dir; a
# stand-in install script; nothing leaves the runner.
- name: Smoke-test update-prepare (safe updates)
if: runner.os != 'Windows'
env:
BINARY: artifacts/${{ matrix.binary }}
run: |
chmod +x "$BINARY"
bash scripts/smoke-update-prepare.sh "$BINARY"

- name: Smoke-test binary
if: runner.os == 'Windows'
shell: pwsh
Expand Down
8 changes: 8 additions & 0 deletions apps/hook/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import {
} from "@plannotator/server/review";
import { runGuideCli } from "@plannotator/server/guide-cli";
import { runUpdateCommand } from "./update-command";
import { runUpdatePrepareCommand } from "@plannotator/server/update-prepare";
import { takeUpdateHint } from "@plannotator/shared/update-check";
import {
startAnnotateServer as startAnnotateServerUnguarded,
Expand Down Expand Up @@ -746,6 +747,13 @@ if (args[0] === "update") {
process.exit(await runUpdateCommand(rawArgs.slice(rawArgs.indexOf("update") + 1), { currentVersion: getCliVersion() }));
}

// Internal: the POSIX update wrapper's download step (safe updates, step 1).
// This binary's own fetch downloads the install script and the expected
// checksums into a private temp dir and prints the dir (update-prepare.ts).
if (args[0] === "update-prepare") {
process.exit(await runUpdatePrepareCommand(args.slice(1), { platform: process.platform }));
}

if (args[0] === "install-runtime") {
const runtime = args[1];
if (runtime !== "agent-terminal" && runtime !== "call-flow") {
Expand Down
1 change: 1 addition & 0 deletions apps/hook/server/unknown-subcommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {

const INTERNAL_SUBCOMMANDS = [
"install-runtime",
"update-prepare",
"opencode-plan",
"opencode-review",
"opencode-review-directory",
Expand Down
10 changes: 9 additions & 1 deletion apps/hook/server/update-command-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@
* full entry (index.ts) imports the built HTML, which the test job does not
* build. index.ts dispatches the same function.
*/
import { runUpdatePrepareCommand } from "@plannotator/server/update-prepare";
import { getCliVersion } from "./cli";
import { runUpdateCommand } from "./update-command";

process.exit(await runUpdateCommand(process.argv.slice(2), { currentVersion: getCliVersion() }));
// The update wrapper calls back into its binary for `update-prepare`; a
// source run passes this entry (updatePrepareInvocation), so it answers too.
const argv = process.argv.slice(2);
process.exit(
argv[0] === "update-prepare"
? await runUpdatePrepareCommand(argv.slice(1), { platform: process.platform })
: await runUpdateCommand(argv, { currentVersion: getCliVersion() }),
);
Loading
Loading