chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# Binding ownership policy for codebase-memory-mcp.
|
||||
#
|
||||
# This repository is user-owned, so ownership is expressed with individual
|
||||
# GitHub handles rather than organization teams.
|
||||
#
|
||||
# CODEOWNERS is the binding merge gate, not the reviewer directory.
|
||||
# Advisory area reviewers are recorded in MAINTAINERS.md. A handle appears in
|
||||
# this file only when that person is authorized to satisfy binding code-owner
|
||||
# approval for the matching path.
|
||||
|
||||
# Default owner gate: every pull request crosses the project owner's desk.
|
||||
* @DeusData
|
||||
|
||||
# Security-sensitive and authority-sensitive surfaces.
|
||||
/.github/ @DeusData
|
||||
/scripts/ @DeusData
|
||||
/Makefile.cbm @DeusData
|
||||
/SECURITY.md @DeusData
|
||||
/docs/SECURITY-DISCLOSURE.md @DeusData
|
||||
/CONTRIBUTING.md @DeusData
|
||||
/CODE_OF_CONDUCT.md @DeusData
|
||||
/DCO @DeusData
|
||||
/LICENSE @DeusData
|
||||
/MAINTAINERS.md @DeusData
|
||||
|
||||
# Release and distribution surfaces.
|
||||
/pkg/ @DeusData
|
||||
/install.sh @DeusData
|
||||
/install.ps1 @DeusData
|
||||
/server.json @DeusData
|
||||
/glama.json @DeusData
|
||||
/flake.nix @DeusData
|
||||
/flake.lock @DeusData
|
||||
/THIRD_PARTY.md @DeusData
|
||||
/vendored/ @DeusData
|
||||
/internal/cbm/vendored/ @DeusData
|
||||
|
||||
# Core product surfaces.
|
||||
/src/foundation/ @DeusData
|
||||
/src/store/ @DeusData
|
||||
/src/cypher/ @DeusData
|
||||
/src/graph_buffer/ @DeusData
|
||||
/src/mcp/ @DeusData
|
||||
/src/cli/ @DeusData
|
||||
/src/discover/ @DeusData
|
||||
/src/watcher/ @DeusData
|
||||
/src/pipeline/ @DeusData
|
||||
/src/git/ @DeusData
|
||||
/src/semantic/ @DeusData
|
||||
/src/simhash/ @DeusData
|
||||
/src/traces/ @DeusData
|
||||
/src/ui/ @DeusData
|
||||
|
||||
# Extraction, language support, and graph UI.
|
||||
/internal/cbm/ @DeusData
|
||||
/tools/ @DeusData
|
||||
/graph-ui/ @DeusData
|
||||
|
||||
# Test and reproduction infrastructure.
|
||||
/tests/ @DeusData
|
||||
/test-infrastructure/ @DeusData
|
||||
@@ -0,0 +1,136 @@
|
||||
name: Bug report
|
||||
description: Something is wrong — help us reproduce it exactly.
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for the report! The single most valuable thing you can give us
|
||||
is a **reproduction we can run ourselves**.
|
||||
|
||||
**Please do not paste proprietary code or logs.** If the problem
|
||||
shows up on private code, reproduce it with a small dummy snippet or
|
||||
point us at a **public OSS repository** (e.g. zod, django, serilog)
|
||||
plus the file/commit where it happens — that is how we debug here.
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: Output of `codebase-memory-mcp --version`
|
||||
placeholder: codebase-memory-mcp 0.8.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: Platform
|
||||
options:
|
||||
- macOS (Apple Silicon)
|
||||
- macOS (Intel)
|
||||
- Linux (x64)
|
||||
- Linux (arm64)
|
||||
- Windows (x64)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: channel
|
||||
attributes:
|
||||
label: Install channel
|
||||
options:
|
||||
- GitHub release archive / install.sh / install.ps1
|
||||
- npm
|
||||
- PyPI
|
||||
- Homebrew
|
||||
- AUR
|
||||
- Scoop / Winget / Chocolatey
|
||||
- go install
|
||||
- Built from source
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: variant
|
||||
attributes:
|
||||
label: Binary variant
|
||||
options:
|
||||
- standard
|
||||
- ui
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened, and what did you expect?
|
||||
description: Actual vs. expected behavior, in a few sentences.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction
|
||||
description: |
|
||||
The exact steps so we can reproduce it ourselves:
|
||||
1. The code being indexed — a minimal dummy snippet pasted here, or a
|
||||
public repo + commit + file path (e.g. `colinhacks/zod@a1b2c3d,
|
||||
packages/zod/src/v4/core/parse.ts`).
|
||||
2. The exact command or MCP tool call, including JSON arguments
|
||||
(e.g. `codebase-memory-mcp cli search_graph '{"project":"...",
|
||||
"name_pattern":"..."}'`).
|
||||
3. What the output was vs. what it should have been.
|
||||
placeholder: |
|
||||
1. Code: public repo colinhacks/zod @ <commit>, file packages/...
|
||||
— or dummy snippet:
|
||||
```ts
|
||||
interface Model<T> { create(doc: Partial<T>): Promise<T>; }
|
||||
```
|
||||
2. Command: codebase-memory-mcp cli index_repository '{"repo_path":"/tmp/zod"}'
|
||||
3. Result: ... / Expected: ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs
|
||||
description: |
|
||||
Relevant log output (stderr of the binary; for the UI variant also
|
||||
`GET /api/logs`). Will be auto-formatted as code — no backticks needed.
|
||||
Please check for private paths/strings before pasting.
|
||||
render: text
|
||||
|
||||
- type: textarea
|
||||
id: diagnostics
|
||||
attributes:
|
||||
label: Diagnostics trajectory (memory / performance / leak issues)
|
||||
description: |
|
||||
We collect **no telemetry**, so for a memory or performance problem we
|
||||
need the diagnostics trajectory from your machine. Set
|
||||
`CBM_DIAGNOSTICS=1`, reproduce the issue, then attach (or paste) the file
|
||||
`cbm-diagnostics-<pid>.ndjson` from your temp dir (`$TMPDIR` / `/tmp`, or
|
||||
`%TEMP%` on Windows). It records only resource counters (rss, committed,
|
||||
fds, query counts) over time — no code or queries. See the README
|
||||
"Troubleshooting & Diagnostics" section. Pasting an agent's summary of
|
||||
the trajectory is also fine.
|
||||
render: text
|
||||
|
||||
- type: input
|
||||
id: scale
|
||||
attributes:
|
||||
label: Project scale (if relevant)
|
||||
description: Approximate nodes/edges/files from the indexing summary
|
||||
placeholder: "5,656 nodes / 14,517 edges / 475 files"
|
||||
|
||||
- type: checkboxes
|
||||
id: confirmations
|
||||
attributes:
|
||||
label: Confirmations
|
||||
options:
|
||||
- label: I searched existing issues and this is not a duplicate.
|
||||
required: true
|
||||
- label: My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.
|
||||
required: true
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Security vulnerability
|
||||
url: https://github.com/DeusData/codebase-memory-mcp/security/advisories/new
|
||||
about: Please report security issues privately — never in a public issue. See SECURITY.md.
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Feature request
|
||||
description: Propose an improvement or new capability.
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: What problem does this solve?
|
||||
description: The situation where the current behavior falls short — concrete examples beat abstractions.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: Proposed solution
|
||||
description: What you would like to happen. For new language/LSP support, name the public OSS repos that would make good test beds.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives considered
|
||||
description: Other approaches and why they fall short (optional).
|
||||
|
||||
- type: checkboxes
|
||||
id: confirmations
|
||||
attributes:
|
||||
label: Confirmations
|
||||
options:
|
||||
- label: I searched existing issues and this is not a duplicate.
|
||||
required: true
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -0,0 +1,21 @@
|
||||
# Config for the Issue area labeler (.github/workflows/issue-labeler.yml).
|
||||
# FORMAT (parsed line-wise by the workflow): one rule per line, exactly
|
||||
# "label": 'regex'
|
||||
# Patterns are compiled as JavaScript RegExp with the `i` flag applied by
|
||||
# the workflow — do NOT use PCRE-only syntax like inline `(?i)` groups
|
||||
# (that broke every run, #764). Matched against issue title + body.
|
||||
# Additive only — these never remove a maintainer's manual label.
|
||||
|
||||
"windows": '\b(windows|win\s?1[01]|powershell|\.ps1|mapped drive|smb share|unc path)\b'
|
||||
|
||||
"stability/performance": '(out of memory|\boom\b|memory leak|segfault|sigsegv|sigbus|crash(es|ed|ing)?|core dumped|\bhang(s|ing)?\b|deadlock|freezes?|time(s)?\s?out|timeout|never finishes|cgroup)'
|
||||
|
||||
"parsing/quality": '(trace_path|query_graph|search_graph|calls? edge|missing (edges|nodes)|false positive|zero edges|0 edges|module node|not indexed|extraction|wrong (node|label))'
|
||||
|
||||
"editor/integration": '(cursor|vscode|vs code|opencode|codex|claude code|gemini cli|antigravity|mcp client|\.mcp\.json|installer)'
|
||||
|
||||
"ux/behavior": '(web ui|\bui\b|dashboard|3d graph|visuali[sz]ation|watcher|project name|frontend)'
|
||||
|
||||
"cypher": '\bcypher\b'
|
||||
|
||||
"language-request": '(language support|hybrid lsp|new language support|support for \w+ language)'
|
||||
@@ -0,0 +1,35 @@
|
||||
# Config for dessant/label-actions (.github/workflows/label-actions.yml).
|
||||
# Each top-level key is a label; when that label is added to an issue, the
|
||||
# listed actions run. See https://github.com/dessant/label-actions
|
||||
|
||||
# When a maintainer marks an issue as a duplicate.
|
||||
duplicate:
|
||||
comment: >
|
||||
Thanks for raising this! It looks like a duplicate of an existing issue —
|
||||
the canonical one is linked above. Continuing the discussion there keeps
|
||||
everything in one place. We leave this open for visibility; a maintainer
|
||||
will close it once it's fully covered.
|
||||
# We intentionally do NOT auto-close duplicates (we prefer linking).
|
||||
# To auto-close instead, uncomment the two lines below:
|
||||
# close: true
|
||||
# close-reason: not planned
|
||||
|
||||
# When a maintainer marks an issue as waiting on the reporter.
|
||||
# (The stale workflow then warns at 21 days and closes at 35.)
|
||||
awaiting-reporter:
|
||||
comment: >
|
||||
Thanks for the report! To move this forward we need a bit more so we can
|
||||
reproduce it ourselves:
|
||||
|
||||
|
||||
- the `codebase-memory-mcp --version` you're on
|
||||
|
||||
- the exact steps or command you ran
|
||||
|
||||
- a **public** repo (or a small dummy snippet) that shows the problem —
|
||||
please don't paste proprietary code
|
||||
|
||||
|
||||
Once that's here we'll pick it straight back up. Heads-up: issues left
|
||||
`awaiting-reporter` are automatically closed after a few weeks of silence,
|
||||
but a comment reopens the door anytime.
|
||||
@@ -0,0 +1,11 @@
|
||||
## What does this PR do?
|
||||
|
||||
<!-- Short description of the change and why it is needed. -->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Every commit is signed off (`git commit -s`) — required, CI rejects
|
||||
unsigned commits ([DCO](../DCO), see [CONTRIBUTING.md](../CONTRIBUTING.md))
|
||||
- [ ] Tests pass locally (`make -f Makefile.cbm test`)
|
||||
- [ ] Lint passes (`make -f Makefile.cbm lint-ci`)
|
||||
- [ ] New behavior is covered by a test (reproduce-first for bug fixes)
|
||||
@@ -0,0 +1,378 @@
|
||||
# Reusable: build binaries (standard + UI) on all platforms
|
||||
name: Build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version string (e.g. v0.8.0)'
|
||||
type: string
|
||||
default: ''
|
||||
attest:
|
||||
description: 'Generate build provenance attestations for release artifacts'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
build-unix:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
- os: ubuntu-24.04-arm
|
||||
goos: linux
|
||||
goarch: arm64
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
- os: macos-14
|
||||
goos: darwin
|
||||
goarch: arm64
|
||||
cc: cc
|
||||
cxx: c++
|
||||
- os: macos-15-intel
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
cc: cc
|
||||
cxx: c++
|
||||
runs-on: ${{ matrix.os }}
|
||||
# Every leg is BLOCKING — no continue-on-error. The darwin-amd64 binary
|
||||
# built on macos-15-intel must ship with every release; if that runner is
|
||||
# unavailable the build fails loudly rather than silently publishing a
|
||||
# release with no Intel macOS binary (a user-reported gap). macos-15-intel
|
||||
# is GitHub's supported Intel image through Aug 2027 (the last x86_64 macOS
|
||||
# runner); revisit the Intel leg before that retirement.
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Install deps (Ubuntu)
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
|
||||
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Build standard binary
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
CC: ${{ matrix.cc }}
|
||||
CXX: ${{ matrix.cxx }}
|
||||
run: |
|
||||
if [ -n "$VERSION" ]; then
|
||||
scripts/build.sh --version "$VERSION" "CC=$CC" "CXX=$CXX"
|
||||
else
|
||||
scripts/build.sh "CC=$CC" "CXX=$CXX"
|
||||
fi
|
||||
|
||||
- name: Ad-hoc sign macOS binary
|
||||
if: startsWith(matrix.os, 'macos')
|
||||
run: codesign --sign - --force build/c/codebase-memory-mcp
|
||||
|
||||
- name: Archive standard binary
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
run: |
|
||||
cp LICENSE install.sh build/c/
|
||||
scripts/gen-third-party-notices.sh build/c/THIRD_PARTY_NOTICES.md
|
||||
tar -czf "codebase-memory-mcp-${GOOS}-${GOARCH}.tar.gz" \
|
||||
-C build/c codebase-memory-mcp LICENSE install.sh THIRD_PARTY_NOTICES.md
|
||||
|
||||
- name: Attest standard binary provenance
|
||||
if: ${{ inputs.attest }}
|
||||
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
|
||||
with:
|
||||
subject-path: codebase-memory-mcp-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
|
||||
|
||||
- name: Build UI binary
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
CC: ${{ matrix.cc }}
|
||||
CXX: ${{ matrix.cxx }}
|
||||
run: |
|
||||
if [ -n "$VERSION" ]; then
|
||||
scripts/build.sh --with-ui --version "$VERSION" "CC=$CC" "CXX=$CXX"
|
||||
else
|
||||
scripts/build.sh --with-ui "CC=$CC" "CXX=$CXX"
|
||||
fi
|
||||
|
||||
- name: Ad-hoc sign macOS UI binary
|
||||
if: startsWith(matrix.os, 'macos')
|
||||
run: codesign --sign - --force build/c/codebase-memory-mcp
|
||||
|
||||
- name: Frontend integrity scan
|
||||
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
|
||||
run: scripts/security-ui.sh
|
||||
|
||||
- name: Archive UI binary
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
run: |
|
||||
cp LICENSE install.sh build/c/
|
||||
scripts/gen-third-party-notices.sh build/c/THIRD_PARTY_NOTICES.md
|
||||
tar -czf "codebase-memory-mcp-ui-${GOOS}-${GOARCH}.tar.gz" \
|
||||
-C build/c codebase-memory-mcp LICENSE install.sh THIRD_PARTY_NOTICES.md
|
||||
|
||||
- name: Attest UI binary provenance
|
||||
if: ${{ inputs.attest }}
|
||||
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
|
||||
with:
|
||||
subject-path: codebase-memory-mcp-ui-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
|
||||
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
path: "*.tar.gz"
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
||||
with:
|
||||
msystem: CLANG64
|
||||
path-type: inherit
|
||||
install: >-
|
||||
mingw-w64-clang-x86_64-clang
|
||||
mingw-w64-clang-x86_64-zlib
|
||||
make
|
||||
zip
|
||||
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Build standard binary
|
||||
shell: msys2 {0}
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if [ -n "$VERSION" ]; then
|
||||
scripts/build.sh --version "$VERSION" CC=clang CXX=clang++
|
||||
else
|
||||
scripts/build.sh CC=clang CXX=clang++
|
||||
fi
|
||||
|
||||
- name: Archive standard binary
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
BIN=build/c/codebase-memory-mcp
|
||||
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
|
||||
cp "$BIN" codebase-memory-mcp.exe
|
||||
scripts/gen-third-party-notices.sh THIRD_PARTY_NOTICES.md
|
||||
zip codebase-memory-mcp-windows-amd64.zip codebase-memory-mcp.exe LICENSE install.ps1 THIRD_PARTY_NOTICES.md
|
||||
|
||||
- name: Attest standard binary provenance
|
||||
if: ${{ inputs.attest }}
|
||||
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
|
||||
with:
|
||||
subject-path: codebase-memory-mcp-windows-amd64.zip
|
||||
|
||||
- name: Build UI binary
|
||||
shell: msys2 {0}
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if [ -n "$VERSION" ]; then
|
||||
scripts/build.sh --with-ui --version "$VERSION" CC=clang CXX=clang++
|
||||
else
|
||||
scripts/build.sh --with-ui CC=clang CXX=clang++
|
||||
fi
|
||||
|
||||
- name: Archive UI binary
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
BIN=build/c/codebase-memory-mcp
|
||||
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
|
||||
cp "$BIN" codebase-memory-mcp.exe
|
||||
scripts/gen-third-party-notices.sh THIRD_PARTY_NOTICES.md
|
||||
zip codebase-memory-mcp-ui-windows-amd64.zip codebase-memory-mcp.exe LICENSE install.ps1 THIRD_PARTY_NOTICES.md
|
||||
|
||||
- name: Attest UI binary provenance
|
||||
if: ${{ inputs.attest }}
|
||||
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
|
||||
with:
|
||||
subject-path: codebase-memory-mcp-ui-windows-amd64.zip
|
||||
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: binaries-windows-amd64
|
||||
path: "*.zip"
|
||||
|
||||
build-windows-arm64:
|
||||
# Native ARM64 Windows binary via the CLANGARM64 toolchain on the
|
||||
# windows-11-arm runner (the same toolchain the test suite already
|
||||
# exercises). Without it, ARM-Windows users fall back to the x86_64
|
||||
# binary under emulation, and npm/pip on native-ARM Node/Python 404.
|
||||
runs-on: windows-11-arm
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
||||
with:
|
||||
msystem: CLANGARM64
|
||||
path-type: inherit
|
||||
install: >-
|
||||
mingw-w64-clang-aarch64-clang
|
||||
mingw-w64-clang-aarch64-zlib
|
||||
make
|
||||
zip
|
||||
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Build standard binary
|
||||
shell: msys2 {0}
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if [ -n "$VERSION" ]; then
|
||||
scripts/build.sh --version "$VERSION" CC=clang CXX=clang++
|
||||
else
|
||||
scripts/build.sh CC=clang CXX=clang++
|
||||
fi
|
||||
|
||||
- name: Archive standard binary
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
BIN=build/c/codebase-memory-mcp
|
||||
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
|
||||
cp "$BIN" codebase-memory-mcp.exe
|
||||
scripts/gen-third-party-notices.sh THIRD_PARTY_NOTICES.md
|
||||
zip codebase-memory-mcp-windows-arm64.zip codebase-memory-mcp.exe LICENSE install.ps1 THIRD_PARTY_NOTICES.md
|
||||
|
||||
- name: Attest standard binary provenance
|
||||
if: ${{ inputs.attest }}
|
||||
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
|
||||
with:
|
||||
subject-path: codebase-memory-mcp-windows-arm64.zip
|
||||
|
||||
- name: Build UI binary
|
||||
shell: msys2 {0}
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if [ -n "$VERSION" ]; then
|
||||
scripts/build.sh --with-ui --version "$VERSION" CC=clang CXX=clang++
|
||||
else
|
||||
scripts/build.sh --with-ui CC=clang CXX=clang++
|
||||
fi
|
||||
|
||||
- name: Archive UI binary
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
BIN=build/c/codebase-memory-mcp
|
||||
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
|
||||
cp "$BIN" codebase-memory-mcp.exe
|
||||
scripts/gen-third-party-notices.sh THIRD_PARTY_NOTICES.md
|
||||
zip codebase-memory-mcp-ui-windows-arm64.zip codebase-memory-mcp.exe LICENSE install.ps1 THIRD_PARTY_NOTICES.md
|
||||
|
||||
- name: Attest UI binary provenance
|
||||
if: ${{ inputs.attest }}
|
||||
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
|
||||
with:
|
||||
subject-path: codebase-memory-mcp-ui-windows-arm64.zip
|
||||
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: binaries-windows-arm64
|
||||
path: "*.zip"
|
||||
|
||||
build-linux-portable:
|
||||
# Fully static Linux binaries (gcc -static on Ubuntu).
|
||||
# Runs on any Linux distro without shared library dependencies.
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: amd64
|
||||
runner: ubuntu-latest
|
||||
- arch: arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Install deps
|
||||
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
|
||||
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Build standard binary (static)
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if [ -n "$VERSION" ]; then
|
||||
scripts/build.sh --version "$VERSION" CC=gcc CXX=g++ STATIC=1
|
||||
else
|
||||
scripts/build.sh CC=gcc CXX=g++ STATIC=1
|
||||
fi
|
||||
|
||||
- name: Verify static linking
|
||||
run: |
|
||||
file build/c/codebase-memory-mcp
|
||||
ldd build/c/codebase-memory-mcp 2>&1 | grep -q "not a dynamic executable" || ldd build/c/codebase-memory-mcp 2>&1 | grep -q "statically linked"
|
||||
|
||||
- name: Archive standard binary
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
run: |
|
||||
cp LICENSE install.sh build/c/
|
||||
scripts/gen-third-party-notices.sh build/c/THIRD_PARTY_NOTICES.md
|
||||
tar -czf "codebase-memory-mcp-linux-${ARCH}-portable.tar.gz" \
|
||||
-C build/c codebase-memory-mcp LICENSE install.sh THIRD_PARTY_NOTICES.md
|
||||
|
||||
- name: Attest standard binary provenance
|
||||
if: ${{ inputs.attest }}
|
||||
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
|
||||
with:
|
||||
subject-path: codebase-memory-mcp-linux-${{ matrix.arch }}-portable.tar.gz
|
||||
|
||||
- name: Build UI binary (static)
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
if [ -n "$VERSION" ]; then
|
||||
scripts/build.sh --with-ui --version "$VERSION" CC=gcc CXX=g++ STATIC=1
|
||||
else
|
||||
scripts/build.sh --with-ui CC=gcc CXX=g++ STATIC=1
|
||||
fi
|
||||
|
||||
- name: Archive UI binary
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
run: |
|
||||
cp LICENSE install.sh build/c/
|
||||
scripts/gen-third-party-notices.sh build/c/THIRD_PARTY_NOTICES.md
|
||||
tar -czf "codebase-memory-mcp-ui-linux-${ARCH}-portable.tar.gz" \
|
||||
-C build/c codebase-memory-mcp LICENSE install.sh THIRD_PARTY_NOTICES.md
|
||||
|
||||
- name: Attest UI binary provenance
|
||||
if: ${{ inputs.attest }}
|
||||
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
|
||||
with:
|
||||
subject-path: codebase-memory-mcp-ui-linux-${{ matrix.arch }}-portable.tar.gz
|
||||
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: binaries-linux-${{ matrix.arch }}-portable
|
||||
path: "*.tar.gz"
|
||||
@@ -0,0 +1,51 @@
|
||||
# Reusable: lint only (cppcheck + clang-format).
|
||||
# Security-static and CodeQL gate are separate — see _security.yml.
|
||||
name: Lint & Security
|
||||
|
||||
on:
|
||||
workflow_call: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
# Tests must pass or fail — no SKIPs except genuinely platform-specific
|
||||
# ones (SKIP_PLATFORM / #ifdef). Fails the lint phase on any plain SKIP().
|
||||
- name: No-skips policy (tests pass or fail)
|
||||
run: bash scripts/check-no-test-skips.sh
|
||||
|
||||
- name: Install build deps
|
||||
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev cmake
|
||||
|
||||
- name: Install LLVM 20
|
||||
run: |
|
||||
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
|
||||
echo "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" | sudo tee /etc/apt/sources.list.d/llvm-20.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-format-20
|
||||
|
||||
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
id: cppcheck-cache
|
||||
with:
|
||||
path: /opt/cppcheck
|
||||
key: cppcheck-2.20.0-ubuntu-amd64
|
||||
|
||||
- name: Build cppcheck 2.20.0
|
||||
if: steps.cppcheck-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
git clone --depth 1 --branch 2.20.0 https://github.com/danmar/cppcheck.git /tmp/cppcheck
|
||||
cmake -S /tmp/cppcheck -B /tmp/cppcheck/build -DCMAKE_BUILD_TYPE=Release -DHAVE_RULES=OFF -DCMAKE_INSTALL_PREFIX=/opt/cppcheck
|
||||
cmake --build /tmp/cppcheck/build -j$(nproc)
|
||||
cmake --install /tmp/cppcheck/build
|
||||
|
||||
- name: Add cppcheck to PATH
|
||||
run: echo "/opt/cppcheck/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Lint (cppcheck + clang-format, no clang-tidy — enforced locally)
|
||||
run: scripts/lint.sh --ci CLANG_FORMAT=clang-format-20
|
||||
@@ -0,0 +1,86 @@
|
||||
# Reusable: security-static + CodeQL gate.
|
||||
# Runs independently from lint/test/build — does not block the main pipeline.
|
||||
# Affects overall workflow success status.
|
||||
name: Security Gate
|
||||
|
||||
on:
|
||||
workflow_call: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
security-static:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: "Layer 1: Static allow-list audit"
|
||||
run: scripts/security-audit.sh
|
||||
- name: "Layer 6: UI security audit"
|
||||
run: scripts/security-ui.sh
|
||||
- name: "Layer 8: Vendored dependency integrity"
|
||||
run: scripts/security-vendored.sh
|
||||
|
||||
license-gate:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Install ScanCode Toolkit
|
||||
run: pipx install scancode-toolkit
|
||||
- name: "Gate self-test (a planted violation must be detected)"
|
||||
run: scripts/license-gate.sh --selftest
|
||||
- name: "License compliance gate (one finding fails)"
|
||||
run: scripts/license-gate.sh
|
||||
- name: "License provenance audit (byte-identity vs upstream)"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: scripts/audit-license-provenance.py
|
||||
|
||||
codeql-gate:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- name: Wait for CodeQL on current commit (max 45 min)
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# On pull_request events github.sha is the synthetic merge commit;
|
||||
# CodeQL runs are recorded against the PR head SHA.
|
||||
CURRENT_SHA="${{ github.event.pull_request.head.sha || github.sha }}"
|
||||
echo "Waiting for CodeQL to complete on $CURRENT_SHA..."
|
||||
for attempt in $(seq 1 90); do
|
||||
LATEST=$(gh api "repos/${{ github.repository }}/actions/workflows/codeql.yml/runs?head_sha=$CURRENT_SHA&per_page=1" \
|
||||
--jq '.workflow_runs[] | "\(.conclusion) \(.status)"' 2>/dev/null | head -1 || echo "")
|
||||
if [ -z "$LATEST" ]; then
|
||||
echo " $attempt/90: no run yet..."; sleep 30; continue
|
||||
fi
|
||||
CONCLUSION=$(echo "$LATEST" | cut -d' ' -f1)
|
||||
STATUS=$(echo "$LATEST" | cut -d' ' -f2)
|
||||
if [ "$STATUS" = "completed" ] && [ "$CONCLUSION" = "success" ]; then
|
||||
echo "=== CodeQL passed ==="; exit 0
|
||||
elif [ "$STATUS" = "completed" ]; then
|
||||
echo "BLOCKED: CodeQL $CONCLUSION"; exit 1
|
||||
fi
|
||||
echo " $attempt/90: $STATUS..."; sleep 30
|
||||
done
|
||||
echo "BLOCKED: CodeQL timeout"; exit 1
|
||||
|
||||
- name: Check for open code scanning alerts
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "Waiting 60s for alert API to settle..."
|
||||
sleep 60
|
||||
ALERTS=$(gh api 'repos/${{ github.repository }}/code-scanning/alerts?state=open' --jq 'length' 2>/dev/null || echo "0")
|
||||
sleep 15
|
||||
ALERTS2=$(gh api 'repos/${{ github.repository }}/code-scanning/alerts?state=open' --jq 'length' 2>/dev/null || echo "0")
|
||||
[ "$ALERTS" -lt "$ALERTS2" ] && ALERTS=$ALERTS2
|
||||
if [ "$ALERTS" -gt 0 ]; then
|
||||
echo "BLOCKED: $ALERTS open alert(s)"
|
||||
gh api 'repos/${{ github.repository }}/code-scanning/alerts?state=open' \
|
||||
--jq '.[] | " #\(.number) [\(.rule.security_severity_level // .rule.severity)] \(.rule.id) — \(.most_recent_instance.location.path):\(.most_recent_instance.location.start_line)"' 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
echo "=== CodeQL gate passed (0 alerts) ==="
|
||||
@@ -0,0 +1,328 @@
|
||||
# Reusable: smoke test every binary (standard + UI, all platforms)
|
||||
name: Smoke
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
broad_platforms:
|
||||
description: 'Smoke the shipped binaries on the broad platform matrix (extra OS versions) instead of the core set'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# Emit the platform matrices as JSON. The CORE set is the default (fast,
|
||||
# unchanged); the BROAD set adds extra free runners (additional OS versions)
|
||||
# that download the SAME shipped artifact for their goos/goarch and verify it
|
||||
# runs on a wider range of OS versions. No new artifacts are built — broad
|
||||
# legs reuse the exact binaries produced by _build.yml.
|
||||
setup-matrix:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
unix: ${{ steps.set.outputs.unix }}
|
||||
windows: ${{ steps.set.outputs.windows }}
|
||||
portable: ${{ steps.set.outputs.portable }}
|
||||
steps:
|
||||
- name: Compute matrices
|
||||
id: set
|
||||
env:
|
||||
BROAD: ${{ inputs.broad_platforms }}
|
||||
run: |
|
||||
CORE_UNIX='[
|
||||
{"os":"ubuntu-latest","goos":"linux","goarch":"amd64"},
|
||||
{"os":"ubuntu-24.04-arm","goos":"linux","goarch":"arm64"},
|
||||
{"os":"macos-14","goos":"darwin","goarch":"arm64"},
|
||||
{"os":"macos-15-intel","goos":"darwin","goarch":"amd64"}
|
||||
]'
|
||||
# Broad legs reuse existing goos/goarch artifacts on additional OS
|
||||
# versions to widen the run-anywhere signal without building new targets.
|
||||
# Broad legs are REQUIRED gates (no optional / continue-on-error):
|
||||
# every smoke leg must pass. No `optional` flags anywhere.
|
||||
#
|
||||
# NOTE: the *dynamic* linux binary links glibc 2.38+ and cannot run on
|
||||
# older distros by design — older-glibc coverage is the -portable (static)
|
||||
# binary's job, exercised green by the smoke-linux-portable broad legs
|
||||
# (ubuntu-22.04 / 22.04-arm). So the dynamic broad legs stay on
|
||||
# forward-compatible OSes only (macOS); running the dynamic binary on
|
||||
# ubuntu-22.04 would fail Phase 1 (glibc too old), not a real regression.
|
||||
BROAD_UNIX='[
|
||||
{"os":"macos-15","goos":"darwin","goarch":"arm64"}
|
||||
]'
|
||||
CORE_WIN='[{"os":"windows-latest","arch":"amd64"}]'
|
||||
# windows-11-arm now runs the NATIVE arm64 binary (build-windows-arm64),
|
||||
# not the x86_64 binary under emulation — we smoke the artifact we ship.
|
||||
BROAD_WIN='[{"os":"windows-2025","arch":"amd64"},{"os":"windows-11-arm","arch":"arm64"}]'
|
||||
CORE_PORTABLE='[
|
||||
{"arch":"amd64","runner":"ubuntu-latest"},
|
||||
{"arch":"arm64","runner":"ubuntu-24.04-arm"}
|
||||
]'
|
||||
BROAD_PORTABLE='[
|
||||
{"arch":"amd64","runner":"ubuntu-22.04"},
|
||||
{"arch":"arm64","runner":"ubuntu-22.04-arm"}
|
||||
]'
|
||||
if [ "$BROAD" = "true" ]; then
|
||||
UNIX=$(jq -cn --argjson a "$CORE_UNIX" --argjson b "$BROAD_UNIX" '$a + $b')
|
||||
WIN=$(jq -cn --argjson a "$CORE_WIN" --argjson b "$BROAD_WIN" '$a + $b')
|
||||
PORTABLE=$(jq -cn --argjson a "$CORE_PORTABLE" --argjson b "$BROAD_PORTABLE" '$a + $b')
|
||||
else
|
||||
UNIX=$(jq -cn --argjson a "$CORE_UNIX" '$a')
|
||||
WIN=$(jq -cn --argjson a "$CORE_WIN" '$a')
|
||||
PORTABLE=$(jq -cn --argjson a "$CORE_PORTABLE" '$a')
|
||||
fi
|
||||
# Expand each OS entry over both variants into a FLAT include list so
|
||||
# every {os,variant} runs as its own job. A {variant:[...],include:$LIST}
|
||||
# matrix collapses under GitHub's include-merge (later os entries
|
||||
# overwrite the variant combos last-wins), which silently dropped every
|
||||
# OS except the last — e.g. only windows-11-arm ran, never windows-latest.
|
||||
# Building the cartesian explicitly avoids that.
|
||||
VARIANTS='["standard","ui"]'
|
||||
UNIX_M=$(jq -cn --argjson a "$UNIX" --argjson v "$VARIANTS" '{include:[$a[] as $o | $v[] as $t | ($o + {variant:$t})]}')
|
||||
WIN_M=$(jq -cn --argjson a "$WIN" --argjson v "$VARIANTS" '{include:[$a[] as $o | $v[] as $t | ($o + {variant:$t})]}')
|
||||
PORTABLE_M=$(jq -cn --argjson a "$PORTABLE" --argjson v "$VARIANTS" '{include:[$a[] as $o | $v[] as $t | ($o + {variant:$t})]}')
|
||||
echo "unix=$UNIX_M" >> "$GITHUB_OUTPUT"
|
||||
echo "windows=$WIN_M" >> "$GITHUB_OUTPUT"
|
||||
echo "portable=$PORTABLE_M" >> "$GITHUB_OUTPUT"
|
||||
|
||||
smoke-unix:
|
||||
needs: setup-matrix
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.setup-matrix.outputs.unix) }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
|
||||
- name: Extract binary
|
||||
run: |
|
||||
SUFFIX=${{ matrix.variant == 'ui' && '-ui' || '' }}
|
||||
tar -xzf codebase-memory-mcp${SUFFIX}-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
|
||||
chmod +x codebase-memory-mcp
|
||||
|
||||
- name: Start artifact server
|
||||
run: |
|
||||
mkdir -p /tmp/smoke-server
|
||||
cp codebase-memory-mcp /tmp/smoke-server/
|
||||
OS=${{ matrix.goos }}; ARCH=${{ matrix.goarch }}
|
||||
SUFFIX=${{ matrix.variant == 'ui' && '-ui' || '' }}
|
||||
tar -czf "/tmp/smoke-server/codebase-memory-mcp${SUFFIX}-${OS}-${ARCH}.tar.gz" \
|
||||
-C /tmp/smoke-server codebase-memory-mcp
|
||||
if [ -n "$SUFFIX" ]; then
|
||||
cp "/tmp/smoke-server/codebase-memory-mcp${SUFFIX}-${OS}-${ARCH}.tar.gz" \
|
||||
"/tmp/smoke-server/codebase-memory-mcp-${OS}-${ARCH}.tar.gz"
|
||||
fi
|
||||
# The linux binary self-updates from the fully-static "-portable" asset
|
||||
# (build_update_url in src/cli/cli.c appends -portable on linux; _build.yml's
|
||||
# build-linux-portable job ships it), so the smoke server must serve that name
|
||||
# too -- otherwise `update` 404s and Phase 14 fails. Mirror the tarball(s) under
|
||||
# the -portable name on linux only.
|
||||
if [ "$OS" = "linux" ]; then
|
||||
cp "/tmp/smoke-server/codebase-memory-mcp${SUFFIX}-${OS}-${ARCH}.tar.gz" \
|
||||
"/tmp/smoke-server/codebase-memory-mcp${SUFFIX}-${OS}-${ARCH}-portable.tar.gz"
|
||||
if [ -n "$SUFFIX" ]; then
|
||||
cp "/tmp/smoke-server/codebase-memory-mcp${SUFFIX}-${OS}-${ARCH}-portable.tar.gz" \
|
||||
"/tmp/smoke-server/codebase-memory-mcp-${OS}-${ARCH}-portable.tar.gz"
|
||||
fi
|
||||
fi
|
||||
cd /tmp/smoke-server
|
||||
sha256sum *.tar.gz > checksums.txt 2>/dev/null || shasum -a 256 *.tar.gz > checksums.txt
|
||||
python3 -m http.server 18080 -d /tmp/smoke-server &
|
||||
|
||||
- name: Smoke test
|
||||
run: scripts/smoke-test.sh ./codebase-memory-mcp
|
||||
env:
|
||||
SMOKE_DOWNLOAD_URL: http://localhost:18080
|
||||
|
||||
- name: Security audits
|
||||
run: |
|
||||
scripts/security-strings.sh ./codebase-memory-mcp
|
||||
scripts/security-install.sh ./codebase-memory-mcp
|
||||
scripts/security-network.sh ./codebase-memory-mcp
|
||||
|
||||
- name: MCP robustness test (linux-amd64 standard only)
|
||||
if: matrix.variant == 'standard' && matrix.goos == 'linux' && matrix.goarch == 'amd64'
|
||||
run: |
|
||||
scripts/security-fuzz.sh ./codebase-memory-mcp
|
||||
scripts/security-fuzz-random.sh ./codebase-memory-mcp 60
|
||||
|
||||
- name: ClamAV scan (Linux)
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq clamav > /dev/null 2>&1
|
||||
# apt auto-starts the clamav-freshclam daemon, which holds a lock on
|
||||
# freshclam's log/db; stop it so the manual freshclam below can run
|
||||
# (else: "Failed to lock the log file ... Resource temporarily unavailable").
|
||||
sudo systemctl stop clamav-freshclam 2>/dev/null || true
|
||||
sudo sed -i 's/^Example/#Example/' /etc/clamav/freshclam.conf 2>/dev/null || true
|
||||
grep -q "DatabaseMirror" /etc/clamav/freshclam.conf 2>/dev/null || \
|
||||
echo "DatabaseMirror database.clamav.net" | sudo tee -a /etc/clamav/freshclam.conf > /dev/null
|
||||
sudo freshclam --quiet
|
||||
clamscan --no-summary ./codebase-memory-mcp
|
||||
|
||||
- name: ClamAV scan (macOS)
|
||||
if: startsWith(matrix.os, 'macos')
|
||||
run: |
|
||||
brew install clamav > /dev/null 2>&1
|
||||
CLAMAV_ETC=$(brew --prefix)/etc/clamav
|
||||
if [ ! -f "$CLAMAV_ETC/freshclam.conf" ]; then
|
||||
cp "$CLAMAV_ETC/freshclam.conf.sample" "$CLAMAV_ETC/freshclam.conf" 2>/dev/null || true
|
||||
sed -i '' 's/^Example/#Example/' "$CLAMAV_ETC/freshclam.conf" 2>/dev/null || true
|
||||
echo "DatabaseMirror database.clamav.net" >> "$CLAMAV_ETC/freshclam.conf"
|
||||
fi
|
||||
freshclam --quiet --no-warnings 2>/dev/null || freshclam --quiet 2>/dev/null || echo "WARNING: freshclam update failed"
|
||||
clamscan --no-summary ./codebase-memory-mcp
|
||||
|
||||
smoke-windows:
|
||||
needs: setup-matrix
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.setup-matrix.outputs.windows) }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
||||
with:
|
||||
msystem: ${{ matrix.arch == 'arm64' && 'CLANGARM64' || 'CLANG64' }}
|
||||
path-type: inherit
|
||||
install: >-
|
||||
mingw-w64-clang-${{ matrix.arch == 'arm64' && 'aarch64' || 'x86_64' }}-python3
|
||||
mingw-w64-clang-${{ matrix.arch == 'arm64' && 'aarch64' || 'x86_64' }}-curl
|
||||
unzip
|
||||
zip
|
||||
coreutils
|
||||
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: binaries-windows-${{ matrix.arch }}
|
||||
|
||||
- name: Extract binary
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
SUFFIX=${{ matrix.variant == 'ui' && '-ui' || '' }}
|
||||
ARCH=${{ matrix.arch }}
|
||||
unzip -o "codebase-memory-mcp${SUFFIX}-windows-${ARCH}.zip"
|
||||
[ -n "$SUFFIX" ] && cp "codebase-memory-mcp${SUFFIX}.exe" codebase-memory-mcp.exe || true
|
||||
|
||||
- name: Start artifact server
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
mkdir -p /tmp/smoke-server
|
||||
cp codebase-memory-mcp.exe /tmp/smoke-server/
|
||||
SUFFIX=${{ matrix.variant == 'ui' && '-ui' || '' }}
|
||||
ARCH=${{ matrix.arch }}
|
||||
cd /tmp/smoke-server
|
||||
zip -q "codebase-memory-mcp${SUFFIX}-windows-${ARCH}.zip" codebase-memory-mcp.exe
|
||||
if [ -n "$SUFFIX" ]; then
|
||||
cp "codebase-memory-mcp${SUFFIX}-windows-${ARCH}.zip" "codebase-memory-mcp-windows-${ARCH}.zip"
|
||||
fi
|
||||
sha256sum *.zip > checksums.txt
|
||||
# Pin to explicit IPv4: on windows-11-arm, `localhost` resolves to ::1 (IPv6)
|
||||
# for msys2 curl while python's http.server is IPv4-only -> instant connect
|
||||
# failure in smoke Phase 12a. --bind 127.0.0.1 + a 127.0.0.1 URL pin both ends.
|
||||
python3 -m http.server 18080 --bind 127.0.0.1 -d /tmp/smoke-server &
|
||||
|
||||
- name: Smoke test
|
||||
shell: msys2 {0}
|
||||
run: scripts/smoke-test.sh ./codebase-memory-mcp.exe
|
||||
env:
|
||||
SMOKE_DOWNLOAD_URL: http://127.0.0.1:18080
|
||||
SMOKE_ARCH: ${{ matrix.arch }}
|
||||
|
||||
- name: Security audits
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
scripts/security-strings.sh ./codebase-memory-mcp.exe
|
||||
scripts/security-install.sh ./codebase-memory-mcp.exe
|
||||
|
||||
- name: Windows Defender scan
|
||||
shell: pwsh
|
||||
run: |
|
||||
& "C:\Program Files\Windows Defender\MpCmdRun.exe" -SignatureUpdate 2>$null
|
||||
$result = & "C:\Program Files\Windows Defender\MpCmdRun.exe" -Scan -ScanType 3 -File "$PWD\codebase-memory-mcp.exe" -DisableRemediation
|
||||
$code = $LASTEXITCODE
|
||||
Write-Host $result
|
||||
# MpCmdRun -Scan exit codes: 0 = clean, 2 = threat found. Any OTHER non-zero
|
||||
# means the scan engine could not run at all (e.g. hr=0x800106ba: the Defender
|
||||
# antimalware service is unavailable on the runner) — that is NOT a detection.
|
||||
# Fail soft on an engine failure so a transient runner-side AV outage can't
|
||||
# false-fail a release; only a real detection (exit 2) hard-blocks.
|
||||
if ($code -eq 2) {
|
||||
Write-Host "BLOCKED: Windows Defender flagged binary!"; exit 1
|
||||
} elseif ($code -ne 0) {
|
||||
Write-Host "::warning::Windows Defender scan could not run (exit $code) - skipping AV gate on this runner"
|
||||
} else {
|
||||
Write-Host "=== Windows Defender: clean ==="
|
||||
}
|
||||
|
||||
smoke-linux-portable:
|
||||
needs: setup-matrix
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.setup-matrix.outputs.portable) }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: binaries-linux-${{ matrix.arch }}-portable
|
||||
|
||||
- name: Extract and smoke test
|
||||
run: |
|
||||
SUFFIX=${{ matrix.variant == 'ui' && '-ui' || '' }}
|
||||
tar -xzf codebase-memory-mcp${SUFFIX}-linux-${{ matrix.arch }}-portable.tar.gz
|
||||
chmod +x codebase-memory-mcp
|
||||
scripts/smoke-test.sh ./codebase-memory-mcp
|
||||
|
||||
# The -portable binary is what all linux install/update paths now deliver;
|
||||
# it MUST start on old glibc (Debian 11 / RHEL 8 / Ubuntu 20.04). Runs it
|
||||
# in debian:bullseye (glibc 2.31) — the standard dynamic binary would fail
|
||||
# here with `GLIBC_2.38 not found`.
|
||||
- name: Old-glibc compatibility
|
||||
run: scripts/ci/check-glibc-compat.sh ./codebase-memory-mcp
|
||||
|
||||
- name: Security audits
|
||||
run: |
|
||||
scripts/security-strings.sh ./codebase-memory-mcp
|
||||
scripts/security-install.sh ./codebase-memory-mcp
|
||||
scripts/security-network.sh ./codebase-memory-mcp
|
||||
|
||||
# ── Packaging check (non-gating) ──────────────────────────────────
|
||||
# Builds pkg/glama/Dockerfile — the image Glama builds to score the
|
||||
# server — and runs an MCP initialize + tools/list handshake to confirm the
|
||||
# containerized stdio server still starts and introspects. Guards the Glama
|
||||
# listing integration against drift (Dockerfile breakage, release-asset
|
||||
# renames, introspection regressions).
|
||||
#
|
||||
# Also tests the brew tap/install flow.
|
||||
#
|
||||
# continue-on-error: a broken *directory* image must never block a release —
|
||||
# the shipped product binaries are unaffected. The red X is the signal to fix
|
||||
# the integration, not a release gate.
|
||||
smoke-packages:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Build Glama image and verify MCP introspection
|
||||
run: bash pkg/glama/verify.sh
|
||||
- name: Test homebrew installation
|
||||
env:
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
run: |
|
||||
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
|
||||
brew tap deusdata/codebase-memory-mcp "$GITHUB_WORKSPACE"
|
||||
brew trust deusdata/codebase-memory-mcp
|
||||
brew install codebase-memory-mcp
|
||||
codebase-memory-mcp --version
|
||||
@@ -0,0 +1,285 @@
|
||||
# Reusable: soak tests (quick + ASan, all platforms)
|
||||
name: Soak
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
duration_minutes:
|
||||
description: 'Soak duration in minutes'
|
||||
type: number
|
||||
default: 10
|
||||
run_asan:
|
||||
description: 'Run ASan soak in addition to quick soak'
|
||||
type: boolean
|
||||
default: false
|
||||
version:
|
||||
description: 'Version string for build'
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
soak-quick:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
- os: ubuntu-24.04-arm
|
||||
goos: linux
|
||||
goarch: arm64
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
- os: macos-14
|
||||
goos: darwin
|
||||
goarch: arm64
|
||||
cc: cc
|
||||
cxx: c++
|
||||
- os: macos-15-intel
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
cc: cc
|
||||
cxx: c++
|
||||
runs-on: ${{ matrix.os }}
|
||||
# BUG FIX: this was hard-coded to 30, but the caller (nightly-soak.yml)
|
||||
# passes duration_minutes: 240. GitHub killed the job at 30 min, so the
|
||||
# "4h nightly soak" was SILENTLY TRUNCATED to 30 min and never once ran
|
||||
# multi-hour. Budget must always exceed the passed duration; 300 covers
|
||||
# the 240-min nightly with headroom (build + analysis + idle phases).
|
||||
timeout-minutes: 300
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Install deps (Linux)
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev python3 git
|
||||
- name: Build
|
||||
run: scripts/build.sh ${{ inputs.version && format('--version {0}', inputs.version) || '' }} CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
|
||||
- name: Soak (${{ inputs.duration_minutes }} min)
|
||||
run: scripts/soak-test.sh build/c/codebase-memory-mcp ${{ inputs.duration_minutes }}
|
||||
# #581 guard: read-only soak (never reindex/mutate) so any memory growth is
|
||||
# a query-path leak, not WAL/indexing. Reuses the build above.
|
||||
- name: Query-leak soak (#581, read-only)
|
||||
env:
|
||||
CBM_SOAK_MODE: query-leak
|
||||
RESULTS_DIR: soak-results-query-leak
|
||||
run: scripts/soak-test.sh build/c/codebase-memory-mcp ${{ inputs.duration_minutes }} --skip-crash-test
|
||||
- name: Upload metrics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: soak-quick-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
path: soak-results/
|
||||
retention-days: 14
|
||||
- name: Upload query-leak metrics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: soak-query-leak-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
path: soak-results-query-leak/
|
||||
retention-days: 14
|
||||
|
||||
soak-quick-windows:
|
||||
runs-on: windows-latest
|
||||
# BUG FIX (same 30→240 mismatch as soak-quick above): the caller passes
|
||||
# duration_minutes: 240, so a 30-min cap truncated the nightly soak here
|
||||
# too. 300 covers the 240-min nightly with headroom.
|
||||
timeout-minutes: 300
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
||||
with:
|
||||
msystem: CLANG64
|
||||
path-type: inherit
|
||||
install: >-
|
||||
mingw-w64-clang-x86_64-clang
|
||||
mingw-w64-clang-x86_64-zlib
|
||||
mingw-w64-clang-x86_64-python3
|
||||
make
|
||||
git
|
||||
coreutils
|
||||
- name: Build
|
||||
shell: msys2 {0}
|
||||
run: scripts/build.sh ${{ inputs.version && format('--version {0}', inputs.version) || '' }} CC=clang CXX=clang++
|
||||
- name: Soak (${{ inputs.duration_minutes }} min)
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
BIN=build/c/codebase-memory-mcp
|
||||
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
|
||||
scripts/soak-test.sh "$BIN" ${{ inputs.duration_minutes }}
|
||||
# #581 guard on Windows — the platform the bug is reported on.
|
||||
- name: Query-leak soak (#581, read-only)
|
||||
shell: msys2 {0}
|
||||
env:
|
||||
CBM_SOAK_MODE: query-leak
|
||||
RESULTS_DIR: soak-results-query-leak
|
||||
run: |
|
||||
BIN=build/c/codebase-memory-mcp
|
||||
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
|
||||
scripts/soak-test.sh "$BIN" ${{ inputs.duration_minutes }} --skip-crash-test
|
||||
- name: Upload metrics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: soak-quick-windows-amd64
|
||||
path: soak-results/
|
||||
retention-days: 14
|
||||
- name: Upload query-leak metrics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: soak-query-leak-windows-amd64
|
||||
path: soak-results-query-leak/
|
||||
retention-days: 14
|
||||
|
||||
soak-quick-windows-arm64:
|
||||
# Native ARM64 Windows soak (CLANGARM64, no sanitizer — ASan is unavailable
|
||||
# on native ARM64 Windows). Builds from source like soak-quick-windows.
|
||||
runs-on: windows-11-arm
|
||||
timeout-minutes: 300
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
||||
with:
|
||||
msystem: CLANGARM64
|
||||
path-type: inherit
|
||||
install: >-
|
||||
mingw-w64-clang-aarch64-clang
|
||||
mingw-w64-clang-aarch64-zlib
|
||||
mingw-w64-clang-aarch64-python3
|
||||
make
|
||||
git
|
||||
coreutils
|
||||
- name: Build
|
||||
shell: msys2 {0}
|
||||
run: scripts/build.sh ${{ inputs.version && format('--version {0}', inputs.version) || '' }} CC=clang CXX=clang++
|
||||
- name: Soak (${{ inputs.duration_minutes }} min)
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
BIN=build/c/codebase-memory-mcp
|
||||
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
|
||||
scripts/soak-test.sh "$BIN" ${{ inputs.duration_minutes }}
|
||||
# #581 guard on Windows — the platform the bug is reported on.
|
||||
- name: Query-leak soak (#581, read-only)
|
||||
shell: msys2 {0}
|
||||
env:
|
||||
CBM_SOAK_MODE: query-leak
|
||||
RESULTS_DIR: soak-results-query-leak
|
||||
run: |
|
||||
BIN=build/c/codebase-memory-mcp
|
||||
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
|
||||
scripts/soak-test.sh "$BIN" ${{ inputs.duration_minutes }} --skip-crash-test
|
||||
- name: Upload metrics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: soak-quick-windows-arm64
|
||||
path: soak-results/
|
||||
retention-days: 14
|
||||
- name: Upload query-leak metrics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: soak-query-leak-windows-arm64
|
||||
path: soak-results-query-leak/
|
||||
retention-days: 14
|
||||
|
||||
soak-asan:
|
||||
if: ${{ inputs.run_asan }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
- os: ubuntu-24.04-arm
|
||||
goos: linux
|
||||
goarch: arm64
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
- os: macos-14
|
||||
goos: darwin
|
||||
goarch: arm64
|
||||
cc: cc
|
||||
cxx: c++
|
||||
- os: macos-15-intel
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
cc: cc
|
||||
cxx: c++
|
||||
runs-on: ${{ matrix.os }}
|
||||
# ASan soak runs a FIXED 15-min soak (hard-coded below, NOT driven by
|
||||
# inputs.duration_minutes), but the ASan-instrumented build is slow and
|
||||
# leak reporting adds teardown time. 60 keeps the budget comfortably above
|
||||
# the 15-min run so it is never truncated. (Same class of bug as the
|
||||
# soak-quick 30→240 mismatch above — keep the timeout above the run length.)
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Install deps (Linux)
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev python3 git
|
||||
- name: Build (ASan)
|
||||
run: |
|
||||
SANITIZE="-fsanitize=address,undefined -fno-omit-frame-pointer"
|
||||
scripts/build.sh ${{ inputs.version && format('--version {0}', inputs.version) || '' }} CC=${{ matrix.cc }} CXX=${{ matrix.cxx }} EXTRA_CFLAGS="$SANITIZE" EXTRA_LDFLAGS="$SANITIZE"
|
||||
- name: ASan soak (15 min)
|
||||
env:
|
||||
ASAN_OPTIONS: "detect_leaks=1:halt_on_error=0:log_path=soak-results/asan"
|
||||
run: scripts/soak-test.sh build/c/codebase-memory-mcp 15
|
||||
- name: Upload metrics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: soak-asan-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
path: soak-results/
|
||||
retention-days: 14
|
||||
|
||||
soak-asan-windows:
|
||||
if: ${{ inputs.run_asan }}
|
||||
runs-on: windows-latest
|
||||
# FIXED 15-min soak (hard-coded below). MSYS2/Wine + ASan build is the
|
||||
# slowest path; 60 keeps the budget well above the run length so it is
|
||||
# never truncated.
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
||||
with:
|
||||
msystem: CLANG64
|
||||
path-type: inherit
|
||||
install: >-
|
||||
mingw-w64-clang-x86_64-clang
|
||||
mingw-w64-clang-x86_64-zlib
|
||||
mingw-w64-clang-x86_64-python3
|
||||
make
|
||||
git
|
||||
coreutils
|
||||
- name: Build (ASan)
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
SANITIZE="-fsanitize=address,undefined -fno-omit-frame-pointer"
|
||||
scripts/build.sh ${{ inputs.version && format('--version {0}', inputs.version) || '' }} CC=clang CXX=clang++ EXTRA_CFLAGS="$SANITIZE" EXTRA_LDFLAGS="$SANITIZE"
|
||||
- name: ASan soak (15 min, no leak detection)
|
||||
shell: msys2 {0}
|
||||
env:
|
||||
ASAN_OPTIONS: "detect_leaks=0:halt_on_error=0:log_path=soak-results/asan"
|
||||
run: |
|
||||
BIN=build/c/codebase-memory-mcp
|
||||
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
|
||||
scripts/soak-test.sh "$BIN" 15
|
||||
- name: Upload metrics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: soak-asan-windows-amd64
|
||||
path: soak-results/
|
||||
retention-days: 14
|
||||
@@ -0,0 +1,177 @@
|
||||
# Reusable: unit + integration tests on all platforms
|
||||
name: Test
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
skip_perf:
|
||||
description: 'Skip incremental perf tests (phases 2-7)'
|
||||
type: boolean
|
||||
default: true
|
||||
broad_platforms:
|
||||
description: 'Test the broad platform matrix (older glibc + extra OS versions) instead of the core set'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# Emit the platform matrices as JSON. The CORE set is the default (fast,
|
||||
# unchanged); the BROAD set adds extra free runners (older glibc /
|
||||
# additional OS versions) for a wider "does it build everywhere" picture.
|
||||
setup-matrix:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
unix: ${{ steps.set.outputs.unix }}
|
||||
windows: ${{ steps.set.outputs.windows }}
|
||||
steps:
|
||||
- name: Compute matrices
|
||||
id: set
|
||||
env:
|
||||
BROAD: ${{ inputs.broad_platforms }}
|
||||
run: |
|
||||
CORE_UNIX='[
|
||||
{"os":"ubuntu-latest","cc":"gcc","cxx":"g++"},
|
||||
{"os":"ubuntu-24.04-arm","cc":"gcc","cxx":"g++"},
|
||||
{"os":"macos-14","cc":"cc","cxx":"c++"},
|
||||
{"os":"macos-15-intel","cc":"cc","cxx":"c++"}
|
||||
]'
|
||||
# Broad matrix legs are REQUIRED gates (no optional/continue-on-error
|
||||
# escape hatches): the release dry run must be all-green, every platform.
|
||||
BROAD_UNIX='[
|
||||
{"os":"ubuntu-22.04","cc":"gcc","cxx":"g++"},
|
||||
{"os":"ubuntu-22.04-arm","cc":"gcc","cxx":"g++"},
|
||||
{"os":"macos-15","cc":"cc","cxx":"c++"}
|
||||
]'
|
||||
# Each Windows leg pins the msys2 environment + package arch to the
|
||||
# RUNNER architecture so the build is native, never emulated:
|
||||
# x86-64 runners -> CLANG64 (mingw-w64-clang-x86_64-*)
|
||||
# ARM64 runner -> CLANGARM64 (mingw-w64-clang-aarch64-*)
|
||||
# windows-11-arm previously used the x86-64 CLANG64 toolchain, so its
|
||||
# binary ran under Windows-on-ARM x86-64 emulation and ASan's function
|
||||
# interception crashed (interception_win: unhandled instruction). With
|
||||
# the native ARM64 toolchain ASan instruments native ARM64 code, so it
|
||||
# is a real (non-optional) gate, not a tolerated emulated-flake.
|
||||
CORE_WIN='[{"os":"windows-latest","msystem":"CLANG64","pkg":"x86_64"}]'
|
||||
BROAD_WIN='[{"os":"windows-2025","msystem":"CLANG64","pkg":"x86_64"},{"os":"windows-11-arm","msystem":"CLANGARM64","pkg":"aarch64"}]'
|
||||
if [ "$BROAD" = "true" ]; then
|
||||
UNIX=$(jq -cn --argjson a "$CORE_UNIX" --argjson b "$BROAD_UNIX" '$a + $b')
|
||||
WIN=$(jq -cn --argjson a "$CORE_WIN" --argjson b "$BROAD_WIN" '$a + $b')
|
||||
else
|
||||
UNIX=$(jq -cn --argjson a "$CORE_UNIX" '$a')
|
||||
WIN=$(jq -cn --argjson a "$CORE_WIN" '$a')
|
||||
fi
|
||||
echo "unix={\"include\":$UNIX}" >> "$GITHUB_OUTPUT"
|
||||
echo "windows={\"include\":$WIN}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
test-unix:
|
||||
needs: setup-matrix
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.setup-matrix.outputs.unix) }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
# Broad-only legs (extra OS versions) are informational: visible but
|
||||
# non-blocking, so a flaky/less-common runner can't block a release.
|
||||
continue-on-error: ${{ matrix.optional == true }}
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Install deps (Ubuntu)
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
|
||||
|
||||
- name: Test
|
||||
run: scripts/test.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
|
||||
env:
|
||||
CBM_SKIP_PERF: ${{ inputs.skip_perf && '1' || '' }}
|
||||
|
||||
test-tsan:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Install deps (Ubuntu)
|
||||
run: sudo apt-get update && sudo apt-get install -y clang zlib1g-dev
|
||||
|
||||
- name: ThreadSanitizer tests
|
||||
run: make -f Makefile.cbm test-tsan CC=clang CXX=clang++
|
||||
|
||||
test-windows:
|
||||
needs: setup-matrix
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.setup-matrix.outputs.windows) }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.optional == true }}
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
||||
with:
|
||||
msystem: ${{ matrix.msystem }}
|
||||
path-type: inherit
|
||||
install: >-
|
||||
mingw-w64-clang-${{ matrix.pkg }}-clang
|
||||
mingw-w64-clang-${{ matrix.pkg }}-compiler-rt
|
||||
mingw-w64-clang-${{ matrix.pkg }}-zlib
|
||||
make
|
||||
git
|
||||
|
||||
- name: Test
|
||||
shell: msys2 {0}
|
||||
# AddressSanitizer is unavailable on native ARM64 Windows (LLVM ships no
|
||||
# libclang_rt.asan for aarch64-w64-windows-gnu) and cannot intercept the
|
||||
# system DLLs under x86-64 emulation either, so windows-11-arm runs the
|
||||
# native ARM64 build with SANITIZE= (no sanitizer) — still a real
|
||||
# functional gate. ASan/UBSan coverage comes from the other 9 legs,
|
||||
# including native-ARM Linux/macOS. x86-64 Windows keeps full sanitizers.
|
||||
run: scripts/test.sh CC=clang CXX=clang++ ${{ matrix.os == 'windows-11-arm' && 'SANITIZE=' || '' }}
|
||||
env:
|
||||
CBM_SKIP_PERF: ${{ inputs.skip_perf && '1' || '' }}
|
||||
|
||||
# Windows product-surface regression guards. Distinct from test-windows above
|
||||
# (the sanitizer C suite): these drive a real product binary + embedded HTTP UI
|
||||
# over stdio / CLI / HTTP and fail if a Windows bug already fixed on main comes
|
||||
# back -- non-ASCII repo paths dropping definitions (#636/#357, fixed by #700),
|
||||
# the PreToolUse hook augmenter no-op on drive-letter cwd (#618, fixed by #619),
|
||||
# the UI directory picker not enumerating drives (#548, roots field), and non-ASCII
|
||||
# CLI arguments being mangled by the narrow-argv main() (#423/#20, fixed by the
|
||||
# wide-argv entrypoint that reads GetCommandLineW).
|
||||
test-windows-guards:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
||||
with:
|
||||
msystem: CLANG64
|
||||
path-type: inherit
|
||||
install: >-
|
||||
mingw-w64-clang-x86_64-clang
|
||||
mingw-w64-clang-x86_64-zlib
|
||||
make
|
||||
git
|
||||
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Build product binary with embedded UI
|
||||
shell: msys2 {0}
|
||||
# --with-ui builds the frontend (npm) and embeds it, so the drive-picker
|
||||
# guard's HTTP UI is available. Functional gate only (no sanitizers).
|
||||
run: scripts/build.sh --with-ui CC=clang CXX=clang++
|
||||
|
||||
- name: Windows regression guards (#636/#357, #618, #548, #423/#20)
|
||||
shell: pwsh
|
||||
# -GuardsOnly runs the four green guards and gates on them. The runner sets
|
||||
# CBM_INDEX_SUPERVISOR=0 for determinism, but the non-ASCII CLI guard drops that
|
||||
# override so it exercises the real supervisor->worker spawn (where #423/#20's
|
||||
# second half lives); the other guards test path/hook/drive fixes in-process.
|
||||
run: ./scripts/test-windows.ps1 -GuardsOnly -Binary build/c/codebase-memory-mcp.exe
|
||||
@@ -0,0 +1,84 @@
|
||||
# Bug-reproduction board — runs the cumulative reproduce-first suite (RED by
|
||||
# design, one case per open bug) across every platform on a chosen branch.
|
||||
#
|
||||
# This is the "test many bug vectors on many platforms at once" harness. It is
|
||||
# NON-GATING: dispatch-only, never a required check, so a red board never blocks
|
||||
# a merge. Dispatch against a feature branch with:
|
||||
# gh workflow run bug-repro.yml --ref <branch> -f platforms=all
|
||||
name: Bug Repro Board
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
platforms:
|
||||
description: 'Which platforms to run the repro board on'
|
||||
type: choice
|
||||
options: ['all', 'linux', 'macos', 'windows']
|
||||
default: 'all'
|
||||
# Iteration convenience: any push to a qa/** branch runs the board straight
|
||||
# from that branch's own copy of this file (no main merge needed). Non-gating.
|
||||
push:
|
||||
# Exclude the dedicated lane branches so they only run their own workflow
|
||||
# (fast-repro / soak / smoke), not the full board too.
|
||||
branches: ['qa/**', '!qa/fast-**', '!qa/soak-**', '!qa/smoke-**']
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
repro-unix:
|
||||
if: ${{ github.event_name == 'push' || inputs.platforms == 'all' || inputs.platforms == 'linux' || inputs.platforms == 'macos' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
group: linux
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
- os: ubuntu-24.04-arm
|
||||
group: linux
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
- os: macos-14
|
||||
group: macos
|
||||
cc: cc
|
||||
cxx: c++
|
||||
- os: macos-15-intel
|
||||
group: macos
|
||||
cc: cc
|
||||
cxx: c++
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Install deps (Ubuntu)
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
|
||||
|
||||
- name: Run bug-reproduction board
|
||||
if: ${{ github.event_name == 'push' || inputs.platforms == 'all' || inputs.platforms == matrix.group }}
|
||||
run: scripts/repro.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
|
||||
|
||||
repro-windows:
|
||||
if: ${{ github.event_name == 'push' || inputs.platforms == 'all' || inputs.platforms == 'windows' }}
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
||||
with:
|
||||
msystem: CLANG64
|
||||
path-type: inherit
|
||||
install: >-
|
||||
mingw-w64-clang-x86_64-clang
|
||||
mingw-w64-clang-x86_64-compiler-rt
|
||||
mingw-w64-clang-x86_64-zlib
|
||||
make
|
||||
git
|
||||
|
||||
- name: Run bug-reproduction board
|
||||
shell: msys2 {0}
|
||||
run: scripts/repro.sh CC=clang CXX=clang++
|
||||
@@ -0,0 +1,43 @@
|
||||
name: CodeQL SAST
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: codeql-${{ github.ref }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
runs-on: ubuntu-latest
|
||||
# CodeQL needs security-events: write to upload results; scoped to this job so
|
||||
# the workflow's top-level token stays read-only (Scorecard TokenPermissions).
|
||||
permissions:
|
||||
security-events: write
|
||||
actions: read
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Install build dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
with:
|
||||
languages: c-cpp
|
||||
build-mode: manual
|
||||
|
||||
- name: Build for CodeQL analysis
|
||||
run: scripts/build.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||
with:
|
||||
category: "/language:c-cpp"
|
||||
@@ -0,0 +1,46 @@
|
||||
# DCO enforcement — every commit on every branch must carry a Signed-off-by
|
||||
# trailer matching its author (Developer Certificate of Origin 1.1, see DCO).
|
||||
# Runs on all pushes and pull requests; scripts/check-dco.sh holds the rules.
|
||||
name: DCO
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: dco-${{ github.ref }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
dco:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check Signed-off-by on all new commits
|
||||
env:
|
||||
EVENT: ${{ github.event_name }}
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
PUSH_BEFORE: ${{ github.event.before }}
|
||||
PUSH_AFTER: ${{ github.event.after }}
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
run: |
|
||||
# Ranges always cover only the NEW commits of this event, so history
|
||||
# predating DCO adoption is naturally exempt.
|
||||
if [ "$EVENT" = "pull_request" ]; then
|
||||
RANGE="$BASE_SHA..$HEAD_SHA"
|
||||
elif [ "$PUSH_BEFORE" = "0000000000000000000000000000000000000000" ]; then
|
||||
# New branch: check the commits not on the default branch
|
||||
RANGE="origin/$DEFAULT_BRANCH..$PUSH_AFTER"
|
||||
else
|
||||
RANGE="$PUSH_BEFORE..$PUSH_AFTER"
|
||||
fi
|
||||
echo "checking range: $RANGE"
|
||||
scripts/check-dco.sh "$RANGE"
|
||||
@@ -0,0 +1,85 @@
|
||||
# Manual trigger: test everything before pushing a release.
|
||||
# Each step can be skipped for faster iteration.
|
||||
#
|
||||
# Pipeline: lint → test → build → smoke/soak (sequential chain)
|
||||
# Security: security-static + codeql-gate (independent island)
|
||||
#
|
||||
# Security does NOT block lint/test/build/smoke/soak. All jobs must pass
|
||||
# for the overall workflow to be green.
|
||||
name: Dry Run
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
skip_lint:
|
||||
description: 'Skip lint (cppcheck + clang-format)'
|
||||
type: boolean
|
||||
default: false
|
||||
skip_tests:
|
||||
description: 'Skip unit/integration tests'
|
||||
type: boolean
|
||||
default: false
|
||||
skip_builds:
|
||||
description: 'Skip build + smoke'
|
||||
type: boolean
|
||||
default: false
|
||||
soak_level:
|
||||
description: 'Soak: full (quick+asan), quick (10min), none'
|
||||
type: choice
|
||||
options: ['full', 'quick', 'none']
|
||||
default: 'quick'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# ── Security (independent island — does not block main pipeline) ──
|
||||
security:
|
||||
uses: ./.github/workflows/_security.yml
|
||||
secrets: inherit
|
||||
|
||||
# ── Lint (cppcheck + clang-format) ────────────────────────────
|
||||
lint:
|
||||
if: ${{ inputs.skip_lint != true }}
|
||||
uses: ./.github/workflows/_lint.yml
|
||||
|
||||
# ── Tests (all platforms, perf tests skipped on CI) ────────────
|
||||
test:
|
||||
needs: [lint]
|
||||
if: ${{ inputs.skip_tests != true && !cancelled() && (needs.lint.result == 'success' || needs.lint.result == 'skipped') }}
|
||||
uses: ./.github/workflows/_test.yml
|
||||
with:
|
||||
skip_perf: true
|
||||
broad_platforms: true
|
||||
|
||||
# ── Build all platforms ────────────────────────────────────────
|
||||
build:
|
||||
if: ${{ inputs.skip_builds != true && !cancelled() && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
|
||||
needs: [test]
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
attestations: write
|
||||
uses: ./.github/workflows/_build.yml
|
||||
with:
|
||||
attest: false
|
||||
|
||||
# ── Smoke test every binary ────────────────────────────────────
|
||||
# Run unless builds were skipped or a build leg FAILED. Every build leg
|
||||
# (including the macos-15-intel darwin-amd64 binary) is blocking now, so a
|
||||
# failed/missing platform binary correctly stops smoke — see _build.yml.
|
||||
smoke:
|
||||
if: ${{ inputs.skip_builds != true && !cancelled() && needs.build.result != 'failure' && needs.build.result != 'skipped' }}
|
||||
needs: [build]
|
||||
uses: ./.github/workflows/_smoke.yml
|
||||
with:
|
||||
broad_platforms: true
|
||||
|
||||
# ── Soak tests (optional, parallel with smoke) ────────────────
|
||||
soak:
|
||||
if: ${{ inputs.soak_level != 'none' && !cancelled() && needs.build.result != 'failure' && needs.build.result != 'skipped' }}
|
||||
needs: [build]
|
||||
uses: ./.github/workflows/_soak.yml
|
||||
with:
|
||||
duration_minutes: 10
|
||||
run_asan: ${{ inputs.soak_level == 'full' }}
|
||||
@@ -0,0 +1,37 @@
|
||||
# Fast repro lane — single platform, NO sanitizers — for quick fix-iteration
|
||||
# feedback (the red-count after a fix) without waiting ~15 min for the full
|
||||
# 5-platform ASan board. The full bug-repro.yml board remains the comprehensive
|
||||
# all-platform check; this is just the fast inner loop.
|
||||
#
|
||||
# Trigger: workflow_dispatch, or push to a qa/fast-** branch. Non-gating.
|
||||
name: Fast Repro
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
suites:
|
||||
description: 'Comma list of suite-name substrings to run (empty = all)'
|
||||
type: string
|
||||
default: ''
|
||||
push:
|
||||
branches: ['qa/fast-**']
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
fast:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Install deps
|
||||
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
|
||||
- name: test-repro (single platform, ASan; CBM_REPRO_ONLY filters suites)
|
||||
env:
|
||||
# Optionally narrow to specific suites for a fast targeted check, e.g.
|
||||
# CBM_REPRO_ONLY="repro_invariant_enclosing_parity,repro_grammar_systems".
|
||||
# Empty = run all. (No-sanitizer builds crash on some suites, so ASan
|
||||
# stays on; the single-platform run is the speedup vs the 5-platform board.)
|
||||
CBM_REPRO_ONLY: ${{ github.event.inputs.suites }}
|
||||
run: scripts/repro.sh CC=gcc CXX=g++
|
||||
@@ -0,0 +1,59 @@
|
||||
name: Issue area labeler
|
||||
|
||||
# Adds area labels to new/edited issues based on keyword regexes in
|
||||
# .github/issue-labeler.yml. Additive only (addLabels never removes): it
|
||||
# never touches a label a maintainer set by hand. Base bug/enhancement
|
||||
# labels already come from the issue forms.
|
||||
#
|
||||
# Implemented with first-party actions/github-script (not a third-party
|
||||
# labeler action) so every pattern is compiled as a JavaScript RegExp with
|
||||
# the `i` flag applied centrally — inline `(?i)` groups are PCRE-only and
|
||||
# threw `SyntaxError: Invalid group` on every issue (#764). A pattern that
|
||||
# fails to compile now fails the run loudly instead of silently no-oping.
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const text = [context.payload.issue.title, context.payload.issue.body]
|
||||
.filter(Boolean).join('\n');
|
||||
const rules = [];
|
||||
const bad = [];
|
||||
// Config format: one rule per line — "label": 'regex' (see the
|
||||
// header comment in .github/issue-labeler.yml).
|
||||
for (const line of fs.readFileSync('.github/issue-labeler.yml', 'utf8').split('\n')) {
|
||||
const m = line.match(/^"([^"]+)":\s*'(.*)'\s*$/);
|
||||
if (!m) continue;
|
||||
try { rules.push({ label: m[1], re: new RegExp(m[2], 'i') }); }
|
||||
catch (e) { bad.push(`${m[1]}: ${e.message}`); }
|
||||
}
|
||||
if (rules.length === 0 && bad.length === 0) {
|
||||
core.setFailed('no labeling rules parsed from .github/issue-labeler.yml');
|
||||
return;
|
||||
}
|
||||
const labels = rules.filter(r => r.re.test(text)).map(r => r.label);
|
||||
if (labels.length) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels,
|
||||
});
|
||||
}
|
||||
core.info(`matched: ${labels.join(', ') || '(none)'}`);
|
||||
if (bad.length) core.setFailed(`invalid label regex(es): ${bad.join('; ')}`);
|
||||
@@ -0,0 +1,21 @@
|
||||
name: Label actions
|
||||
|
||||
# Posts a templated comment when a maintainer adds certain labels.
|
||||
# Behavior is configured in .github/label-actions.yml. Issues only.
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled, unlabeled]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: dessant/label-actions@65225c179d3b2502f6eda7b3d15101a3f412366b # v5.0.3
|
||||
with:
|
||||
process-only: issues
|
||||
@@ -0,0 +1,22 @@
|
||||
# Weekly soak test: 4h sustained load + ASan leak detection
|
||||
name: Nightly Soak
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * 0' # Every Sunday at 2am UTC
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
duration_minutes:
|
||||
description: 'Soak duration in minutes (default: 240 = 4h)'
|
||||
type: number
|
||||
default: 240
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
soak:
|
||||
uses: ./.github/workflows/_soak.yml
|
||||
with:
|
||||
duration_minutes: ${{ inputs.duration_minutes || 240 }}
|
||||
run_asan: true
|
||||
@@ -0,0 +1,44 @@
|
||||
# Deploy the static site in /docs to GitHub Pages.
|
||||
#
|
||||
# Replaces the legacy "Deploy from a branch" (Jekyll) build, which ran on
|
||||
# EVERY push to main and failed because Jekyll's Liquid parser choked on the
|
||||
# planning docs under /docs (e.g. `Unknown tag 'data'` in EVALUATION_PLAN.md).
|
||||
# /docs is a hand-written static site (index.html + robots/sitemap/llms.txt),
|
||||
# so we upload it as-is — no Jekyll — and only run when the site changes.
|
||||
#
|
||||
# Requires the repo Pages source to be set to "GitHub Actions"
|
||||
# (Settings → Pages → Build and deployment → Source: GitHub Actions).
|
||||
name: Deploy Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- '.github/workflows/pages.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# One deployment at a time; never cancel an in-progress publish.
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
||||
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
||||
with:
|
||||
path: ./docs
|
||||
- id: deployment
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
@@ -0,0 +1,140 @@
|
||||
# PR validation: security gates + lint + full test suite. Builds, smoke and
|
||||
# soak stay maintainer-driven via the dry-run workflow_dispatch. Branch
|
||||
# protection requires `dco` + `ci-ok` — a single stable summary context that
|
||||
# fails unless every PR stage succeeded.
|
||||
name: PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
# A new push to the PR supersedes the running validation — cancel it
|
||||
# instead of stacking zombie pipelines.
|
||||
concurrency:
|
||||
group: pr-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
jobs:
|
||||
security:
|
||||
uses: ./.github/workflows/_security.yml
|
||||
secrets: inherit
|
||||
|
||||
lint:
|
||||
uses: ./.github/workflows/_lint.yml
|
||||
|
||||
test:
|
||||
needs: [lint]
|
||||
if: ${{ !cancelled() && needs.lint.result == 'success' }}
|
||||
uses: ./.github/workflows/_test.yml
|
||||
with:
|
||||
# Perf assertions are timing-sensitive on shared runners; they stay in
|
||||
# dry runs and releases where a flaky red does not block a merge.
|
||||
skip_perf: true
|
||||
|
||||
# ── Which files changed? Gate the (heavier) build+smoke on product code so
|
||||
# docs / CI / test-only PRs stay fast. ──
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
product: ${{ steps.f.outputs.product }}
|
||||
steps:
|
||||
- name: Detect product-code changes
|
||||
id: f
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR: ${{ github.event.pull_request.number }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
FILES=$(gh pr diff "$PR" --repo "$REPO" --name-only)
|
||||
printf '%s\n' "$FILES"
|
||||
if printf '%s\n' "$FILES" | grep -qE '^(src/|internal/|scripts/build\.sh|scripts/smoke-test\.sh|scripts/env\.sh|Makefile\.cbm)'; then
|
||||
echo "product=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "product=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# ── Light smoke: build the PRODUCTION binary and run the core smoke on the
|
||||
# three RELIABLE NATIVE platforms. Catches built-binary regressions at PR
|
||||
# time (e.g. the Windows CreateProcess argv-quoting class that previously
|
||||
# only surfaced in the release dry run). The full broad/emulated smoke stays
|
||||
# in the dry run. Only product-code PRs pay this; every leg gates.
|
||||
# SMOKE_DOWNLOAD_URL is intentionally unset — the download/update phases of
|
||||
# smoke-test.sh self-skip; the core index/search/trace phases still run. ──
|
||||
pr-smoke:
|
||||
needs: [changes]
|
||||
if: ${{ !cancelled() && needs.changes.outputs.product == 'true' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-14, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Install deps (Ubuntu)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
|
||||
|
||||
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
||||
if: matrix.os == 'windows-latest'
|
||||
with:
|
||||
msystem: CLANG64
|
||||
path-type: inherit
|
||||
install: >-
|
||||
mingw-w64-clang-x86_64-clang
|
||||
mingw-w64-clang-x86_64-zlib
|
||||
mingw-w64-clang-x86_64-python3
|
||||
make
|
||||
coreutils
|
||||
|
||||
- name: Build prod + smoke (Ubuntu)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
scripts/build.sh CC=gcc CXX=g++
|
||||
scripts/smoke-test.sh "$(pwd)/build/c/codebase-memory-mcp"
|
||||
|
||||
- name: Build prod + smoke (macOS)
|
||||
if: matrix.os == 'macos-14'
|
||||
run: |
|
||||
scripts/build.sh CC=cc CXX=c++
|
||||
codesign --sign - --force build/c/codebase-memory-mcp
|
||||
scripts/smoke-test.sh "$(pwd)/build/c/codebase-memory-mcp"
|
||||
|
||||
- name: Build prod + smoke (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
scripts/build.sh CC=clang CXX=clang++
|
||||
BIN="$(pwd)/build/c/codebase-memory-mcp"
|
||||
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
|
||||
scripts/smoke-test.sh "$BIN"
|
||||
|
||||
ci-ok:
|
||||
# The one required context (besides dco) — fails unless every PR stage
|
||||
# succeeded, so matrix renames can never silently deadlock merges. `skipped`
|
||||
# is OK: pr-smoke skips on docs/CI/test-only PRs (changes.product == false).
|
||||
needs: [security, lint, test, changes, pr-smoke]
|
||||
if: ${{ always() }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: All PR stages must have succeeded
|
||||
env:
|
||||
RESULTS: ${{ toJSON(needs) }}
|
||||
run: |
|
||||
echo "$RESULTS" | python3 -c "
|
||||
import json, sys
|
||||
needs = json.load(sys.stdin)
|
||||
bad = {k: v['result'] for k, v in needs.items() if v['result'] not in ('success', 'skipped')}
|
||||
if bad:
|
||||
print('CI NOT OK:', bad)
|
||||
sys.exit(1)
|
||||
print('CI OK:', ', '.join(needs))
|
||||
"
|
||||
@@ -0,0 +1,416 @@
|
||||
# Release pipeline: lint → test → build → smoke/soak → draft → verify → publish
|
||||
#
|
||||
# Security (security-static + codeql-gate) starts immediately and runs in
|
||||
# parallel with lint/test/build/smoke/soak. It only blocks the final verify
|
||||
# step — everything else proceeds independently.
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Release version (e.g. v0.8.0)"
|
||||
required: true
|
||||
type: string
|
||||
release_notes:
|
||||
description: "Release notes (optional — auto-generated from commits if empty)"
|
||||
required: false
|
||||
type: string
|
||||
replace:
|
||||
description: "Replace existing release if it exists"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
soak_level:
|
||||
description: 'Soak: full (quick+asan), quick (10min), none'
|
||||
type: choice
|
||||
options: ['full', 'quick', 'none']
|
||||
default: 'quick'
|
||||
skip_perf:
|
||||
description: "Skip performance tests (use when no pipeline logic changed)"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# ── Security (starts immediately, blocks only verify) ───────────
|
||||
security:
|
||||
uses: ./.github/workflows/_security.yml
|
||||
secrets: inherit
|
||||
|
||||
# ── 1. Lint (cppcheck + clang-format) ───────────────────────────
|
||||
lint:
|
||||
uses: ./.github/workflows/_lint.yml
|
||||
|
||||
# ── 2. Tests (all platforms, full suite for release) ────────────
|
||||
test:
|
||||
needs: [lint]
|
||||
uses: ./.github/workflows/_test.yml
|
||||
with:
|
||||
skip_perf: ${{ inputs.skip_perf }}
|
||||
broad_platforms: true
|
||||
|
||||
# ── 3. Build all platforms ──────────────────────────────────────
|
||||
build:
|
||||
needs: [test]
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
attestations: write
|
||||
uses: ./.github/workflows/_build.yml
|
||||
with:
|
||||
version: ${{ inputs.version }}
|
||||
attest: true
|
||||
|
||||
# ── 4. Smoke test every binary ──────────────────────────────────
|
||||
smoke:
|
||||
needs: [build]
|
||||
uses: ./.github/workflows/_smoke.yml
|
||||
with:
|
||||
broad_platforms: true
|
||||
|
||||
# ── 5. Soak tests ──────────────────────────────────────────────
|
||||
soak:
|
||||
if: ${{ inputs.soak_level != 'none' }}
|
||||
needs: [build]
|
||||
uses: ./.github/workflows/_soak.yml
|
||||
with:
|
||||
duration_minutes: 10
|
||||
run_asan: ${{ inputs.soak_level == 'full' }}
|
||||
version: ${{ inputs.version }}
|
||||
|
||||
# ── 6. Create DRAFT release ────────────────────────────────────
|
||||
release-draft:
|
||||
needs: [smoke, soak]
|
||||
if: ${{ !cancelled() && !failure() }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
- name: List artifacts
|
||||
run: ls -la *.tar.gz *.zip
|
||||
|
||||
- name: Generate checksums
|
||||
run: sha256sum *.tar.gz *.zip > checksums.txt
|
||||
|
||||
- name: Attest checksum provenance
|
||||
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
|
||||
with:
|
||||
subject-path: checksums.txt
|
||||
|
||||
- name: Generate SBOM
|
||||
run: |
|
||||
python3 -c "
|
||||
import json, glob, os
|
||||
n_grammars = len([d for d in glob.glob('internal/cbm/vendored/grammars/*') if os.path.isdir(d)])
|
||||
sbom = {
|
||||
'spdxVersion': 'SPDX-2.3',
|
||||
'dataLicense': 'CC0-1.0',
|
||||
'SPDXID': 'SPDXRef-DOCUMENT',
|
||||
'name': 'codebase-memory-mcp-${{ inputs.version }}',
|
||||
'documentNamespace': 'https://github.com/DeusData/codebase-memory-mcp/releases/${{ inputs.version }}',
|
||||
'creationInfo': {
|
||||
'created': '$(date -u +%Y-%m-%dT%H:%M:%SZ)',
|
||||
'creators': ['Tool: codebase-memory-mcp-release-pipeline']
|
||||
},
|
||||
'packages': [
|
||||
{'SPDXID': 'SPDXRef-Package-sqlite3', 'name': 'sqlite3', 'versionInfo': '3.51.3', 'licenseDeclared': 'blessing', 'downloadLocation': 'https://sqlite.org', 'filesAnalyzed': False},
|
||||
{'SPDXID': 'SPDXRef-Package-yyjson', 'name': 'yyjson', 'versionInfo': '0.12.0', 'licenseDeclared': 'MIT', 'downloadLocation': 'https://github.com/ibireme/yyjson', 'filesAnalyzed': False},
|
||||
{'SPDXID': 'SPDXRef-Package-mimalloc', 'name': 'mimalloc', 'versionInfo': '3.3.2', 'licenseDeclared': 'MIT', 'downloadLocation': 'https://github.com/microsoft/mimalloc', 'filesAnalyzed': False},
|
||||
{'SPDXID': 'SPDXRef-Package-xxhash', 'name': 'xxhash', 'versionInfo': '0.8.3', 'licenseDeclared': 'BSD-2-Clause', 'downloadLocation': 'https://github.com/Cyan4973/xxHash', 'filesAnalyzed': False},
|
||||
{'SPDXID': 'SPDXRef-Package-tre', 'name': 'tre', 'versionInfo': '0.8.0', 'licenseDeclared': 'BSD-2-Clause', 'downloadLocation': 'https://github.com/laurikari/tre', 'filesAnalyzed': False},
|
||||
{'SPDXID': 'SPDXRef-Package-tree-sitter', 'name': 'tree-sitter', 'versionInfo': '0.24.4', 'licenseDeclared': 'MIT', 'downloadLocation': 'https://github.com/tree-sitter/tree-sitter', 'filesAnalyzed': False},
|
||||
{'SPDXID': 'SPDXRef-Package-lz4', 'name': 'lz4', 'versionInfo': '1.10.0', 'licenseDeclared': 'BSD-2-Clause', 'downloadLocation': 'https://github.com/lz4/lz4', 'filesAnalyzed': False},
|
||||
{'SPDXID': 'SPDXRef-Package-zstd', 'name': 'zstd', 'versionInfo': '1.5.7', 'licenseDeclared': 'BSD-3-Clause', 'downloadLocation': 'https://github.com/facebook/zstd', 'filesAnalyzed': False},
|
||||
{'SPDXID': 'SPDXRef-Package-simplecpp', 'name': 'simplecpp', 'versionInfo': '1.x', 'licenseDeclared': '0BSD', 'downloadLocation': 'https://github.com/danmar/simplecpp', 'filesAnalyzed': False},
|
||||
{'SPDXID': 'SPDXRef-Package-verstable', 'name': 'verstable', 'versionInfo': '2.2.1', 'licenseDeclared': 'MIT', 'downloadLocation': 'https://github.com/JacksonAllan/Verstable', 'filesAnalyzed': False},
|
||||
{'SPDXID': 'SPDXRef-Package-wyhash', 'name': 'wyhash', 'versionInfo': 'final-4.3', 'licenseDeclared': 'Unlicense', 'downloadLocation': 'https://github.com/wangyi-fudan/wyhash', 'filesAnalyzed': False},
|
||||
{'SPDXID': 'SPDXRef-Package-nomic-embed-code', 'name': 'nomic-embed-code-token-embeddings', 'versionInfo': '1.0', 'licenseDeclared': 'Apache-2.0', 'downloadLocation': 'https://huggingface.co/nomic-ai/nomic-embed-code', 'filesAnalyzed': False, 'comment': 'Derived int8 token embeddings; see vendored/nomic/NOTICE'},
|
||||
{'SPDXID': 'SPDXRef-Package-tree-sitter-grammars', 'name': 'tree-sitter-grammars-aggregate', 'versionInfo': f'{n_grammars}-grammars', 'licenseDeclared': 'MIT', 'downloadLocation': 'NOASSERTION', 'filesAnalyzed': False, 'comment': f'Aggregate of {n_grammars} vendored tree-sitter grammars, compiled statically. Predominantly MIT; non-MIT families present include CC0-1.0 (clojure, fennel), Apache-2.0 (elixir, erlang, gleam, hcl, ini, jinja2, just, pkl, sway, wit), ISC (pine, templ), Unlicense (fish); first-party grammars (c) DeusData, MIT. This summary is non-exhaustive; the authoritative complete per-grammar license list is internal/cbm/vendored/grammars/MANIFEST.md, and full license texts ship in THIRD_PARTY_NOTICES.md inside each archive.'}
|
||||
]
|
||||
}
|
||||
json.dump(sbom, open('sbom.json', 'w'), indent=2)
|
||||
"
|
||||
|
||||
- name: Attest SBOM
|
||||
uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0
|
||||
with:
|
||||
subject-path: '*.tar.gz'
|
||||
sbom-path: 'sbom.json'
|
||||
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||
|
||||
- name: Sign artifacts
|
||||
run: |
|
||||
for f in *.tar.gz *.zip checksums.txt; do
|
||||
cosign sign-blob --yes --bundle "${f}.bundle" "$f"
|
||||
done
|
||||
|
||||
- name: Delete existing release
|
||||
if: ${{ inputs.replace }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: gh release delete "$VERSION" --yes --cleanup-tag || true
|
||||
|
||||
- name: Create tag
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
# Tag the DISPATCHED sha, not the checkout HEAD: the artifacts were
|
||||
# built from github.sha, and a commit pushed to main mid-run must not
|
||||
# move the release tag. (A floating HEAD also broke the 0.8.0 release:
|
||||
# HEAD had become a commit touching .github/workflows/, and the App
|
||||
# token may not create refs pointing at workflow-modifying commits.)
|
||||
run: |
|
||||
git tag -f "$VERSION" "$GITHUB_SHA"
|
||||
git push origin "$VERSION" --force
|
||||
|
||||
- uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v2
|
||||
with:
|
||||
tag_name: ${{ inputs.version }}
|
||||
draft: true
|
||||
files: |
|
||||
*.tar.gz
|
||||
*.zip
|
||||
checksums.txt
|
||||
sbom.json
|
||||
*.bundle
|
||||
body: ${{ inputs.release_notes || '' }}
|
||||
generate_release_notes: ${{ inputs.release_notes == '' }}
|
||||
|
||||
# ── 7. Verify + Publish (requires security gate) ───────────────
|
||||
verify:
|
||||
needs: [release-draft, security]
|
||||
# Explicit condition: a skipped ancestor (soak with soak_level=none) must
|
||||
# not skip the publish chain — only real failures/cancellations block it.
|
||||
if: ${{ !cancelled() && !failure() && needs.release-draft.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download and extract release binaries
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
mkdir -p assets binaries
|
||||
gh release download "$VERSION" --dir assets --repo "$GITHUB_REPOSITORY" --pattern '*.tar.gz' --pattern '*.zip'
|
||||
for f in assets/*.tar.gz; do
|
||||
NAME=$(basename "$f" .tar.gz)
|
||||
tar -xzf "$f" -C binaries/ 2>/dev/null || true
|
||||
[ -f binaries/codebase-memory-mcp ] && mv binaries/codebase-memory-mcp "binaries/${NAME}"
|
||||
done
|
||||
for f in assets/*.zip; do
|
||||
NAME=$(basename "$f" .zip)
|
||||
unzip -o "$f" -d binaries/ 2>/dev/null || true
|
||||
[ -f binaries/codebase-memory-mcp.exe ] && mv binaries/codebase-memory-mcp.exe "binaries/${NAME}.exe"
|
||||
done
|
||||
cp install.sh binaries/ 2>/dev/null || true
|
||||
cp install.ps1 binaries/ 2>/dev/null || true
|
||||
ls -la binaries/
|
||||
|
||||
- name: Security audits on all release files
|
||||
run: |
|
||||
# Audit every file that ships in the release archives — binaries,
|
||||
# install scripts, LICENSE, and any future companion files.
|
||||
# security-strings.sh detects file type and applies binary-only
|
||||
# rules (URL allowlist, dangerous-command detection) only to real
|
||||
# binaries; for shell scripts it still runs credential and base64
|
||||
# pattern audits.
|
||||
for f in binaries/*; do
|
||||
[ -f "$f" ] || continue
|
||||
echo "--- Auditing: $(basename "$f") ---"
|
||||
scripts/security-strings.sh "$f"
|
||||
done
|
||||
|
||||
- name: VirusTotal scan
|
||||
uses: crazy-max/ghaction-virustotal@936d8c5c00afe97d3d9a1af26d017cfdf26800a2 # v5.0.0
|
||||
id: virustotal
|
||||
with:
|
||||
vt_api_key: ${{ secrets.VIRUS_TOTAL_SCANNER_API_KEY }}
|
||||
files: binaries/*
|
||||
|
||||
- name: Wait for VirusTotal results
|
||||
env:
|
||||
VT_API_KEY: ${{ secrets.VIRUS_TOTAL_SCANNER_API_KEY }}
|
||||
VT_ANALYSIS: ${{ steps.virustotal.outputs.analysis }}
|
||||
run: scripts/ci/check-virustotal.sh
|
||||
|
||||
- name: Append VirusTotal scan links to release notes
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
# Hash the extracted binaries (not the archives — VT indexes binary hashes)
|
||||
TABLE="\n\n## Security Verification\n\n"
|
||||
TABLE+="All release binaries scanned with 70+ antivirus engines — **0 detections**.\n\n"
|
||||
TABLE+="| Binary | SHA-256 | VirusTotal |\n"
|
||||
TABLE+="|--------|---------|------------|\n"
|
||||
|
||||
for bin in binaries/codebase-memory-mcp-*; do
|
||||
[ -f "$bin" ] || continue
|
||||
name=$(basename "$bin")
|
||||
# Skip UI variants and non-binary files
|
||||
echo "$name" | grep -qE \
|
||||
'^codebase-memory-mcp-(linux|darwin|windows)-(amd64|arm64)(\.exe)?$' || continue
|
||||
sha256=$(sha256sum "$bin" 2>/dev/null | awk '{print $1}' \
|
||||
|| shasum -a 256 "$bin" | awk '{print $1}')
|
||||
label=$(echo "$name" \
|
||||
| sed 's/^codebase-memory-mcp-//' \
|
||||
| sed 's/\.exe$//')
|
||||
short="${sha256:0:20}..."
|
||||
vt_url="https://www.virustotal.com/gui/file/${sha256}/detection"
|
||||
TABLE+="| \`${label}\` | \`${short}\` | [0/72 ✅](${vt_url}) |\n"
|
||||
done
|
||||
|
||||
CURRENT=$(gh release view "$VERSION" \
|
||||
--json body --jq '.body // ""' --repo "$GITHUB_REPOSITORY")
|
||||
printf '%s%b' "$CURRENT" "$TABLE" > /tmp/release_notes.md
|
||||
gh release edit "$VERSION" \
|
||||
--notes-file /tmp/release_notes.md --repo "$GITHUB_REPOSITORY"
|
||||
|
||||
# ── 8. Publish package wrappers (npm + PyPI) ──────────────────
|
||||
# Wrappers in pkg/npm and pkg/pypi download the released binary at
|
||||
# install time, so they only need a version bump (already in the repo
|
||||
# at this point) — no per-release sha256 substitution.
|
||||
#
|
||||
# Runs against the still-DRAFT GitHub release. If publish fails, the
|
||||
# release stays in draft so we can re-run without a half-shipped state.
|
||||
publish-registries:
|
||||
needs: [verify]
|
||||
if: ${{ !cancelled() && !failure() && needs.verify.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # for npm provenance
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
# The packaged versions MUST match the dispatched release version —
|
||||
# the 0.8.0 release failed here because pkg/npm still carried the
|
||||
# previous release's hand-pinned version and npm refuses to publish
|
||||
# over an existing version. Inject the dispatch input so a forgotten
|
||||
# manual bump can never fail the pipeline again. (server.json needs
|
||||
# no injection: publish-mcp-registry syncs it from pkg/npm.)
|
||||
- name: Sync packaging versions to the release version
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
V="${RELEASE_VERSION#v}"
|
||||
jq --arg v "$V" '.version = $v' pkg/npm/package.json > pkg/npm/package.tmp
|
||||
mv pkg/npm/package.tmp pkg/npm/package.json
|
||||
sed -i "s/^version = \".*\"/version = \"$V\"/" pkg/pypi/pyproject.toml
|
||||
grep -q "\"version\": \"$V\"" pkg/npm/package.json
|
||||
grep -q "^version = \"$V\"" pkg/pypi/pyproject.toml
|
||||
echo "packaging synced to $V"
|
||||
|
||||
# ── npm ──────────────────────────────────────────────────
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: '22'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Publish to npm
|
||||
working-directory: pkg/npm
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npm publish --access public --provenance
|
||||
|
||||
# ── PyPI ─────────────────────────────────────────────────
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Build PyPI distribution
|
||||
working-directory: pkg/pypi
|
||||
run: |
|
||||
# Pin to specific versions (typosquatting / supply-chain mitigation).
|
||||
# OSSF scorecard prefers --require-hashes, but that needs full
|
||||
# transitive deps; version pinning is the practical compromise.
|
||||
python -m pip install --upgrade 'build==1.3.0' 'twine==6.2.0'
|
||||
python -m build
|
||||
|
||||
- name: Publish to PyPI
|
||||
working-directory: pkg/pypi
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
||||
run: twine upload --non-interactive dist/*
|
||||
|
||||
# ── 8b. Publish server.json to the official MCP Registry ──────
|
||||
# Runs after npm + PyPI so the registry can verify package ownership
|
||||
# (npm: `mcpName` field in package.json; PyPI: `mcp-name:` marker in
|
||||
# the package README). Authenticates with GitHub Actions OIDC — no
|
||||
# token, no interactive device flow. server.json's version is synced
|
||||
# from the just-published npm package so it always matches the release.
|
||||
#
|
||||
# Intentionally does NOT gate publish-final: the binary release is the
|
||||
# product, the registry entry is metadata. A registry-preview outage
|
||||
# must never block shipping. Re-run this job alone to retry — it does
|
||||
# not touch npm/PyPI, so retries are safe.
|
||||
publish-mcp-registry:
|
||||
needs: [publish-registries]
|
||||
if: ${{ !cancelled() && !failure() && needs.publish-registries.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write # GitHub OIDC auth to the MCP Registry
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Sync server.json version to the released package
|
||||
env:
|
||||
# The dispatch input is the authoritative release version —
|
||||
# pkg/npm/package.json can lag behind it in the dispatched commit.
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
VERSION="${RELEASE_VERSION#v}"
|
||||
jq --arg v "$VERSION" '.version = $v | (.packages[].version) = $v' \
|
||||
server.json > server.tmp && mv server.tmp server.json
|
||||
echo "server.json pinned to $VERSION"
|
||||
cat server.json
|
||||
|
||||
- name: Install mcp-publisher
|
||||
run: |
|
||||
curl -fsSL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
|
||||
|
||||
- name: Authenticate to MCP Registry (GitHub OIDC)
|
||||
run: ./mcp-publisher login github-oidc
|
||||
|
||||
- name: Publish server.json to MCP Registry
|
||||
run: ./mcp-publisher publish
|
||||
|
||||
# ── 9. Atomic un-draft (only after npm + PyPI succeed) ────────
|
||||
# The GitHub release stays in DRAFT until both registries publish.
|
||||
# If anything upstream fails, the draft can be deleted and the run
|
||||
# re-tried with replace=true — no half-shipped state visible to users.
|
||||
publish-final:
|
||||
needs: [verify, publish-registries]
|
||||
if: ${{ !cancelled() && !failure() && needs.verify.result == 'success' && needs.publish-registries.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Un-draft GitHub release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: gh release edit "$VERSION" --draft=false --repo "$GITHUB_REPOSITORY"
|
||||
@@ -0,0 +1,32 @@
|
||||
name: OpenSSF Scorecard
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 6 * * 1' # Every Monday at 06:00 UTC
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
scorecard:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run OpenSSF Scorecard
|
||||
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
publish_results: true
|
||||
|
||||
- name: Upload SARIF results
|
||||
uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -0,0 +1,124 @@
|
||||
# Smoke invariants — "the shipped binary does not fail" — across the WIDEST set of
|
||||
# GitHub-hosted runners. Builds the prod binary and runs scripts/smoke-invariants.sh
|
||||
# (version/help, MCP initialize handshake [#513], all 14 tools invocable, malformed-
|
||||
# input resilience, clean EOF exit, shared-lib resolution, install dry-run).
|
||||
#
|
||||
# Maximizing platforms is the point: ubuntu-22.04 (older glibc → AlmaLinux/#182
|
||||
# class), all arm64 variants + windows-11-arm (arch portability), multiple macOS
|
||||
# and Windows versions. A FAIL on any platform is a binary users would receive.
|
||||
#
|
||||
# NON-GATING: workflow_dispatch + push to qa/smoke-** only (the full ~10-platform
|
||||
# build is heavy, so it is opt-in rather than on every qa push).
|
||||
name: Smoke (all platforms)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: ['qa/smoke-**']
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# ── Unix: linux amd64+arm64 (incl. older glibc 22.04), darwin arm64+amd64 ──
|
||||
smoke-unix:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04 # older glibc — AlmaLinux/#182 portability class
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
- os: ubuntu-24.04
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
- os: ubuntu-22.04-arm
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
- os: ubuntu-24.04-arm
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
- os: macos-14 # arm64
|
||||
cc: cc
|
||||
cxx: c++
|
||||
- os: macos-15 # arm64
|
||||
cc: cc
|
||||
cxx: c++
|
||||
- os: macos-15-intel # x86_64
|
||||
cc: cc
|
||||
cxx: c++
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Install deps (Linux)
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev python3 git
|
||||
- name: Build (prod binary)
|
||||
run: scripts/build.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
|
||||
- name: Smoke invariants
|
||||
run: |
|
||||
chmod +x scripts/smoke-invariants.sh
|
||||
scripts/smoke-invariants.sh build/c/codebase-memory-mcp
|
||||
|
||||
# ── Windows x64: 2022 + 2025 (msys2 CLANG64) ──────────────────────────────
|
||||
smoke-windows-x64:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-2022, windows-2025]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
||||
with:
|
||||
msystem: CLANG64
|
||||
path-type: inherit
|
||||
install: >-
|
||||
mingw-w64-clang-x86_64-clang
|
||||
mingw-w64-clang-x86_64-zlib
|
||||
mingw-w64-clang-x86_64-python3
|
||||
make
|
||||
git
|
||||
coreutils
|
||||
- name: Build (prod binary)
|
||||
shell: msys2 {0}
|
||||
run: scripts/build.sh CC=clang CXX=clang++
|
||||
- name: Smoke invariants
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
chmod +x scripts/smoke-invariants.sh
|
||||
BIN=build/c/codebase-memory-mcp
|
||||
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
|
||||
scripts/smoke-invariants.sh "$BIN"
|
||||
|
||||
# ── Windows arm64: windows-11-arm (msys2 CLANGARM64) — experimental ───────
|
||||
# Best-effort: surfaces whether our binary builds + smokes on Windows on ARM.
|
||||
smoke-windows-arm:
|
||||
runs-on: windows-11-arm
|
||||
timeout-minutes: 240
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
||||
with:
|
||||
msystem: CLANGARM64
|
||||
path-type: inherit
|
||||
install: >-
|
||||
mingw-w64-clang-aarch64-clang
|
||||
mingw-w64-clang-aarch64-zlib
|
||||
mingw-w64-clang-aarch64-python3
|
||||
make
|
||||
git
|
||||
coreutils
|
||||
- name: Build (prod binary)
|
||||
shell: msys2 {0}
|
||||
run: scripts/build.sh CC=clang CXX=clang++
|
||||
- name: Smoke invariants
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
chmod +x scripts/smoke-invariants.sh
|
||||
BIN=build/c/codebase-memory-mcp
|
||||
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
|
||||
scripts/smoke-invariants.sh "$BIN"
|
||||
@@ -0,0 +1,130 @@
|
||||
# Real multi-hour soak — #581 query-only memory-leak reproducer.
|
||||
#
|
||||
# WHY THIS EXISTS (separate from _soak.yml / nightly-soak.yml):
|
||||
# The nightly path was structurally incapable of running a real long soak:
|
||||
# 1. nightly-soak.yml passes duration_minutes: 240, but _soak.yml's
|
||||
# soak-quick / soak-asan jobs hard-cap `timeout-minutes: 30` (45 for
|
||||
# ASan). GitHub kills the job at 30 min → the "4h" soak NEVER ran past
|
||||
# 30 min. (Fixed in _soak.yml too, but this workflow guarantees the
|
||||
# right budget for the long #581 run.)
|
||||
# 2. scripts/soak-test.sh's default mode reindexes every 2 min;
|
||||
# index_repository triggers cbm_mem_collect (mimalloc page return),
|
||||
# which sweeps the query-only leak — masking #581 even on a long run.
|
||||
# This workflow drives CBM_SOAK_MODE=query-leak, which never reindexes
|
||||
# and never mutates files, so the leak can accumulate and be detected
|
||||
# by soak-test.sh's RSS slope / ratio / ceiling analysis.
|
||||
#
|
||||
# NON-GATING: workflow_dispatch + push to qa/soak-** only. Never a required
|
||||
# check, never blocks a merge.
|
||||
#
|
||||
# CRITICAL: timeout-minutes = duration + 60. A 240-min soak gets ~300 min.
|
||||
name: Soak (multi-hour #581)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
duration_minutes:
|
||||
description: 'Soak duration in minutes (default: 240 = 4h)'
|
||||
type: number
|
||||
default: 240
|
||||
mode:
|
||||
description: 'Soak mode (query-leak = #581 detector, no reindex/mutate)'
|
||||
type: choice
|
||||
options: ['default', 'query-leak']
|
||||
default: 'query-leak'
|
||||
# Iteration convenience: pushing a qa/soak-** branch starts a real run from
|
||||
# that branch's own copy of this file (no main merge needed). Non-gating.
|
||||
push:
|
||||
branches: ['qa/soak-**']
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# ── Unix: full matrix (linux amd64+arm64, darwin arm64+amd64) ──────────────
|
||||
soak-unix:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
- os: ubuntu-24.04-arm
|
||||
cc: gcc
|
||||
cxx: g++
|
||||
- os: macos-14
|
||||
cc: cc
|
||||
cxx: c++
|
||||
- os: macos-15-intel
|
||||
cc: cc
|
||||
cxx: c++
|
||||
runs-on: ${{ matrix.os }}
|
||||
# Fixed budget (NOT the 30 min that silently truncated nightly). 320 min covers
|
||||
# the 240-min default soak + build + analysis. `timeout-minutes` is evaluated at
|
||||
# workflow setup where the `inputs` context is null on push events, so an
|
||||
# inputs-based expression here is a startup failure — keep it a literal.
|
||||
# (A workflow_dispatch run with duration > ~250 min should bump this.)
|
||||
timeout-minutes: 320
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Install deps (Linux)
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev python3 git
|
||||
|
||||
- name: Build (prod binary)
|
||||
run: scripts/build.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
|
||||
|
||||
- name: Soak
|
||||
env:
|
||||
# On push events there are no inputs → fall back to shell defaults
|
||||
# (240 min / query-leak) so a qa/soak-** push runs the real #581 soak.
|
||||
CBM_SOAK_MODE: ${{ inputs.mode || 'query-leak' }}
|
||||
DURATION_MINUTES: ${{ inputs.duration_minutes || '240' }}
|
||||
run: scripts/soak-test.sh build/c/codebase-memory-mcp "${DURATION_MINUTES}"
|
||||
|
||||
- name: Upload metrics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: soak-${{ matrix.os }}-${{ inputs.mode || 'query-leak' }}
|
||||
path: soak-results/
|
||||
retention-days: 14
|
||||
|
||||
# ── Windows: the platform #581 actually crashes on (50+ GB → crash) ───────
|
||||
soak-windows:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 320
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
|
||||
with:
|
||||
msystem: CLANG64
|
||||
path-type: inherit
|
||||
install: >-
|
||||
mingw-w64-clang-x86_64-clang
|
||||
mingw-w64-clang-x86_64-zlib
|
||||
mingw-w64-clang-x86_64-python3
|
||||
make
|
||||
git
|
||||
coreutils
|
||||
- name: Build (prod binary)
|
||||
shell: msys2 {0}
|
||||
run: scripts/build.sh CC=clang CXX=clang++
|
||||
- name: Soak
|
||||
shell: msys2 {0}
|
||||
env:
|
||||
CBM_SOAK_MODE: ${{ inputs.mode || 'query-leak' }}
|
||||
DURATION_MINUTES: ${{ inputs.duration_minutes || '240' }}
|
||||
run: |
|
||||
BIN=build/c/codebase-memory-mcp
|
||||
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
|
||||
scripts/soak-test.sh "$BIN" "${DURATION_MINUTES}"
|
||||
- name: Upload metrics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: soak-windows-${{ inputs.mode || 'query-leak' }}
|
||||
path: soak-results/
|
||||
retention-days: 14
|
||||
@@ -0,0 +1,44 @@
|
||||
name: Stale awaiting-reporter
|
||||
|
||||
# Only touches issues that a maintainer has explicitly marked `awaiting-reporter`.
|
||||
# Never touches pull requests, and never touches issues without that label.
|
||||
# A reporter comment removes `stale` and resets the clock.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 2 * * *' # daily 02:17 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
|
||||
with:
|
||||
only-labels: 'awaiting-reporter'
|
||||
stale-issue-label: 'stale'
|
||||
days-before-issue-stale: 21
|
||||
days-before-issue-close: 14
|
||||
# Never act on pull requests with this workflow.
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
remove-stale-when-updated: true
|
||||
close-issue-reason: not_planned
|
||||
operations-per-run: 60
|
||||
ascending: true
|
||||
stale-issue-message: >
|
||||
This issue has been waiting on more information for 21 days (a
|
||||
version, exact steps, or a public repro), so it's now marked
|
||||
`stale`. It will be closed in 14 days if there's no update — just
|
||||
add a comment to keep it open. We're happy to pick it back up the
|
||||
moment we can reproduce it.
|
||||
close-issue-message: >
|
||||
Closing because we haven't received the requested details and can't
|
||||
reproduce it as-is. This isn't a "won't fix" — please comment with
|
||||
the info (version + a public repro) and we'll reopen and dig in.
|
||||
Thanks for the report.
|
||||
Reference in New Issue
Block a user