Skip to content

Commit 55908c5

Browse files
committed
fix(release): gate publishing and tags behind CI
1 parent 8a60065 commit 55908c5

11 files changed

Lines changed: 293 additions & 66 deletions

File tree

.github/workflows/publish.yml

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,33 @@
11
name: publish
22

33
on:
4-
push:
5-
tags:
6-
- "v*"
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: "Package version to verify and publish"
8+
required: true
9+
type: string
10+
commit_sha:
11+
description: "Exact release commit SHA to verify and publish"
12+
required: true
13+
type: string
714

815
permissions:
916
id-token: write
1017
contents: write
1118

1219
concurrency:
13-
group: publish-${{ github.ref }}
20+
group: publish-${{ inputs.version }}
1421
cancel-in-progress: false
1522

1623
jobs:
1724
publish:
1825
runs-on: ubuntu-latest
1926
steps:
2027
- uses: actions/checkout@v4
28+
with:
29+
ref: ${{ inputs.commit_sha }}
30+
fetch-depth: 0
2131

2232
- name: Setup Node.js
2333
uses: actions/setup-node@v4
@@ -30,15 +40,39 @@ jobs:
3040
with:
3141
bun-version: 1.3.8
3242

33-
- name: Verify tag matches package version
43+
- name: Verify release request
44+
id: release
45+
env:
46+
REQUESTED_VERSION: ${{ inputs.version }}
47+
REQUESTED_SHA: ${{ inputs.commit_sha }}
3448
run: |
35-
TAG="${GITHUB_REF_NAME}"
49+
set -euo pipefail
3650
PKG_VERSION="$(node -p "require('./package.json').version")"
37-
if [ "v${PKG_VERSION}" != "${TAG}" ]; then
38-
echo "Tag ${TAG} does not match package.json version v${PKG_VERSION}"
51+
CURRENT_SHA="$(git rev-parse HEAD)"
52+
TAG="v${PKG_VERSION}"
53+
54+
if [ "${PKG_VERSION}" != "${REQUESTED_VERSION}" ]; then
55+
echo "Requested version ${REQUESTED_VERSION} does not match package.json version ${PKG_VERSION}"
56+
exit 1
57+
fi
58+
if [ "${CURRENT_SHA}" != "${REQUESTED_SHA}" ]; then
59+
echo "Checked out SHA ${CURRENT_SHA} does not match requested SHA ${REQUESTED_SHA}"
3960
exit 1
4061
fi
41-
echo "Tag ${TAG} matches package.json version v${PKG_VERSION}"
62+
63+
git fetch --force --tags origin
64+
if git rev-parse --verify --quiet "refs/tags/${TAG}" >/dev/null; then
65+
TAG_SHA="$(git rev-list -n 1 "${TAG}")"
66+
if [ "${TAG_SHA}" != "${CURRENT_SHA}" ]; then
67+
echo "Existing tag ${TAG} points to ${TAG_SHA}, expected ${CURRENT_SHA}"
68+
exit 1
69+
fi
70+
echo "Existing tag ${TAG} already points to the requested release commit"
71+
fi
72+
73+
echo "version=${PKG_VERSION}" >> "${GITHUB_OUTPUT}"
74+
echo "tag=${TAG}" >> "${GITHUB_OUTPUT}"
75+
echo "Release request verified for ${TAG} at ${CURRENT_SHA}"
4276
4377
- name: Install dependencies
4478
run: bun install --frozen-lockfile
@@ -62,12 +96,53 @@ jobs:
6296
run: bun run pack:check
6397

6498
- name: Publish to npm
65-
run: npm publish --access public
99+
env:
100+
RELEASE_VERSION: ${{ steps.release.outputs.version }}
101+
run: |
102+
set -euo pipefail
103+
PACKAGE_NAME="@osovv/vv-opencode"
104+
CURRENT_SHA="$(git rev-parse HEAD)"
105+
106+
if PUBLISHED_SHA="$(npm view "${PACKAGE_NAME}@${RELEASE_VERSION}" gitHead 2>/dev/null)"; then
107+
PUBLISHED_SHA="$(printf '%s' "${PUBLISHED_SHA}" | tr -d '[:space:]')"
108+
if [ -z "${PUBLISHED_SHA}" ]; then
109+
echo "${PACKAGE_NAME}@${RELEASE_VERSION} already exists but has no gitHead metadata"
110+
exit 1
111+
fi
112+
if [ "${PUBLISHED_SHA}" != "${CURRENT_SHA}" ]; then
113+
echo "${PACKAGE_NAME}@${RELEASE_VERSION} was published from ${PUBLISHED_SHA}, expected ${CURRENT_SHA}"
114+
exit 1
115+
fi
116+
echo "${PACKAGE_NAME}@${RELEASE_VERSION} is already published from the requested commit; continuing"
117+
else
118+
npm publish --access public --provenance
119+
fi
120+
121+
- name: Create and push release tag
122+
env:
123+
TAG: ${{ steps.release.outputs.tag }}
124+
run: |
125+
set -euo pipefail
126+
CURRENT_SHA="$(git rev-parse HEAD)"
127+
git fetch --force --tags origin
128+
129+
if git rev-parse --verify --quiet "refs/tags/${TAG}" >/dev/null; then
130+
TAG_SHA="$(git rev-list -n 1 "${TAG}")"
131+
if [ "${TAG_SHA}" != "${CURRENT_SHA}" ]; then
132+
echo "Existing tag ${TAG} points to ${TAG_SHA}, expected ${CURRENT_SHA}"
133+
exit 1
134+
fi
135+
echo "Tag ${TAG} already points to ${CURRENT_SHA}; skipping tag push"
136+
else
137+
git tag -a "${TAG}" -m "${TAG}"
138+
git push origin "${TAG}"
139+
fi
66140
67141
- name: Extract changelog entry for release
68142
id: changelog
143+
env:
144+
VERSION: ${{ steps.release.outputs.version }}
69145
run: |
70-
VERSION="${GITHUB_REF_NAME#v}"
71146
awk -v ver="$VERSION" '
72147
$0 ~ "^## " && $0 ~ ver { found=1; print; next }
73148
found && /^## / { exit }
@@ -77,7 +152,7 @@ jobs:
77152
- name: Create GitHub Release
78153
uses: softprops/action-gh-release@v2
79154
with:
80-
tag_name: ${{ github.ref_name }}
155+
tag_name: ${{ steps.release.outputs.tag }}
81156
body_path: /tmp/release-body.md
82157
fail_on_unmatched_files: false
83158
env:

.grace/context/deployment.xml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@
3434
<Command>bun run release:check</Command>
3535
</ReleaseGates>
3636
<Publishing>
37-
<Policy>Publishing is tag-gated through .github/workflows/publish.yml after a local bun run release:bump prepares and pushes the release commit and v* tag.</Policy>
38-
<Workflow>.github/workflows/publish.yml verifies the tag, release consistency, typecheck, lint, format, tests, pack check, then publishes to npm and creates a GitHub Release.</Workflow>
39-
<LocalBump>bun run release:bump wraps npm version, AI changelog summary generation, schema id patching, release checks, release commit, tag creation, and push.</LocalBump>
37+
<Policy>Publishing is exact-commit and CI-gated through an explicit publish.yml workflow dispatch; normal branch and tag pushes never publish.</Policy>
38+
<Workflow>.github/workflows/publish.yml checks out the requested release SHA, verifies version and existing tag/package identity, runs release consistency, typecheck, lint, format, tests, and pack checks, publishes to npm, then creates the annotated tag and GitHub Release.</Workflow>
39+
<LocalBump>bun run release:bump wraps npm version, AI changelog summary generation, schema id patching, release checks, release commit creation, branch-only push, and publish.yml dispatch with exact version/SHA inputs; it never creates or pushes the release tag locally.</LocalBump>
4040
<TrustedPublishing>The workflow uses npm provenance/trusted publishing through id-token permissions.</TrustedPublishing>
4141
<NormalPushes>Normal branch pushes must not publish the package.</NormalPushes>
42+
<Recovery>A rerun may skip npm publication only when the existing package version gitHead matches the exact requested release SHA, then safely recover tag or GitHub Release creation.</Recovery>
4243
</Publishing>
4344
<Rollback>
4445
<Expectation>Package rollback is managed by npm version selection and by rerunning vvoc sync or fixing vvoc.json manually when strict config validation blocks mutation.</Expectation>

.grace/context/requirements.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@
194194
<Constraint>Project-owned specs and plans produced by vvoc skills live under date-prefixed package directories .vvoc/specs/YYYY-MM-DD-&lt;slug&gt;/.</Constraint>
195195
<Constraint>Hashline-backed editing must fail safely on stale or malformed edit references instead of applying ambiguous line-based changes.</Constraint>
196196
<Constraint>Package exports are public API and must remain valid unless an explicit breaking change is approved.</Constraint>
197+
<Constraint>Release automation must verify the exact release commit in CI before npm publication, and it must not create or push the release tag until publication succeeds.</Constraint>
197198
<Constraint>After the 1.0 stabilization point, user-facing setup flow, managed skill names, canonical vvoc schema v3, public package exports, and spec package layout changes must be release-noted as compatibility-relevant changes.</Constraint>
198199
</Constraints>
199200
<Risks>

.grace/context/technology.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
<Script name="check">Runs typecheck, lint, fmt:check, and tests.</Script>
6363
<Script name="pack:check">Builds dist, verifies root, server plugin, and default TUI subpath exports by importing generated files, then runs npm pack --dry-run.</Script>
6464
<Script name="release:check">Runs scripts/release-check.ts to validate package identity, package version, hosted schema id, schema config const, changelog presence, and latest changelog summary.</Script>
65-
<Script name="release:bump">Runs scripts/release-bump.ts to guard a clean worktree, bump version, generate changelog, generate required AI release summary, update schema id, run release:check, commit, tag, and push.</Script>
65+
<Script name="release:bump">Runs scripts/release-bump.ts to guard a clean worktree, bump version, generate changelog, generate required AI release summary, update schema id, run release:check, create and push the release commit, then dispatch publish.yml with exact version/SHA inputs without creating a local tag.</Script>
6666
<Script name="prepare">Installs lefthook hooks.</Script>
6767
</Scripts>
6868
<TestingStack>
@@ -100,7 +100,7 @@
100100
<Constraint>Do not edit generated dist output manually.</Constraint>
101101
<Constraint>Root package exports and plugin subpath exports must stay valid unless an explicit breaking change is approved.</Constraint>
102102
<Constraint>OpenCode TUI integration requires OpenCode 1.18.2 or newer and a default TuiPluginModule export selected from the pinned base package registered in tui.json(c).</Constraint>
103-
<Constraint>Publishing remains manual from terminal with npm publish; normal branch pushes must not publish the package.</Constraint>
103+
<Constraint>Publishing requires an explicit exact-SHA GitHub Actions workflow dispatch; normal branch and tag pushes must not publish the package, and the release tag is created only after CI gates and npm publication succeed.</Constraint>
104104
<Constraint>README, .grace/graph, and .grace/verification must be updated with behavior, public API, setup, command, flow, or verification changes as applicable.</Constraint>
105105
</OperationalConstraints>
106106
</GraceTechnology>

.grace/graph/main.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<M-MODEL-ROLES><Name>ModelRoles</Name><Type>CORE_LOGIC</Type><Status>implemented</Status><Purpose>Define built-in role IDs, vv-role:* syntax, model-selection parsing, role normalization, resolution helpers, and built-in agent role bindings.</Purpose><Paths>src/lib/model-roles.ts</Paths><Depends>none</Depends><Verification>V-M-MODEL-ROLES</Verification></M-MODEL-ROLES>
66
<M-CONFIG-LAYERS><Name>ConfigLayers</Name><Type>UTILITY</Type><Status>implemented</Status><Purpose>Resolve vvoc, OpenCode runtime, and dedicated OpenCode TUI config sources across env, project, global, and defaults plus project/global write targets.</Purpose><Paths>src/lib/config-layers.ts, src/lib/vvoc-paths.ts</Paths><Depends>none</Depends><Verification>V-M-CONFIG-LAYERS</Verification></M-CONFIG-LAYERS>
77
<M-CLI-CONFIG><Name>OpenCodeConfigTools</Name><Type>UTILITY</Type><Status>implemented</Status><Purpose>Mutate OpenCode runtime/TUI and vvoc config conservatively, pin base package registrations, migrate legacy TUI subpath specs, preserve tuple options, inspect OpenCode host compatibility, write managed agent/skill registrations, and render strict schema v3.</Purpose><Paths>src/lib/opencode.ts, src/lib/package.ts, src/lib/vvoc-config.ts, src/lib/model-roles.ts, src/lib/vvoc-paths.ts, schemas/vvoc/v3.json</Paths><Depends>M-CONFIG-LAYERS, M-MODEL-ROLES, M-VVOC-PRESET-REGISTRY, M-CLI-MANAGED-AGENTS, M-CLI-MANAGED-SKILLS, M-CLI-AGENT-MODELS</Depends><Verification>V-M-CLI-CONFIG</Verification></M-CLI-CONFIG>
8-
<M-RELEASE-AUTOMATION><Name>ReleaseAutomation</Name><Type>SCRIPT</Type><Status>implemented</Status><Purpose>Guard package exports and releases with source-path checks, isolated local TUI smoke launches, local version bumping, AI changelog summary generation, schema id consistency, release checks, and tag-gated publish workflow.</Purpose><Paths>scripts/check-exports.ts, scripts/tui-local.ts, scripts/release-bump.ts, scripts/release-check.ts, scripts/release-summary.ts, .github/workflows/publish.yml, schemas/vvoc/v3.json</Paths><Depends>M-CLI-CONFIG, M-CLI-COMMANDS</Depends><Verification>V-M-RELEASE-AUTOMATION</Verification></M-RELEASE-AUTOMATION>
8+
<M-RELEASE-AUTOMATION><Name>ReleaseAutomation</Name><Type>SCRIPT</Type><Status>implemented</Status><Purpose>Guard package exports and releases with source-path checks, deterministic OpenTUI Solid test preloading, isolated local TUI smoke launches, local version bumping, AI changelog summary generation, schema id consistency, exact-SHA CI dispatch, verification-before-publication, and post-publication tag creation.</Purpose><Paths>bunfig.toml, scripts/check-exports.ts, scripts/tui-local.ts, scripts/release-bump.ts, scripts/release-bump.test.ts, scripts/release-check.ts, scripts/release-summary.ts, .github/workflows/publish.yml, schemas/vvoc/v3.json</Paths><Depends>M-CLI-CONFIG, M-CLI-COMMANDS, @opentui/solid, gh CLI, GitHub Actions, npm trusted publishing</Depends><Verification>V-M-RELEASE-AUTOMATION</Verification></M-RELEASE-AUTOMATION>
99
<M-CLI-MANAGED-AGENTS><Name>ManagedAgents</Name><Type>UTILITY</Type><Status>implemented</Status><Purpose>Define bundled vv-controller, enhancer, guardian, investigator, vv-implementer, vv-spec-reviewer, and vv-code-reviewer templates and load managed prompt text.</Purpose><Paths>src/lib/managed-agents.ts, templates/agents/*.md</Paths><Depends>M-CLI-CONFIG</Depends><Verification>V-M-CLI-MANAGED-AGENTS</Verification></M-CLI-MANAGED-AGENTS>
1010
<M-CLI-MANAGED-SKILLS><Name>ManagedSkills</Name><Type>UTILITY</Type><Status>implemented</Status><Purpose>Define bundled vv-spec, vv-plan, vv-review, vv-execute, vv-reflect, and vv-handoff skill templates and reference loading.</Purpose><Paths>src/lib/managed-skills.ts, templates/skills/**/SKILL.md</Paths><Depends>M-CLI-CONFIG</Depends><Verification>V-M-CLI-MANAGED-SKILLS</Verification></M-CLI-MANAGED-SKILLS>
1111
<M-CLI-AGENT-MODELS><Name>AgentModels</Name><Type>UTILITY</Type><Status>implemented</Status><Purpose>Define supported vvoc model target IDs and shared model override validation helpers retained for current command and config compatibility.</Purpose><Paths>src/lib/agent-models.ts</Paths><Depends>M-CLI-MANAGED-AGENTS</Depends><Verification>V-M-CLI-AGENT-MODELS</Verification></M-CLI-AGENT-MODELS>
@@ -40,7 +40,7 @@
4040
<DF-CONFIG-SYNC>vvoc sync strict-parses current vvoc config, preserves valid role/plugin values and unrelated TUI JSONC entries, refreshes managed presets/prompts/skills, and rewrites managed runtime/TUI registrations.</DF-CONFIG-SYNC>
4141
<DF-LAYERED-CONFIG>Commands and runtime resolve vvoc, OpenCode runtime, and OpenCode TUI env overrides, nearest project layer, global layer, then defaults where permitted.</DF-LAYERED-CONFIG>
4242
<DF-VVOC-LAUNCH>vvoc launch selects OpenCode runtime and vvoc config paths, conditionally pins an existing TUI config with OPENCODE_TUI_CONFIG, forwards passthrough args, and preserves child exit code.</DF-VVOC-LAUNCH>
43-
<DF-RELEASE>tui:local builds and injects the local TUI export through an isolated temporary config for pre-release smoke testing; release:bump and publish workflow then verify package/schema/changelog consistency before publishing.</DF-RELEASE>
43+
<DF-RELEASE>tui:local builds and injects the local TUI export through an isolated temporary config for pre-release smoke testing; release:bump creates and pushes only an exact release commit, dispatches publish.yml with its version and SHA, and CI verifies all gates before npm publication, remote tag creation, and GitHub Release creation.</DF-RELEASE>
4444
<DF-ROLE-MANAGEMENT>role list/set/unset loads selected vvoc config, validates role IDs, normalizes assignments, writes role map, or renders inventory.</DF-ROLE-MANAGEMENT>
4545
<DF-ROLE-RESOLUTION>ModelRolesPlugin loads role map, scans supported model fields, resolves vv-role:* references, splits variants where safe, and fails loudly for unknown roles.</DF-ROLE-RESOLUTION>
4646
<DF-PRESET-SWITCH>Preset command reads presets, validates selected name, normalizes role and orchestration assignments, applies declared fields atomically, and leaves OpenCode config untouched.</DF-PRESET-SWITCH>

0 commit comments

Comments
 (0)