@thegeorgepu — now that the repository is nearly ready to open source, the one substantial gap left is release bookkeeping. Two related problems, both cheap to fix, and one of them blocks the other.
To be clear about scope up front: the release pipeline is not the problem. vinci-release.yml is stronger than anything else we run — build-then-verify with the tested payload carried forward rather than rebuilt, OIDC-assumed role, write-once upload, signed manifest published last as the single activation point, client verification against a pinned key. None of that needs changing. What is missing is the bookkeeping around it: deriving the version, and generating the changelog.
1. The version is maintained by hand in three places
A release today requires editing three files that must agree exactly, and vinci-release.yml hard-fails if they do not:
| Source |
Current value |
the vinci-v* tag |
supplied by hand at release time |
vinci/identity.json → .version |
0.0.49 |
vinci/extensions/vinci-header.ts → VINCI_VERSION |
0.0.49 |
# .github/workflows/vinci-release.yml
test "$TAG_VERSION" = "$IDENTITY_VERSION" || { echo "identity.json disagrees with the tag"; exit 1; }
test "$TAG_VERSION" = "$HEADER_VERSION" || { echo "vinci-header.ts disagrees with the tag"; exit 1; }
The guard is good — it fails closed. But it is catching a class of mistake that should not be possible, and it only runs once a tag is already pushed.
There is also a fourth version number that nothing checks: package.json says 0.0.3 while the product is at 0.0.49. Whatever it was meant to track, it stopped.
2. Release notes exist for 2 of ~49 versions
vinci/release-notes/ contains 0.0.36.md and 0.0.49.md. Everything else shipped with no notes, and that is by design:
If there is no file for the version, the step is skipped and the release proceeds normally — writing notes is optional, and forgetting to never blocks a release.
— vinci/release-notes/README.md
Sensible while private. Once vinci-code-releases is a public tracker, a release appearing with an empty body is what users see. There is also no root CHANGELOG.md.
3. Nothing to automate from — commit messages carry no information
This is the blocker, and it is why I would not just drop a tool in and call it done.
Every Vinci commit on main has the identical subject:
$ git log 244f1dea..HEAD --format='%s' | sort | uniq -c
4 Vinci Code — the Vinci distribution layer
Four commits, one message, repeated. No tool can derive patch vs minor from that, and a generated changelog would have four identical lines. Upstream, by contrast, is 19 of its last 20 commits in Conventional Commits form — the convention works fine in the parts of the tree we did not write.
So the exporter has to emit real commits before any release automation is worth adding. Splitting each export into per-change commits with real subjects is the prerequisite; everything below depends on it.
4. The commit convention is documented where contributors will not see it
A format does exist — in exactly one place:
AGENTS.md:56
- Message format: `{feat,fix,docs}[(ai,tui,agent,coding-agent)]: <commit message> ...`
AGENTS.md is loaded into the agent's context at runtime (resource-loader.ts lists it first among context-file candidates), so coding agents see it. Humans do not: CONTRIBUTING.md mentions the commit format zero times. Nothing enforces it either — husky has a pre-commit hook but no commit-msg hook, and there is no commitlint config.
Two problems with the convention itself:
- The scope list has no
vinci. It reads (ai,tui,agent,coding-agent) — upstream's packages. The Vinci layer, where essentially all of our work happens, has no scope of its own.
- Only
feat, fix, docs are allowed. There is no chore, refactor, test, ci, build or perf, so anything that is not a feature or a bug gets mislabelled. A CI change becomes fix: and silently implies a patch release to any tool reading these.
Recommendation
Release automation: release-it
release-it fits this repository better than release-please or semantic-release, for a specific reason: it stops at the tag.
Our release is triggered by pushing vinci-v*, and everything valuable — signing, OIDC, write-once upload, manifest activation — happens after that. release-please wants to own tagging through its own release PR and would fight that design. semantic-release wants to own publishing. release-it bumps, changelogs, commits, tags and pushes, then gets out of the way, and vinci-release.yml takes over exactly as it does today. No change to the signing pipeline.
It also solves the three-places problem directly, via hooks that update identity.json and vinci-header.ts from the single version it computes — turning the CI guard from something that catches human error into something that should never fire.
npm i -D release-it @release-it/conventional-changelog. The conventional-changelog plugin derives the bump from commit types and writes the changelog, which also fixes problem 2 — and vinci/release-notes/<version>.md can stay for hand-written user-facing notes, with the generated changelog as the complete record underneath.
Do not adopt this before the exporter emits real commits (problem 3), or the bump will be meaningless.
Commit conventions: commitlint on a commit-msg hook
husky is already installed; this is one more hook.
npm i -D @commitlint/cli @commitlint/config-conventional
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
// commitlint.config.js
export default {
extends: ["@commitlint/config-conventional"],
rules: {
"type-enum": [2, "always",
["feat", "fix", "docs", "refactor", "test", "chore", "ci", "build", "perf", "revert"]],
"scope-enum": [2, "always",
["vinci", "ai", "tui", "agent", "coding-agent", "orchestrator", "updater", "release", "deps"]],
"subject-case": [2, "never", ["upper-case", "pascal-case", "start-case"]],
"header-max-length": [2, "always", 100],
},
};
Three things this changes beyond enforcement:
- Adds a
vinci scope, so our own work can be labelled accurately.
- Widens the type list, so a CI change is
ci: and does not imply a release.
- Makes the rule machine-readable, which is what
release-it needs anyway — the same config powers both.
Then document it where humans look: a short section in CONTRIBUTING.md (currently silent on it), and a line in the PR template. AGENTS.md:56 should be updated to match rather than being the only source of truth.
Suggested order
- Exporter emits per-change commits in Conventional Commits form — prerequisite
- commitlint +
commit-msg hook; update AGENTS.md, CONTRIBUTING.md, PR template
vinci/scripts/set-version.mjs writing identity.json and vinci-header.ts from one argument
release-it + conventional-changelog; generate the first CHANGELOG.md
- Decide what
package.json's 0.0.3 is for — track it or drop it
Steps 2 and 3 are useful on their own and do not depend on step 1. Only step 4 does.
Happy to take any of this if it is useful — flagging rather than assuming, since the exporter change is yours.
@thegeorgepu — now that the repository is nearly ready to open source, the one substantial gap left is release bookkeeping. Two related problems, both cheap to fix, and one of them blocks the other.
To be clear about scope up front: the release pipeline is not the problem.
vinci-release.ymlis stronger than anything else we run — build-then-verify with the tested payload carried forward rather than rebuilt, OIDC-assumed role, write-once upload, signed manifest published last as the single activation point, client verification against a pinned key. None of that needs changing. What is missing is the bookkeeping around it: deriving the version, and generating the changelog.1. The version is maintained by hand in three places
A release today requires editing three files that must agree exactly, and
vinci-release.ymlhard-fails if they do not:vinci-v*tagvinci/identity.json→.version0.0.49vinci/extensions/vinci-header.ts→VINCI_VERSION0.0.49The guard is good — it fails closed. But it is catching a class of mistake that should not be possible, and it only runs once a tag is already pushed.
There is also a fourth version number that nothing checks:
package.jsonsays0.0.3while the product is at0.0.49. Whatever it was meant to track, it stopped.2. Release notes exist for 2 of ~49 versions
vinci/release-notes/contains0.0.36.mdand0.0.49.md. Everything else shipped with no notes, and that is by design:Sensible while private. Once
vinci-code-releasesis a public tracker, a release appearing with an empty body is what users see. There is also no rootCHANGELOG.md.3. Nothing to automate from — commit messages carry no information
This is the blocker, and it is why I would not just drop a tool in and call it done.
Every Vinci commit on
mainhas the identical subject:Four commits, one message, repeated. No tool can derive
patchvsminorfrom that, and a generated changelog would have four identical lines. Upstream, by contrast, is 19 of its last 20 commits in Conventional Commits form — the convention works fine in the parts of the tree we did not write.So the exporter has to emit real commits before any release automation is worth adding. Splitting each export into per-change commits with real subjects is the prerequisite; everything below depends on it.
4. The commit convention is documented where contributors will not see it
A format does exist — in exactly one place:
AGENTS.mdis loaded into the agent's context at runtime (resource-loader.tslists it first among context-file candidates), so coding agents see it. Humans do not:CONTRIBUTING.mdmentions the commit format zero times. Nothing enforces it either — husky has apre-commithook but nocommit-msghook, and there is no commitlint config.Two problems with the convention itself:
vinci. It reads(ai,tui,agent,coding-agent)— upstream's packages. The Vinci layer, where essentially all of our work happens, has no scope of its own.feat,fix,docsare allowed. There is nochore,refactor,test,ci,buildorperf, so anything that is not a feature or a bug gets mislabelled. A CI change becomesfix:and silently implies a patch release to any tool reading these.Recommendation
Release automation:
release-itrelease-itfits this repository better than release-please or semantic-release, for a specific reason: it stops at the tag.Our release is triggered by pushing
vinci-v*, and everything valuable — signing, OIDC, write-once upload, manifest activation — happens after that. release-please wants to own tagging through its own release PR and would fight that design. semantic-release wants to own publishing.release-itbumps, changelogs, commits, tags and pushes, then gets out of the way, andvinci-release.ymltakes over exactly as it does today. No change to the signing pipeline.It also solves the three-places problem directly, via hooks that update
identity.jsonandvinci-header.tsfrom the single version it computes — turning the CI guard from something that catches human error into something that should never fire.npm i -D release-it @release-it/conventional-changelog. The conventional-changelog plugin derives the bump from commit types and writes the changelog, which also fixes problem 2 — andvinci/release-notes/<version>.mdcan stay for hand-written user-facing notes, with the generated changelog as the complete record underneath.Do not adopt this before the exporter emits real commits (problem 3), or the bump will be meaningless.
Commit conventions: commitlint on a
commit-msghookhusky is already installed; this is one more hook.
Three things this changes beyond enforcement:
vinciscope, so our own work can be labelled accurately.ci:and does not imply a release.release-itneeds anyway — the same config powers both.Then document it where humans look: a short section in
CONTRIBUTING.md(currently silent on it), and a line in the PR template.AGENTS.md:56should be updated to match rather than being the only source of truth.Suggested order
commit-msghook; updateAGENTS.md,CONTRIBUTING.md, PR templatevinci/scripts/set-version.mjswritingidentity.jsonandvinci-header.tsfrom one argumentrelease-it+ conventional-changelog; generate the firstCHANGELOG.mdpackage.json's0.0.3is for — track it or drop itSteps 2 and 3 are useful on their own and do not depend on step 1. Only step 4 does.
Happy to take any of this if it is useful — flagging rather than assuming, since the exporter change is yours.