From 1b68e39e36cdc8e43b253c7f5678183b8d583a2d Mon Sep 17 00:00:00 2001 From: Albert Najjar Date: Sun, 14 Jun 2026 17:59:37 -0400 Subject: [PATCH] add codestory auto release --- .github/scripts/check-codestory-release.py | 119 ++++++++++ .github/scripts/check-workflow-policy.mjs | 46 ++++ .github/scripts/package-codestory-release.py | 101 +++++++++ .github/workflows/auto-release.yml | 121 ++++++++++ .github/workflows/release.yml | 213 ++++++++++++++++++ .github/workflows/retrieval-sidecar-smoke.yml | 11 +- AGENTS.md | 7 + CHANGELOG.md | 14 ++ docs/contributors/testing-matrix.md | 19 ++ docs/testing/codestory-e2e-stats-log.md | 3 + 10 files changed, 647 insertions(+), 7 deletions(-) create mode 100644 .github/scripts/check-codestory-release.py create mode 100644 .github/scripts/check-workflow-policy.mjs create mode 100644 .github/scripts/package-codestory-release.py create mode 100644 .github/workflows/auto-release.yml create mode 100644 .github/workflows/release.yml create mode 100644 CHANGELOG.md diff --git a/.github/scripts/check-codestory-release.py b/.github/scripts/check-codestory-release.py new file mode 100644 index 0000000..bfb96d9 --- /dev/null +++ b/.github/scripts/check-codestory-release.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import re +import sys +import tomllib +from pathlib import Path + + +SEMVER_RE = re.compile( + r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)" + r"(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?" + r"(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$" +) + + +def read_toml(path: Path) -> dict: + with path.open("rb") as handle: + return tomllib.load(handle) + + +def workspace_members(root: Path) -> list[Path]: + manifest = read_toml(root / "Cargo.toml") + members = manifest.get("workspace", {}).get("members", []) + return [root / member / "Cargo.toml" for member in members] + + +def package_info(manifest_path: Path) -> tuple[str, str]: + manifest = read_toml(manifest_path) + package = manifest.get("package") + if not package: + raise ValueError(f"{manifest_path} does not contain a [package] section") + name = package.get("name") + version = package.get("version") + if not name or not version: + raise ValueError(f"{manifest_path} must declare package.name and package.version") + return name, version + + +def lock_packages(root: Path) -> dict[str, set[str]]: + lock = read_toml(root / "Cargo.lock") + packages: dict[str, set[str]] = {} + for package in lock.get("package", []): + name = package.get("name") + version = package.get("version") + if name and version and name.startswith("codestory-"): + packages.setdefault(name, set()).add(version) + return packages + + +def fail(message: str) -> None: + print(f"error: {message}", file=sys.stderr) + raise SystemExit(1) + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Validate synchronized CodeStory release version surfaces.", + ) + parser.add_argument("--version", required=True, help="Expected release version, without v prefix.") + parser.add_argument( + "--project-root", + default=".", + help="Repository root containing Cargo.toml and Cargo.lock.", + ) + args = parser.parse_args() + + expected = args.version.removeprefix("v") + if not SEMVER_RE.fullmatch(expected): + fail(f"version must be strict semver like 0.7.0, got {args.version!r}") + + root = Path(args.project_root).resolve() + cli_manifest = root / "crates" / "codestory-cli" / "Cargo.toml" + cli_name, cli_version = package_info(cli_manifest) + if cli_name != "codestory-cli": + fail(f"{cli_manifest} package.name is {cli_name!r}, expected 'codestory-cli'") + if cli_version != expected: + fail(f"codestory-cli version is {cli_version}, expected {expected}") + + workspace_versions: dict[str, str] = {} + for manifest_path in workspace_members(root): + name, version = package_info(manifest_path) + if not name.startswith("codestory-"): + continue + workspace_versions[name] = version + if version != expected: + fail(f"{manifest_path.relative_to(root)} is {version}, expected {expected}") + + if "codestory-cli" not in workspace_versions: + fail("workspace members do not include codestory-cli") + + lock_versions = lock_packages(root) + for name in sorted(workspace_versions): + versions = lock_versions.get(name) + if not versions: + fail(f"Cargo.lock does not contain package entry for {name}") + if versions != {expected}: + fail(f"Cargo.lock package {name} versions are {sorted(versions)}, expected {expected}") + + extra_lock_mismatches = { + name: versions + for name, versions in lock_versions.items() + if name.startswith("codestory-") and versions != {expected} + } + if extra_lock_mismatches: + details = ", ".join( + f"{name}={sorted(versions)}" for name, versions in sorted(extra_lock_mismatches.items()) + ) + fail(f"Cargo.lock has CodeStory version mismatches: {details}") + + print( + f"CodeStory release version {expected} is synchronized across " + f"{len(workspace_versions)} workspace crates and Cargo.lock." + ) + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/check-workflow-policy.mjs b/.github/scripts/check-workflow-policy.mjs new file mode 100644 index 0000000..c5b0fa7 --- /dev/null +++ b/.github/scripts/check-workflow-policy.mjs @@ -0,0 +1,46 @@ +#!/usr/bin/env node +import fs from "node:fs"; +import path from "node:path"; + +const workflowRoot = path.join(".github", "workflows"); +const trustedOwners = new Set(["actions", "github"]); +const shaPattern = /^[0-9a-f]{40}$/i; +const violations = []; + +for (const file of fs + .readdirSync(workflowRoot) + .filter((name) => name.endsWith(".yml") || name.endsWith(".yaml"))) { + const workflowPath = path.join(workflowRoot, file); + const content = fs.readFileSync(workflowPath, "utf8"); + + content.split(/\r?\n/).forEach((line, index) => { + const match = line.match(/\buses:\s*['"]?([^'"\s#]+)['"]?/); + if (!match) return; + + const spec = match[1]; + if (spec.startsWith("./")) return; + + const at = spec.lastIndexOf("@"); + if (at === -1) { + violations.push(`${file}:${index + 1} ${spec} is missing a ref`); + return; + } + + const action = spec.slice(0, at); + const ref = spec.slice(at + 1); + const owner = action.split("/")[0]; + + if (!trustedOwners.has(owner) && !shaPattern.test(ref)) { + violations.push( + `${file}:${index + 1} ${spec} must pin third-party actions to a full-length SHA`, + ); + } + }); +} + +if (violations.length > 0) { + console.error(violations.join("\n")); + process.exit(1); +} + +console.log("Workflow policy passed: third-party actions are SHA-pinned."); diff --git a/.github/scripts/package-codestory-release.py b/.github/scripts/package-codestory-release.py new file mode 100644 index 0000000..90ec699 --- /dev/null +++ b/.github/scripts/package-codestory-release.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import hashlib +import shutil +import tarfile +import tempfile +import zipfile +from pathlib import Path + + +def copy_required_file(root: Path, relative: str, destination_root: Path) -> None: + source = root / relative + if not source.is_file(): + raise FileNotFoundError(f"required release file is missing: {relative}") + destination = destination_root / relative + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(source, destination) + + +def copy_required_dir(root: Path, relative: str, destination_root: Path) -> None: + source = root / relative + if not source.is_dir(): + raise FileNotFoundError(f"required release directory is missing: {relative}") + destination = destination_root / relative + if destination.exists(): + shutil.rmtree(destination) + shutil.copytree(source, destination) + + +def archive_zip(source_dir: Path, archive_path: Path) -> None: + with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as archive: + for path in sorted(source_dir.rglob("*")): + if path.is_file(): + archive.write(path, path.relative_to(source_dir.parent).as_posix()) + + +def archive_tar_gz(source_dir: Path, archive_path: Path) -> None: + with tarfile.open(archive_path, "w:gz") as archive: + archive.add(source_dir, arcname=source_dir.name) + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def main() -> None: + parser = argparse.ArgumentParser(description="Package a CodeStory CLI release binary.") + parser.add_argument("--version", required=True, help="Release version without v prefix.") + parser.add_argument("--target", required=True, help="Asset target label.") + parser.add_argument("--binary", required=True, help="Built codestory-cli binary path.") + parser.add_argument("--out-dir", required=True, help="Directory for archive and checksum outputs.") + parser.add_argument("--project-root", default=".", help="Repository root.") + args = parser.parse_args() + + root = Path(args.project_root).resolve() + binary = Path(args.binary).resolve() + if not binary.is_file(): + raise FileNotFoundError(f"binary does not exist: {binary}") + + out_dir = Path(args.out_dir).resolve() + out_dir.mkdir(parents=True, exist_ok=True) + + archive_base = f"codestory-cli-v{args.version}-{args.target}" + archive_ext = ".zip" if "windows" in args.target.lower() else ".tar.gz" + archive_path = out_dir / f"{archive_base}{archive_ext}" + + with tempfile.TemporaryDirectory(prefix="codestory-release-", dir=out_dir) as temp_dir: + stage_root = Path(temp_dir) / archive_base + stage_root.mkdir(parents=True) + + binary_name = "codestory-cli.exe" if binary.suffix.lower() == ".exe" else "codestory-cli" + shutil.copy2(binary, stage_root / binary_name) + + copy_required_file(root, "README.md", stage_root) + copy_required_file(root, "LICENSE", stage_root) + copy_required_file(root, "docs/usage.md", stage_root) + copy_required_dir(root, ".agents/skills/codestory-grounding", stage_root) + + if archive_ext == ".zip": + archive_zip(stage_root, archive_path) + else: + archive_tar_gz(stage_root, archive_path) + + checksum = sha256_file(archive_path) + checksum_line = f"{checksum} {archive_path.name}\n" + checksum_path = out_dir / f"{archive_path.name}.sha256" + checksum_path.write_text(checksum_line, encoding="utf-8", newline="\n") + (out_dir / "SHA256SUMS.txt").write_text(checksum_line, encoding="utf-8", newline="\n") + + print(f"archive={archive_path}") + print(f"checksum={checksum_path}") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml new file mode 100644 index 0000000..9f2d3aa --- /dev/null +++ b/.github/workflows/auto-release.yml @@ -0,0 +1,121 @@ +name: Auto Release + +on: + push: + branches: + - main + paths: + - crates/**/Cargo.toml + - Cargo.lock + - CHANGELOG.md + - AGENTS.md + - docs/contributors/testing-matrix.md + - .github/workflows/auto-release.yml + - .github/workflows/release.yml + - .github/scripts/** + +permissions: + contents: read + +concurrency: + group: auto-release-${{ github.ref }} + cancel-in-progress: false + +jobs: + workflow-policy: + name: Workflow policy + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Enforce third-party action pinning + run: node .github/scripts/check-workflow-policy.mjs + + detect-version: + name: Detect codestory-cli version change + needs: workflow-policy + runs-on: ubuntu-latest + timeout-minutes: 10 + outputs: + should_release: ${{ steps.version.outputs.should_release }} + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Compare codestory-cli versions + id: version + env: + BEFORE_SHA: ${{ github.event.before }} + run: | + set -euo pipefail + + python - <<'PY' + import os + import re + import subprocess + import sys + import tomllib + from pathlib import Path + + semver = re.compile( + r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)" + r"(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?" + r"(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$" + ) + package_path = "crates/codestory-cli/Cargo.toml" + + def read_version_bytes(data: bytes) -> str: + package = tomllib.loads(data.decode("utf-8")).get("package", {}) + return str(package.get("version", "")) + + new_version = read_version_bytes(Path(package_path).read_bytes()) + before = os.environ.get("BEFORE_SHA", "") + old_version = "" + if before and not re.fullmatch(r"0+", before): + result = subprocess.run( + ["git", "show", f"{before}:{package_path}"], + check=False, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + ) + if result.returncode == 0: + old_version = read_version_bytes(result.stdout) + + print(f"Previous codestory-cli version: {old_version or ''}") + print(f"Current codestory-cli version: {new_version}") + + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: + output.write(f"version={new_version}\n") + if old_version == new_version: + print("codestory-cli version did not change; skipping release.") + output.write("should_release=false\n") + sys.exit(0) + + if not semver.fullmatch(new_version): + print( + f"::error::codestory-cli version must be strict semver, got {new_version!r}.", + file=sys.stderr, + ) + sys.exit(1) + + output.write("should_release=true\n") + PY + + - name: Validate synchronized release version + if: steps.version.outputs.should_release == 'true' + run: python .github/scripts/check-codestory-release.py --version "${{ steps.version.outputs.version }}" + + release: + name: Release + needs: detect-version + if: needs.detect-version.outputs.should_release == 'true' + permissions: + contents: write + uses: ./.github/workflows/release.yml + with: + version: ${{ needs.detect-version.outputs.version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..16734f2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,213 @@ +name: Release + +on: + workflow_call: + inputs: + version: + description: "Release version from crates/codestory-cli/Cargo.toml, for example 0.7.0" + required: true + type: string + workflow_dispatch: + inputs: + version: + description: "Release version from crates/codestory-cli/Cargo.toml, for example 0.7.0" + required: true + type: string + +permissions: + contents: read + +concurrency: + group: release-${{ inputs.version }} + cancel-in-progress: false + +jobs: + workflow-policy: + name: Workflow policy + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Enforce third-party action pinning + run: node .github/scripts/check-workflow-policy.mjs + + preflight: + name: Release preflight + needs: workflow-policy + runs-on: ubuntu-latest + timeout-minutes: 10 + outputs: + version: ${{ steps.version.outputs.version }} + tag: ${{ steps.version.outputs.tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Refuse non-main release + run: | + set -euo pipefail + if [ "$GITHUB_REF_NAME" != "main" ]; then + echo "::error::Run releases from main, not $GITHUB_REF_NAME." + exit 1 + fi + + - name: Verify release version + id: version + env: + INPUT_VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + version="${INPUT_VERSION#v}" + python .github/scripts/check-codestory-release.py --version "$version" + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "tag=v$version" >> "$GITHUB_OUTPUT" + + - name: Refuse existing tag or release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ steps.version.outputs.tag }} + run: | + set -euo pipefail + if git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; then + echo "::error::Tag $TAG already exists. Do not push release tags manually." + exit 1 + fi + if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + echo "::error::Release $TAG already exists." + exit 1 + fi + + build: + name: Build ${{ matrix.asset_target }} + needs: preflight + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + rust_target: x86_64-unknown-linux-gnu + asset_target: linux-x64 + exe_suffix: "" + - os: windows-latest + rust_target: x86_64-pc-windows-msvc + asset_target: windows-x64 + exe_suffix: ".exe" + - os: macos-latest + rust_target: x86_64-apple-darwin + asset_target: macos-x64 + exe_suffix: "" + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust stable + shell: pwsh + run: | + rustup toolchain install stable --profile minimal + rustup default stable + rustup target add "${{ matrix.rust_target }}" + + - name: Build codestory-cli + shell: pwsh + run: cargo build --release -p codestory-cli --target "${{ matrix.rust_target }}" + + - name: Smoke codestory-cli + shell: pwsh + run: | + $bin = Join-Path "target/${{ matrix.rust_target }}/release" "codestory-cli${{ matrix.exe_suffix }}" + & $bin --version + & $bin --help + + - name: Package release asset + shell: pwsh + run: | + $bin = Join-Path "target/${{ matrix.rust_target }}/release" "codestory-cli${{ matrix.exe_suffix }}" + python .github/scripts/package-codestory-release.py ` + --version "${{ needs.preflight.outputs.version }}" ` + --target "${{ matrix.asset_target }}" ` + --binary $bin ` + --out-dir target/release-dist + + - name: Upload release asset + uses: actions/upload-artifact@v4 + with: + name: codestory-cli-${{ matrix.asset_target }} + path: | + target/release-dist/*.tar.gz + target/release-dist/*.zip + target/release-dist/*.sha256 + if-no-files-found: error + + publish: + name: Publish GitHub release + needs: + - preflight + - build + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download release assets + uses: actions/download-artifact@v4 + with: + path: target/release-assets + pattern: codestory-cli-* + merge-multiple: true + + - name: Combine and verify checksums + run: | + set -euo pipefail + find target/release-assets -name '*.sha256' -print0 | sort -z | xargs -0 cat > target/release-assets/SHA256SUMS.txt + test -s target/release-assets/SHA256SUMS.txt + (cd target/release-assets && sha256sum -c SHA256SUMS.txt) + + - name: Refuse existing tag or release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ needs.preflight.outputs.tag }} + run: | + set -euo pipefail + if git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; then + echo "::error::Tag $TAG already exists. Do not push release tags manually." + exit 1 + fi + if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + echo "::error::Release $TAG already exists." + exit 1 + fi + + - name: Create GitHub release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ needs.preflight.outputs.tag }} + VERSION: ${{ needs.preflight.outputs.version }} + run: | + set -euo pipefail + shopt -s nullglob + assets=( + target/release-assets/codestory-cli-v${VERSION}-*.tar.gz + target/release-assets/codestory-cli-v${VERSION}-*.zip + target/release-assets/SHA256SUMS.txt + ) + if [ "${#assets[@]}" -ne 4 ]; then + echo "::error::Expected three binary archives plus SHA256SUMS.txt, found ${#assets[@]} assets." + printf '%s\n' "${assets[@]}" + exit 1 + fi + gh release create "$TAG" "${assets[@]}" \ + --repo "$GITHUB_REPOSITORY" \ + --target "$GITHUB_SHA" \ + --title "CodeStory $TAG" \ + --generate-notes diff --git a/.github/workflows/retrieval-sidecar-smoke.yml b/.github/workflows/retrieval-sidecar-smoke.yml index 44e40c0..e0f6b35 100644 --- a/.github/workflows/retrieval-sidecar-smoke.yml +++ b/.github/workflows/retrieval-sidecar-smoke.yml @@ -42,13 +42,10 @@ jobs: with: node-version: "22" - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - - - name: Cache Rust build outputs - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: "true" + - name: Install Rust stable + run: | + rustup toolchain install stable --profile minimal + rustup default stable - name: Generalization lint (production paths) run: node scripts/lint-retrieval-generalization.mjs diff --git a/AGENTS.md b/AGENTS.md index 4765c36..1460360 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,6 +16,7 @@ - Backend build/test: `cargo build`, `cargo test`, `cargo check`, `cargo fmt`, `cargo clippy`. - CLI runtime: `cargo run --release -p codestory-cli -- index --project .`. - Skill-first grounding: `cargo run --release -p codestory-cli -- ground --project .`. +- Release version checks: `python .github/scripts/check-codestory-release.py --version ` and `node .github/scripts/check-workflow-policy.mjs`. - On Windows, the Codex npm shim should be invoked as `codex.cmd` (typically under `%APPDATA%\\npm`); using the extensionless `codex` shim can fail with `os error 193`. - In this PowerShell environment, large parallel file reads can truncate output; when investigating a single large file, prefer one direct read command (for example `Get-Content` or `cmd /c type`) before parallelizing. @@ -39,6 +40,12 @@ - Commit messages are short, lowercase, imperative (e.g., `fix minimap`, `refactor graph style`). - PRs should include a summary, tests run, linked issues, and relevant artifacts for behavior changes. +## Release Guidelines +- `crates/codestory-cli/Cargo.toml` is the release version source. +- Update every `codestory-*` workspace crate version and `Cargo.lock` together. +- Do not create or push `v*` release tags manually. A synchronized version bump on `main` triggers GitHub Actions to create the tag, GitHub release, cross-platform `codestory-cli` binary assets, and `SHA256SUMS.txt`. +- CI binary assets prove build/package smoke only. Packet/search readiness still requires the sidecar evidence tiers in `docs/contributors/testing-matrix.md`. + ## Retrieval documentation - Canonical sidecar retrieval docs are `docs/architecture/retrieval-design.md`, `docs/testing/retrieval-architecture.md`, and `docs/ops/retrieval-sidecars.md`. Parser compatibility records live in `docs/architecture/language-support.md`. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..960fa98 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +## 0.7.0 + +- Current synchronized workspace release baseline. +- Future synchronized CodeStory workspace version bumps on `main` create GitHub + releases with cross-platform `codestory-cli` binary assets and `SHA256SUMS.txt`. + +## Release Notes + +- Add concise human-facing notes under the bumped version before merging a + release version change to `main`. +- Keep release notes focused on user-visible CLI, grounding, retrieval, + packaging, and documentation changes. diff --git a/docs/contributors/testing-matrix.md b/docs/contributors/testing-matrix.md index dbb71d2..55a50de 100644 --- a/docs/contributors/testing-matrix.md +++ b/docs/contributors/testing-matrix.md @@ -35,6 +35,25 @@ cargo clippy --all-targets -- -D warnings These are the default checks for any contributor change. +## Release And Version Bumps + +`crates/codestory-cli/Cargo.toml` is the release version source. When bumping a +release version, update every `codestory-*` workspace crate version and +`Cargo.lock` in the same change. + +```sh +node .github/scripts/check-workflow-policy.mjs +python .github/scripts/check-codestory-release.py --version +``` + +Do not create or push `v*` tags manually. A synchronized version bump merged to +`main` runs the auto-release workflow, which creates the GitHub tag, release, +cross-platform `codestory-cli` archives, and `SHA256SUMS.txt`. + +Binary release assets are packaging evidence only. They are not packet/search +readiness proof; keep using the sidecar evidence tiers below before claiming +agent-facing packet/search readiness. + ## Docs-Only Fast Path If you only changed `README.md` or `docs/**`, use the smallest credible lane: diff --git a/docs/testing/codestory-e2e-stats-log.md b/docs/testing/codestory-e2e-stats-log.md index 7da1182..a350131 100644 --- a/docs/testing/codestory-e2e-stats-log.md +++ b/docs/testing/codestory-e2e-stats-log.md @@ -84,6 +84,7 @@ Rows whose commit cell ends in `+wt` were run from the working tree based on tha | 2026-06-14 | 69c033c4+wt | pass, packet output budget and trace-writer cleanup full-sidecar stats; proof_tier full_sidecar; warnings none; real drill intentionally skipped with CODESTORY_ALLOW_SKIP_REAL_REPO_DRILL_CASES=1; symbol_search_docs 12,425; dense anchors 725; dense skips 11,700; semantic_embedding_ms 46.17s; retrieval_index_seconds 6.90; retrieval_mode full; repeat full refresh 24.87s with 0 embedded; repeat graph 12.49s; repeat semantic 0.70s; repeat cache 5.92s; repeat search projection/index 1.02s/2.55s | 72.75 | 0.30 | 1.69 | 0.57 | 0.25 | 0.21 | 90,984 | 76,741 | 250 | 0 | 725 | true | | 2026-06-14 | 0f7020ed+wt | pass, review remediation spec execution full-sidecar stats; proof_tier full_sidecar; warnings none; real drill intentionally skipped with CODESTORY_ALLOW_SKIP_REAL_REPO_DRILL_CASES=1; symbol_search_docs 12,494; dense anchors 725; dense skips 11,769; semantic_embedding_ms 63.77s; retrieval_index_seconds 5.08; retrieval_mode full; repeat full refresh 28.34s with 0 embedded; repeat graph 13.66s; repeat semantic 1.34s; repeat cache 7.89s; repeat search projection/index 1.29s/2.33s | 101.15 | 0.24 | 1.51 | 0.67 | 0.33 | 0.28 | 91,417 | 77,058 | 251 | 0 | 725 | true | | 2026-06-14 | 3291c4f1+wt | pass, readiness repair, publishable provenance, eval-boundary cleanup, fatal-error readiness split, and full-SHA benchmark manifest pinning full ignored e2e; proof_tier full_sidecar; warnings none; real drill intentionally skipped with CODESTORY_ALLOW_SKIP_REAL_REPO_DRILL_CASES=1 because CODESTORY_REAL_REPO_DRILL_CASES was not set; sidecar_status_after_retrieval_index full; search.sidecar_shadow_retrieval_mode full; symbol_search_docs 12,558; dense anchors 725; dense skips 11,833; semantic_embedding_ms 54.33s; retrieval_index_seconds 8.38; retrieval_status_seconds 0.46; repeat full refresh 29.13s with 725 reused and 0 embedded | 85.71 | 0.26 | 1.92 | 0.70 | 0.24 | 0.24 | 91,707 | 77,287 | 251 | 0 | 725 | true | +| 2026-06-14 | bb4e1ad6+wt | pass, CodeStory auto-release workflow full-sidecar stats; proof_tier full_sidecar; warnings none; real drill intentionally skipped with CODESTORY_ALLOW_SKIP_REAL_REPO_DRILL_CASES=1 because CODESTORY_REAL_REPO_DRILL_CASES was not set; sidecar_status_after_retrieval_index full; search.sidecar_shadow_retrieval_mode full; symbol_search_docs 12,477; dense anchors 725; dense skips 11,752; semantic_embedding_ms 130.54s; retrieval_index_seconds 13.10; retrieval_status_seconds 0.56; repeat full refresh 30.28s with 725 reused and 0 embedded | 166.79 | 0.34 | 1.74 | 0.58 | 0.22 | 0.28 | 91,446 | 77,002 | 250 | 0 | 725 | true | ## Repeat And Report Timing @@ -118,6 +119,7 @@ and zero-reembedding assertions are the actionable repeat-refresh gates. | 2026-06-14 | 69c033c4+wt | packet output budget and trace-writer cleanup full-sidecar stats; proof_tier full_sidecar; real drill skipped with CODESTORY_ALLOW_SKIP_REAL_REPO_DRILL_CASES=1; repeat graph 12.49s; repeat semantic 0.70s; repeat cache/search projection/index 5.92s/1.02s/2.55s | 24.87 | 2.03 | 0.80 | 1.23 | | 2026-06-14 | 0f7020ed+wt | review remediation spec execution full-sidecar stats; proof_tier full_sidecar; real drill skipped with CODESTORY_ALLOW_SKIP_REAL_REPO_DRILL_CASES=1; repeat graph 13.66s; repeat semantic 1.34s; repeat cache/search projection/index 7.89s/1.29s/2.33s | 28.34 | 2.93 | 1.47 | 1.45 | | 2026-06-14 | 3291c4f1+wt | readiness repair, publishable provenance, eval-boundary cleanup, fatal-error readiness split, and full-SHA benchmark manifest pinning full ignored e2e; proof_tier full_sidecar; real drill skipped with CODESTORY_ALLOW_SKIP_REAL_REPO_DRILL_CASES=1 because CODESTORY_REAL_REPO_DRILL_CASES was not set; repeat graph 14.80s; repeat semantic 1.60s; repeat cache/search projection/index 5.88s/1.19s/1.21s | 29.13 | 2.59 | 1.08 | 1.51 | +| 2026-06-14 | bb4e1ad6+wt | CodeStory auto-release workflow full-sidecar stats; proof_tier full_sidecar; real drill skipped with CODESTORY_ALLOW_SKIP_REAL_REPO_DRILL_CASES=1 because CODESTORY_REAL_REPO_DRILL_CASES was not set; repeat graph 14.53s; repeat semantic 1.18s; repeat cache/search projection/index 7.11s/1.72s/1.38s | 30.28 | 2.47 | 1.00 | 1.48 | ## Phase Metrics @@ -195,3 +197,4 @@ from this phase table rather than backfilled. | 2026-06-14 | 69c033c4+wt | packet output budget and trace-writer cleanup full-sidecar stats; proof_tier full_sidecar; symbol_search_docs 12,425; dense anchors 725; dense skips 11,700; reasons public_api 669, entrypoint 6, central_graph_node 40, component_report 10 | 72.75 | 13.61 | 47.47 | 0 | 725 | 0 | | 2026-06-14 | 0f7020ed+wt | review remediation spec execution full-sidecar stats; proof_tier full_sidecar; symbol_search_docs 12,494; dense anchors 725; dense skips 11,769; reasons public_api 669, entrypoint 6, central_graph_node 40, component_report 10 | 101.15 | 16.68 | 73.60 | 0 | 725 | 0 | | 2026-06-14 | 3291c4f1+wt | readiness repair, publishable provenance, eval-boundary cleanup, fatal-error readiness split, and full-SHA benchmark manifest pinning full ignored e2e; proof_tier full_sidecar; warnings none; real drill skipped with CODESTORY_ALLOW_SKIP_REAL_REPO_DRILL_CASES=1 because CODESTORY_REAL_REPO_DRILL_CASES was not set; sidecar_status_after_retrieval_index full; search.sidecar_shadow_retrieval_mode full; retrieval_index_seconds 8.38; retrieval_status_seconds 0.46; repeat full refresh 29.13s with 725 reused and 0 embedded; report_seconds 2.59; symbol_search_docs 12,558; dense anchors 725; dense skips 11,833; reasons public_api 669, entrypoint 6, central_graph_node 40, component_report 10 | 85.71 | 16.59 | 55.35 | 0 | 725 | 0 | +| 2026-06-14 | bb4e1ad6+wt | CodeStory auto-release workflow full-sidecar stats; proof_tier full_sidecar; warnings none; real drill skipped with CODESTORY_ALLOW_SKIP_REAL_REPO_DRILL_CASES=1 because CODESTORY_REAL_REPO_DRILL_CASES was not set; sidecar_status_after_retrieval_index full; search.sidecar_shadow_retrieval_mode full; retrieval_index_seconds 13.10; retrieval_status_seconds 0.56; repeat full refresh 30.28s with 725 reused and 0 embedded; report_seconds 2.47; symbol_search_docs 12,477; dense anchors 725; dense skips 11,752; reasons public_api 669, entrypoint 6, central_graph_node 40, component_report 10 | 166.79 | 20.06 | 132.07 | 0 | 725 | 0 |