mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(pkg): correct stale packaging versions to v0.10.8 + gate the drift
Seven packaging surfaces declared 0.8.1 while v0.10.8 was the shipped release — nine releases of drift in metadata published to package registries, and the third version-metadata drift this month. pkg/go was not cosmetic: it hardcodes its version into the release URL it downloads from, so `go install` fetched v0.8.1 binaries. Chocolatey had to move as a unit. The nuspec is version-only but chocolateyInstall.ps1 pins the asset sha256, so bumping the nuspec alone would ship a package declaring 0.10.8 while installing 0.8.1. The new checksum is the v0.10.8 windows-amd64.zip digest, verified by downloading the published asset and hashing it, not merely copied from checksums.txt. The gate (tests/test_version_metadata_contract.sh, scripts/test.sh Step 0x) checks against the newest stable v-prefixed tag, because nothing in the tree declares the version: CBM_VERSION is injected at build time and the release is dispatched with a version input. That absence is exactly why these files drifted. Mutual consistency across the surfaces needs no git and always runs, so a shallow clone degrades the gate loudly instead of passing silently. Homebrew (0.10.3), Scoop and AUR (0.8.1) are left pinned: each embeds per-release asset checksums, so bumping them is a distribution chore rather than a metadata edit. They are still gated — internally consistent, and never naming a release that does not exist — and each carries a recorded reason for being frozen. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
be7b57828e
commit
5821078ee5
9 files changed
+321
-11
No files matched your search
@@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>codebase-memory-mcp</id>
|
||||
<version>0.8.1</version>
|
||||
<version>0.10.8</version>
|
||||
<title>codebase-memory-mcp</title>
|
||||
<authors>DeusData</authors>
|
||||
<projectUrl>https://github.com/DeusData/codebase-memory-mcp</projectUrl>
|
||||
@@ -28,7 +28,7 @@ After installation, run:
|
||||
|
||||
to configure your coding agents automatically.
|
||||
</description>
|
||||
<releaseNotes>https://github.com/DeusData/codebase-memory-mcp/releases/tag/v0.8.1</releaseNotes>
|
||||
<releaseNotes>https://github.com/DeusData/codebase-memory-mcp/releases/tag/v0.10.8</releaseNotes>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="tools\**" target="tools" />
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$packageName = 'codebase-memory-mcp'
|
||||
$version = '0.8.1'
|
||||
$version = '0.10.8'
|
||||
$url64 = "https://github.com/DeusData/codebase-memory-mcp/releases/download/v${version}/codebase-memory-mcp-windows-amd64.zip"
|
||||
$checksum64 = 'a602ad090ed3f49d86c55472f73f27ad7055222806a82358f2e08513e027f00f'
|
||||
$checksum64 = 'b43ad982994c4d829670749e08d3b622a74bb20041fc0a7d02bef6113f81c34d'
|
||||
$installDir = Join-Path $env:ChocolateyBinRoot $packageName
|
||||
|
||||
Install-ChocolateyZipPackage `
|
||||
|
||||
@@ -36,7 +36,7 @@ import (
|
||||
|
||||
const (
|
||||
repo = "DeusData/codebase-memory-mcp"
|
||||
version = "0.8.1"
|
||||
version = "0.10.8"
|
||||
windowsBinaryName = "codebase-memory-mcp.exe"
|
||||
|
||||
maxRedirects = 5
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codebase-memory-mcp",
|
||||
"version": "0.8.1",
|
||||
"version": "0.10.8",
|
||||
"description": "Fast code intelligence for AI coding agents — installs a verified native runtime set",
|
||||
"mcpName": "io.github.DeusData/codebase-memory-mcp",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "codebase-memory-mcp"
|
||||
version = "0.8.1"
|
||||
version = "0.10.8"
|
||||
description = "Fast code intelligence for AI coding agents — installs a verified native runtime set"
|
||||
readme = "README.md"
|
||||
license = { text = "MIT" }
|
||||
|
||||
@@ -338,7 +338,7 @@ def _version() -> str:
|
||||
from importlib.metadata import version
|
||||
return version("codebase-memory-mcp")
|
||||
except Exception:
|
||||
return "0.8.1"
|
||||
return "0.10.8"
|
||||
|
||||
|
||||
def _os_name() -> str:
|
||||
|
||||
@@ -269,6 +269,9 @@ bash "$ROOT/tests/test_nomic_blob_generator_contract.sh"
|
||||
echo "=== Step 0w: published language-count contract ==="
|
||||
bash "$ROOT/tests/test_language_count_contract.sh"
|
||||
|
||||
echo "=== Step 0x: packaging version-metadata contract ==="
|
||||
bash "$ROOT/tests/test_version_metadata_contract.sh"
|
||||
|
||||
# Verify compiler supports target arch
|
||||
verify_compiler "$CC"
|
||||
|
||||
|
||||
+3
-3
@@ -8,12 +8,12 @@
|
||||
"source": "github"
|
||||
},
|
||||
"websiteUrl": "https://deusdata.github.io/codebase-memory-mcp/",
|
||||
"version": "0.8.1",
|
||||
"version": "0.10.8",
|
||||
"packages": [
|
||||
{
|
||||
"registryType": "npm",
|
||||
"identifier": "codebase-memory-mcp",
|
||||
"version": "0.8.1",
|
||||
"version": "0.10.8",
|
||||
"runtimeHint": "npx",
|
||||
"transport": {
|
||||
"type": "stdio"
|
||||
@@ -22,7 +22,7 @@
|
||||
{
|
||||
"registryType": "pypi",
|
||||
"identifier": "codebase-memory-mcp",
|
||||
"version": "0.8.1",
|
||||
"version": "0.10.8",
|
||||
"runtimeHint": "uvx",
|
||||
"transport": {
|
||||
"type": "stdio"
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
#!/usr/bin/env bash
|
||||
# Every packaging surface that states a product version must state a version we
|
||||
# actually released, and the registry-facing ones must state the newest one.
|
||||
#
|
||||
# Why this exists: server.json, pkg/npm/package.json and the Chocolatey nuspec
|
||||
# sat at 0.8.1 while v0.10.8 was the shipped release — nine releases of drift in
|
||||
# metadata that is published to package registries. It was the third
|
||||
# version-metadata drift in one month. pkg/go additionally hardcodes its version
|
||||
# into the release URL it downloads from, so the stale value was not cosmetic:
|
||||
# `go install` fetched v0.8.1 binaries.
|
||||
#
|
||||
# Why there is no single canonical version file to check against: CBM_VERSION is
|
||||
# injected at BUILD time (-DCBM_VERSION, see Makefile.cbm) and the release is
|
||||
# dispatched with a version input, so nothing in the tree declares the truth.
|
||||
# That absence is precisely why these files drifted — there was nothing to
|
||||
# check them against. The release workflow then institutionalised the drift for
|
||||
# three of them: publish-registries rewrites pkg/npm/package.json and
|
||||
# pkg/pypi/pyproject.toml from the dispatch input, and publish-mcp-registry
|
||||
# rewrites server.json, each with a comment noting the repo copy "can lag". The
|
||||
# published artifact was therefore correct while the repo told everyone reading
|
||||
# it a false version, and no surface outside that sync path was corrected at
|
||||
# all.
|
||||
#
|
||||
# So the truth used here is the newest STABLE v-prefixed git tag: the only
|
||||
# in-repo statement of what actually shipped. Between releases the repo
|
||||
# legitimately sits at the last released version — no surface is bumped ahead of
|
||||
# a release — so "every surface equals the newest tag" is stable, not a
|
||||
# perpetual red.
|
||||
#
|
||||
# Two independent assertions, so the gate cannot go vacuous when tags are absent
|
||||
# (shallow clone, `git archive` tarball, a container that cloned with
|
||||
# --no-tags). Mutual consistency needs no git at all and therefore ALWAYS runs;
|
||||
# the tag comparison runs when tags exist and announces itself loudly when they
|
||||
# do not. A missing truth source degrades this gate, and it says so — it never
|
||||
# reports a silent pass.
|
||||
#
|
||||
# Usage: tests/test_version_metadata_contract.sh
|
||||
# Exit 0 = every assertion passed. Exit 1 = at least one violation. A run that
|
||||
# could extract no version from a registered surface is a FAILURE, not a pass.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
case "${1:-}" in
|
||||
-h | --help)
|
||||
sed -n '2,44p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
failures=0
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
failures=$((failures + 1))
|
||||
}
|
||||
|
||||
# ── 1. The truth: the newest STABLE released tag ────────────────────
|
||||
# Prereleases are excluded (v0.9.1-rc.1): an RC is not what the packaging
|
||||
# surfaces track. Bare tags are excluded too — the v0.10.7 release incident left
|
||||
# a tag named "0.10.7" with no v prefix, and the installers resolve
|
||||
# releases/download/v<version>/..., so only v-prefixed tags describe a release
|
||||
# a user can actually fetch.
|
||||
newest_tag=""
|
||||
if git rev-parse --git-dir >/dev/null 2>&1; then
|
||||
newest_tag=$(
|
||||
git tag -l 'v*' 2>/dev/null |
|
||||
grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' |
|
||||
sed 's/^v//' |
|
||||
sort -t. -k1,1n -k2,2n -k3,3n |
|
||||
tail -1
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ -n "$newest_tag" ]]; then
|
||||
echo "Truth: newest stable release tag is v$newest_tag"
|
||||
else
|
||||
# Not a skip of the contract — only of the half that needs git history.
|
||||
echo "NOTE: no stable v* tags reachable (shallow clone, tarball, or --no-tags)." >&2
|
||||
echo "NOTE: the tag comparison is UNAVAILABLE; mutual consistency is still enforced." >&2
|
||||
fi
|
||||
|
||||
# ── 2. Registered surfaces ──────────────────────────────────────────
|
||||
# "release" = must equal the newest stable tag. Every one of these is
|
||||
# version-only or carries a checksum we re-pin with it, so bumping is a
|
||||
# mechanical edit with no external lookup beyond the release's own checksums.txt.
|
||||
#
|
||||
# "pin:<v>" = frozen at a version we have NOT re-pinned. These embed a
|
||||
# per-release sha256 of a published asset, so bumping them means re-pinning
|
||||
# checksums — a distribution chore, not a metadata edit. They are still gated:
|
||||
# the version must be internally consistent within the file and must be a
|
||||
# version that really shipped. Every pin needs a reason below (PIN_REASONS), so
|
||||
# a frozen surface can never become an unexplained exemption.
|
||||
SURFACES=(
|
||||
"server.json|release"
|
||||
"pkg/npm/package.json|release"
|
||||
"pkg/pypi/pyproject.toml|release"
|
||||
"pkg/pypi/src/codebase_memory_mcp/_cli.py|release"
|
||||
"pkg/go/cmd/codebase-memory-mcp/main.go|release"
|
||||
"pkg/chocolatey/codebase-memory-mcp.nuspec|release"
|
||||
"pkg/chocolatey/tools/chocolateyInstall.ps1|release"
|
||||
"pkg/homebrew/Formula/codebase-memory-mcp.rb|pin:0.10.3"
|
||||
"pkg/scoop/codebase-memory-mcp.json|pin:0.8.1"
|
||||
"pkg/aur/PKGBUILD|pin:0.8.1"
|
||||
"pkg/aur/.SRCINFO|pin:0.8.1"
|
||||
)
|
||||
|
||||
# Why each pinned surface is frozen, and what clearing it costs. To bring one
|
||||
# up to date: download the release's checksums.txt
|
||||
# (gh release download v<X.Y.Z> --pattern checksums.txt), replace the version
|
||||
# AND every sha256 it pins, then move the entry to "release" above.
|
||||
PIN_REASONS=(
|
||||
"pkg/homebrew/Formula/codebase-memory-mcp.rb|pins 4 per-asset sha256 (darwin/linux x arm/intel); last re-pinned for v0.10.3"
|
||||
"pkg/scoop/codebase-memory-mcp.json|pins the windows-amd64.zip sha256; last re-pinned for v0.8.1"
|
||||
"pkg/aur/PKGBUILD|pins sha256sums_x86_64 + sha256sums_aarch64; last re-pinned for v0.8.1"
|
||||
"pkg/aur/.SRCINFO|generated from PKGBUILD, so it must move with it, not before it"
|
||||
)
|
||||
|
||||
# Paths that legitimately contain a semver but make no claim about the
|
||||
# product's current version. Each entry states what its number really is, so an
|
||||
# unexplained exemption cannot hide here.
|
||||
EXEMPT=(
|
||||
'pkg/winget/manifests/' # published version-pinned manifests: the directory
|
||||
# name IS the version, and rewriting a shipped
|
||||
# manifest would falsify what 0.8.1 actually was
|
||||
'pkg/pypi/tests/' # unit-test fixtures; the literal is arbitrary
|
||||
# cache-path input, not a published claim
|
||||
'pkg/npm/test/' # same: publication tests assert shape, not value
|
||||
'pkg/pypi/requirements-publish.txt' # publish-toolchain pins (twine/hatchling)
|
||||
'tests/test_version_metadata_contract.sh' # this file, which names the numbers above
|
||||
)
|
||||
|
||||
# ── 3. Extraction ───────────────────────────────────────────────────
|
||||
# Two independent claim forms, because a surface can lie in either one and the
|
||||
# language-count contract learned the hard way that a gate sharing its regex
|
||||
# with the enumeration can only confirm what the enumeration already saw:
|
||||
# declaration version "X.Y.Z" / "version": "X.Y.Z" / pkgver=X.Y.Z / <version>
|
||||
# release URL releases/download/vX.Y.Z/... , releases/tag/vX.Y.Z , -X.Y.Z-linux
|
||||
# The Homebrew formula interpolates v#{version} into its URLs and so has no URL
|
||||
# form at all; that is fine, the anti-vacuity check below is per FILE, not per
|
||||
# form.
|
||||
versions_in() {
|
||||
local path="$1"
|
||||
{
|
||||
# shellcheck disable=SC2016 # the literal text $version is what the
|
||||
# PowerShell surface writes, so this regex must NOT expand it
|
||||
grep -oiE '(pkgver|packageversion|\$version|"version"|<version>|^[[:space:]]*version)[^0-9]{0,16}[0-9]+\.[0-9]+\.[0-9]+' "$path" |
|
||||
grep -oE '[0-9]+\.[0-9]+\.[0-9]+$'
|
||||
grep -oE 'releases/(download|tag)/v[0-9]+\.[0-9]+\.[0-9]+' "$path" |
|
||||
grep -oE '[0-9]+\.[0-9]+\.[0-9]+$'
|
||||
grep -oE -- '-[0-9]+\.[0-9]+\.[0-9]+-(linux|darwin|windows)' "$path" |
|
||||
grep -oE '[0-9]+\.[0-9]+\.[0-9]+'
|
||||
# Bare literal form: pkg/pypi returns a hardcoded fallback from
|
||||
# _version() when importlib.metadata cannot resolve the installed
|
||||
# distribution, and that string is what `--version` then prints. The
|
||||
# anti-vacuity check below caught this one: the declaration form above
|
||||
# cannot see it, because the version keyword is in the function NAME.
|
||||
grep -oE 'return "[0-9]+\.[0-9]+\.[0-9]+"' "$path" |
|
||||
grep -oE '[0-9]+\.[0-9]+\.[0-9]+'
|
||||
} | sort -u
|
||||
}
|
||||
|
||||
pin_reason_for() {
|
||||
local want="$1" entry
|
||||
for entry in "${PIN_REASONS[@]}"; do
|
||||
if [[ "${entry%%|*}" == "$want" ]]; then
|
||||
printf '%s' "${entry#*|}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
printf ''
|
||||
}
|
||||
|
||||
# ── 4. Every registered surface ─────────────────────────────────────
|
||||
release_surface_count=0
|
||||
checked_values=0
|
||||
|
||||
for entry in "${SURFACES[@]}"; do
|
||||
path="${entry%%|*}"
|
||||
rule="${entry#*|}"
|
||||
|
||||
if [[ ! -f "$path" ]]; then
|
||||
fail "registered surface $path does not exist — update SURFACES"
|
||||
continue
|
||||
fi
|
||||
|
||||
hits=$(versions_in "$path" || true)
|
||||
|
||||
# Anti-vacuity. A surface that states no version is either no longer a
|
||||
# surface or has changed wording in a way this contract can no longer read.
|
||||
# Either way the silence is a defect, not a pass — the whole point of this
|
||||
# gate is that nothing states a version unchecked.
|
||||
if [[ -z "$hits" ]]; then
|
||||
fail "$path is registered as a version surface but states no version —" \
|
||||
"either it lost the claim (drop it from SURFACES) or its wording" \
|
||||
"changed and this check went vacuous"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$rule" == "release" ]]; then
|
||||
release_surface_count=$((release_surface_count + 1))
|
||||
else
|
||||
pinned="${rule#pin:}"
|
||||
reason="$(pin_reason_for "$path")"
|
||||
if [[ -z "$reason" ]]; then
|
||||
fail "$path is pinned at $pinned with no entry in PIN_REASONS —" \
|
||||
"a frozen surface must say WHY it is frozen"
|
||||
fi
|
||||
if [[ -n "$newest_tag" ]] && [[ "$pinned" == "$newest_tag" ]]; then
|
||||
fail "$path is pinned at $pinned, which is now the newest release —" \
|
||||
"move it to the \"release\" rule and drop its PIN_REASONS entry"
|
||||
fi
|
||||
fi
|
||||
|
||||
while IFS= read -r found; do
|
||||
[[ -z "$found" ]] && continue
|
||||
checked_values=$((checked_values + 1))
|
||||
|
||||
if [[ "$rule" == "release" ]]; then
|
||||
# Mutual consistency runs with or without git; the tag comparison
|
||||
# only when a tag exists.
|
||||
if [[ -n "$newest_tag" ]] && [[ "$found" != "$newest_tag" ]]; then
|
||||
fail "$path declares $found, the newest release is $newest_tag"
|
||||
fi
|
||||
else
|
||||
pinned="${rule#pin:}"
|
||||
if [[ "$found" != "$pinned" ]]; then
|
||||
fail "$path declares $found but is pinned at $pinned —" \
|
||||
"it embeds a per-release sha256, so the version and the" \
|
||||
"checksums must move together (update PIN_REASONS or re-pin both)"
|
||||
fi
|
||||
if [[ -n "$newest_tag" ]] &&
|
||||
[[ "$(printf '%s\n%s\n' "$found" "$newest_tag" |
|
||||
sort -t. -k1,1n -k2,2n -k3,3n | tail -1)" != "$newest_tag" ]]; then
|
||||
fail "$path declares $found, which is NEWER than the newest" \
|
||||
"release tag v$newest_tag — it names a release that does not exist"
|
||||
fi
|
||||
fi
|
||||
done <<<"$hits"
|
||||
done
|
||||
|
||||
# ── 5. Mutual consistency, independent of git ───────────────────────
|
||||
# This is the half that still works in a shallow clone. If the tag comparison
|
||||
# above was unavailable, this is the only thing standing between the repo and
|
||||
# silent drift, so it must never be conditional on git.
|
||||
release_values=""
|
||||
for entry in "${SURFACES[@]}"; do
|
||||
path="${entry%%|*}"
|
||||
[[ "${entry#*|}" == "release" ]] || continue
|
||||
[[ -f "$path" ]] || continue
|
||||
# `|| true` is load-bearing: versions_in ends in a pipeline and runs under
|
||||
# pipefail, so a claim form absent from THIS file (server.json has no bare
|
||||
# return form) makes it exit non-zero and set -e would kill the contract
|
||||
# mid-run — exiting 1 with no message, which reads exactly like a failure
|
||||
# that forgot to explain itself.
|
||||
release_values="$release_values$(versions_in "$path" || true)
|
||||
"
|
||||
done
|
||||
distinct=$(printf '%s' "$release_values" | grep -vE '^$' | sort -u || true)
|
||||
distinct_count=$(printf '%s\n' "$distinct" | grep -cE '^[0-9]' || true)
|
||||
|
||||
if ((distinct_count > 1)); then
|
||||
fail "the release-tracking surfaces disagree with each other: $(
|
||||
printf '%s' "$distinct" | tr '\n' ' '
|
||||
)"
|
||||
fi
|
||||
if ((distinct_count == 0)); then
|
||||
fail "no version could be read from ANY release-tracking surface —" \
|
||||
"this contract cannot vouch for anything and must not report success"
|
||||
fi
|
||||
|
||||
# ── 6. No UNREGISTERED packaging surface may state a version ────────
|
||||
# Without this, adding an eighth packaging target reintroduces exactly the drift
|
||||
# this contract exists to prevent, and every check above would still pass. The
|
||||
# sweep pattern is deliberately BROADER than the extractors in section 3: if the
|
||||
# two shared a regex, a claim form the extractors cannot see would be invisible
|
||||
# to the sweep as well.
|
||||
while IFS= read -r path; do
|
||||
[[ -z "$path" ]] && continue
|
||||
for entry in "${SURFACES[@]}"; do
|
||||
[[ "$path" == "${entry%%|*}" ]] && continue 2
|
||||
done
|
||||
for skip in "${EXEMPT[@]}"; do
|
||||
[[ "$path" == "$skip"* ]] && continue 2
|
||||
done
|
||||
fail "$path states a product version but is not registered. Add it to" \
|
||||
"SURFACES so it stays in step with the release, or to EXEMPT with a" \
|
||||
"note saying what its number actually is."
|
||||
done < <(
|
||||
git grep -IlE '(version|pkgver|releases/(download|tag)/v)[^0-9]{0,16}[0-9]+\.[0-9]+\.[0-9]+|return "[0-9]+\.[0-9]+\.[0-9]+"' \
|
||||
-- server.json pkg tests/test_version_metadata_contract.sh 2>/dev/null || true
|
||||
)
|
||||
|
||||
if ((failures > 0)); then
|
||||
echo "FAIL: $failures version-metadata contract violation(s)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$newest_tag" ]]; then
|
||||
echo "Version-metadata contract passed (${#SURFACES[@]} surfaces," \
|
||||
"$release_surface_count tracking v$newest_tag, $checked_values value(s) checked)"
|
||||
else
|
||||
echo "Version-metadata contract passed WITHOUT a tag truth source" \
|
||||
"(${#SURFACES[@]} surfaces, $checked_values value(s) checked, mutual consistency only)"
|
||||
fi
|
||||
Reference in new issue
Block a user