Merge main into fix/issue-2120 for the release batch

Conflict in internal/cbm/cbm.h resolved by keeping both added CBMDefinition fields: decl_namespace (this branch) and the test-definition role/spans (main).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel
2026-10-07 18:21:45 +02:00
271 changed files with 76336 additions and 1571 deletions
+59
View File
@@ -0,0 +1,59 @@
{
"test_impact": {
"version": 1,
"tests": {
"presets": {
"c-cbm": true
}
},
"rules": [
{
"id": "harness",
"paths": ["tests/test_framework.h", "tests/test_helpers.*", "tests/test_main.c"],
"action": "run_all"
},
{
"id": "engine",
"paths": [".codebase-memory.json", "src/mcp/test_impact*", "src/store/store_impact.*"],
"action": "run_all"
},
{
"id": "build",
"paths": ["scripts/**", "test-infrastructure/**", "pkg/**"],
"action": "run_all"
},
{
"id": "grammars",
"paths": ["internal/cbm/vendored/**"],
"action": "run_all"
},
{
"id": "fixtures",
"paths": ["tests/fixtures/*/**"],
"action": "referencing_tests",
"key": "path_segment:2"
},
{
"id": "contracts",
"paths": ["tests/**/*.{sh,py,json,txt}"],
"action": "ignore"
},
{
"id": "notices",
"paths": ["NOTICE*", "CHANGELOG*", "THIRD_PARTY*"],
"action": "ignore"
}
],
"unmapped_files": "run_all",
"coverage": {
"compatibility_paths": [
"tests/test_framework.h",
"tests/test_main.c",
"tests/test_helpers.h",
"Makefile.cbm",
"scripts/test-impact/coverage-map.py",
"scripts/test-impact/coverage-merge.py"
]
}
}
}
+7
View File
@@ -15,3 +15,10 @@ scripts/hooks/pre-commit text eol=lf
# Chialisp grammar corpus: one case asserts CRLF comment termination, so this
# file must keep its exact bytes on every platform (no eol conversion). (#919)
tools/tree-sitter-chialisp/test/corpus/comments.txt -text
# llvm-profdata text captures: tests/test_profile_text_parser.py checks their
# SHA-256, so they keep their exact bytes on every platform (no eol conversion).
tests/fixtures/profile_text/*.proftext -text
# Golden profile-map outputs: tests/test_profile_map_v2.py compares the
# producer's LF bytes against them, so they keep their exact bytes too.
tests/fixtures/profile_map_v2/*.tsv -text
+8 -1
View File
@@ -3,13 +3,19 @@
name: Lint & Security
on:
workflow_call: {}
workflow_call:
inputs:
lanes:
description: 'PR lane selection (scripts/ci/select-lanes.sh JSON list); "all" = every job (dry run, release)'
type: string
default: all
permissions:
contents: read
jobs:
lint:
if: ${{ inputs.lanes == 'all' || contains(fromJSON(inputs.lanes), 'lint') }}
# Pinned, not ubuntu-latest: the LLVM install below adds an
# apt.llvm.org llvm-toolchain-NOBLE repo, which is 24.04 by name. When
# GitHub moves ubuntu-latest to the next LTS this job breaks on a day
@@ -64,6 +70,7 @@ jobs:
# provability (never suppressed — the NOLINT ban applies here too).
# Vendored-tree diagnostics are path-filtered, mirroring .cppcheck.
lint-mem:
if: ${{ inputs.lanes == 'all' || contains(fromJSON(inputs.lanes), 'lint-mem') }}
# Pinned for the same reason as `lint`: llvm-toolchain-noble-21 below.
runs-on: ubuntu-24.04
timeout-minutes: 60
+8 -1
View File
@@ -4,7 +4,12 @@
name: Security Gate
on:
workflow_call: {}
workflow_call:
inputs:
lanes:
description: 'PR lane selection (scripts/ci/select-lanes.sh JSON list); "all" = every job (dry run, release)'
type: string
default: all
permissions:
contents: read
@@ -23,6 +28,7 @@ jobs:
run: scripts/security-vendored.sh
license-gate:
if: ${{ inputs.lanes == 'all' || contains(fromJSON(inputs.lanes), 'license-gate') }}
runs-on: ubuntu-latest
timeout-minutes: 240
steps:
@@ -39,6 +45,7 @@ jobs:
run: scripts/audit-license-provenance.py
codeql-gate:
if: ${{ inputs.lanes == 'all' || contains(fromJSON(inputs.lanes), 'codeql-gate') }}
runs-on: ubuntu-latest
timeout-minutes: 240
# Without security-events:read this job inherits the workflow's
+101 -16
View File
@@ -16,6 +16,18 @@ on:
description: 'Split each C-suite leg across parallel shard jobs (ubuntu x3, windows x2); off = one job per leg, byte-identical to the pre-shard topology'
type: boolean
default: false
shard_profile:
description: 'Shard counts when shard_suites is on: standard = ubuntu x3, windows x2 (dry run, release); pr = ubuntu x86 x1, ubuntu arm64 x2, windows x1 (fewer jobs re-paying setup)'
type: string
default: standard
lanes:
description: 'PR lane selection (scripts/ci/select-lanes.sh JSON list): only those legs and jobs run; "all" = every one (dry run, release)'
type: string
default: all
test_selection:
description: 'PR test selection (smart CI): the artifact the select-tests job uploaded; the test-unix and test-windows legs run only its tests. Empty = every test (dry run, release, and any PR the selection does not narrow)'
type: string
default: ''
permissions:
contents: read
@@ -30,18 +42,26 @@ jobs:
outputs:
unix: ${{ steps.set.outputs.unix }}
windows: ${{ steps.set.outputs.windows }}
tsan: ${{ steps.set.outputs.tsan }}
has_unix: ${{ steps.set.outputs.has_unix }}
has_windows: ${{ steps.set.outputs.has_windows }}
has_tsan: ${{ steps.set.outputs.has_tsan }}
steps:
- name: Compute matrices
id: set
env:
BROAD: ${{ inputs.broad_platforms }}
SHARDS: ${{ inputs.shard_suites }}
SHARD_PROFILE: ${{ inputs.shard_profile }}
LANES: ${{ inputs.lanes }}
run: |
# Every CORE leg is tagged with the lane (scripts/ci/select-lanes.sh)
# it serves; the tag is what lane selection filters on below.
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++"}
{"os":"ubuntu-latest","cc":"gcc","cxx":"g++","lane":"unix-x86"},
{"os":"ubuntu-24.04-arm","cc":"gcc","cxx":"g++","lane":"unix-arm64"},
{"os":"macos-14","cc":"cc","cxx":"c++","lane":"unix-macos14"},
{"os":"macos-15-intel","cc":"cc","cxx":"c++","lane":"unix-macos-intel"}
]'
# Broad matrix legs are REQUIRED gates (no optional/continue-on-error
# escape hatches): the release dry run must be all-green, every platform.
@@ -59,7 +79,7 @@ jobs:
# 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"}]'
CORE_WIN='[{"os":"windows-latest","msystem":"CLANG64","pkg":"x86_64","lane":"windows"}]'
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')
@@ -68,6 +88,30 @@ jobs:
UNIX=$(jq -cn --argjson a "$CORE_UNIX" '$a')
WIN=$(jq -cn --argjson a "$CORE_WIN" '$a')
fi
# The ThreadSanitizer legs (test-tsan), tagged the same way.
TSAN='[
{"os":"ubuntu-latest","cc":"clang","cxx":"clang++","lane":"tsan-x86"},
{"os":"ubuntu-24.04-arm","cc":"clang","cxx":"clang++","lane":"tsan-arm"},
{"os":"macos-14","cc":"cc","cxx":"c++","lane":"tsan-mac"}
]'
# Lane selection. "all" (dry run, release) keeps every leg and drops
# the tags, so those matrices -- and their job names -- stay exactly
# as before lane selection. A PR keeps only its selected legs, and
# the tag stays in the job name, where ci-ok reads it back. An
# untagged leg could never be selected: that is a configuration
# error here, never a leg silently dropped.
if [ "$LANES" = "all" ]; then
UNIX=$(jq -c 'map(del(.lane))' <<< "$UNIX")
WIN=$(jq -c 'map(del(.lane))' <<< "$WIN")
TSAN=$(jq -c 'map(del(.lane))' <<< "$TSAN")
else
for M in "$UNIX" "$WIN" "$TSAN"; do
jq -e 'all(.[]; has("lane"))' <<< "$M" > /dev/null || { echo "::error::untagged test leg in $M"; exit 1; }
done
UNIX=$(jq -c --argjson sel "$LANES" 'map(select(.lane | IN($sel[])))' <<< "$UNIX")
WIN=$(jq -c --argjson sel "$LANES" 'map(select(.lane | IN($sel[])))' <<< "$WIN")
TSAN=$(jq -c --argjson sel "$LANES" 'map(select(.lane | IN($sel[])))' <<< "$TSAN")
fi
# Suite sharding (opt-in via the shard_suites input): each platform's
# C-suite run is split across N runner jobs. CBM_TEST_SHARD="i/N"
# slices deterministically inside run-tests-parallel.sh; each
@@ -78,20 +122,37 @@ jobs:
# shards); Windows gets 2 (every extra shard re-pays ~5 min of
# MSYS2 setup); macOS stays at 1 — mac legs are not the critical
# path and mac runner concurrency ceilings are the tightest.
# The pr profile shards less: a PR runs few legs and every shard
# re-pays its setup, so x86 and Windows run whole and only arm64,
# the slowest leg (~72 min unsharded), splits in two.
case "$SHARD_PROFILE" in
standard) N_X86=3; N_ARM=3; N_WIN=2 ;;
pr) N_X86=1; N_ARM=2; N_WIN=1 ;;
*) echo "::error::unknown shard_profile '$SHARD_PROFILE'"; exit 1 ;;
esac
if [ "$SHARDS" = "true" ]; then
UNIX=$(jq -c '[.[] | . as $e
| (if ($e.os | startswith("ubuntu")) then 3 else 1 end) as $n
UNIX=$(jq -c --argjson x86 "$N_X86" --argjson arm "$N_ARM" '[.[] | . as $e
| (if ($e.os | startswith("ubuntu")) then
(if ($e.os | endswith("-arm")) then $arm else $x86 end)
else 1 end) as $n
| range(1; $n + 1) as $i
| $e + {shard: "\($i)/\($n)"}]' <<< "$UNIX")
WIN=$(jq -c '[.[] | . as $e
| range(1; 3) as $i
| $e + {shard: "\($i)/2"}]' <<< "$WIN")
WIN=$(jq -c --argjson n "$N_WIN" '[.[] | . as $e
| range(1; $n + 1) as $i
| $e + {shard: "\($i)/\($n)"}]' <<< "$WIN")
fi
echo "unix={\"include\":$UNIX}" >> "$GITHUB_OUTPUT"
echo "windows={\"include\":$WIN}" >> "$GITHUB_OUTPUT"
echo "tsan={\"include\":$TSAN}" >> "$GITHUB_OUTPUT"
# GitHub rejects an empty matrix, so a job whose legs were all
# deselected is skipped through these flags instead.
echo "has_unix=$(jq 'length > 0' <<< "$UNIX")" >> "$GITHUB_OUTPUT"
echo "has_windows=$(jq 'length > 0' <<< "$WIN")" >> "$GITHUB_OUTPUT"
echo "has_tsan=$(jq 'length > 0' <<< "$TSAN")" >> "$GITHUB_OUTPUT"
test-unix:
needs: setup-matrix
if: ${{ needs.setup-matrix.outputs.has_unix == 'true' }}
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup-matrix.outputs.unix) }}
@@ -160,12 +221,20 @@ jobs:
ccache-test-${{ matrix.os }}-${{ matrix.cc }}-${{ github.ref }}-
ccache-test-${{ matrix.os }}-${{ matrix.cc }}-
- name: Fetch the test selection
if: ${{ inputs.test_selection != '' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ inputs.test_selection }}
path: ${{ runner.temp }}/test-selection
- name: Test
run: scripts/test.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
env:
CBM_TEST_SHARD: ${{ matrix.shard }}
CBM_TEST_LEG: ${{ matrix.os }}-${{ matrix.cc }}
CBM_SKIP_PERF: ${{ inputs.skip_perf && '1' || '' }}
CBM_TEST_SELECTION_DIR: ${{ inputs.test_selection != '' && format('{0}/test-selection', runner.temp) || '' }}
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_MAXSIZE: 1500M
@@ -205,6 +274,7 @@ jobs:
# inode/flock behavior and Windows handle/link behavior are each gating,
# without adding Node/Go provisioning to every sanitizer/container leg.
test-package-wrappers:
if: ${{ inputs.lanes == 'all' || contains(fromJSON(inputs.lanes), 'pkg-wrappers') }}
strategy:
fail-fast: false
matrix:
@@ -245,6 +315,7 @@ jobs:
# parallel suite — catches what an older compiler-rt can miss. Same
# canonical scripts/test.sh wave as every other leg.
test-diag:
if: ${{ inputs.lanes == 'all' || contains(fromJSON(inputs.lanes), 'diag') }}
# Pinned for the same reason as the lint jobs: the pinned diagnostic
# toolchain below installs from llvm-toolchain-noble-21.
runs-on: ubuntu-24.04
@@ -310,6 +381,7 @@ jobs:
# list both venues now share. Zero use-of-uninitialized-value findings were
# reported across the lane on either architecture.
test-msan:
if: ${{ inputs.lanes == 'all' || contains(fromJSON(inputs.lanes), 'msan') }}
runs-on: ubuntu-latest
timeout-minutes: 180
steps:
@@ -334,6 +406,7 @@ jobs:
# so this leg is the ordinary ASan suite built with that toolchain and run
# with detect_leaks=1. Mirrors the local `make -f Makefile.cbm test-lsan`.
test-lsan-macos:
if: ${{ inputs.lanes == 'all' || contains(fromJSON(inputs.lanes), 'lsan-macos') }}
runs-on: macos-latest
timeout-minutes: 120
steps:
@@ -372,13 +445,11 @@ jobs:
key: ccache-lsan-${{ github.ref }}-${{ github.sha }}
test-tsan:
needs: setup-matrix
if: ${{ needs.setup-matrix.outputs.has_tsan == 'true' }}
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, cc: clang, cxx: clang++ }
- { os: ubuntu-24.04-arm, cc: clang, cxx: clang++ }
- { os: macos-14, cc: cc, cxx: c++ }
matrix: ${{ fromJSON(needs.setup-matrix.outputs.tsan) }}
runs-on: ${{ matrix.os }}
timeout-minutes: 120
steps:
@@ -431,6 +502,7 @@ jobs:
test-windows:
needs: setup-matrix
if: ${{ needs.setup-matrix.outputs.has_windows == 'true' }}
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup-matrix.outputs.windows) }}
@@ -481,6 +553,13 @@ jobs:
ccache-test-${{ matrix.os }}-${{ matrix.msystem }}-${{ github.ref }}-
ccache-test-${{ matrix.os }}-${{ matrix.msystem }}-
- name: Fetch the test selection
if: ${{ inputs.test_selection != '' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ inputs.test_selection }}
path: ${{ runner.temp }}/test-selection
- name: Test
shell: msys2 {0}
# AddressSanitizer is unavailable on native ARM64 Windows (LLVM ships no
@@ -508,6 +587,7 @@ jobs:
CBM_TEST_SHARD: ${{ matrix.shard }}
CBM_TEST_LEG: ${{ matrix.os }}-${{ matrix.msystem }}
CBM_SKIP_PERF: ${{ inputs.skip_perf && '1' || '' }}
CBM_TEST_SELECTION_DIR: ${{ inputs.test_selection != '' && format('{0}/test-selection', runner.temp) || '' }}
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_MAXSIZE: 1500M
@@ -564,8 +644,12 @@ jobs:
with:
pattern: shard-manifest-*
path: manifests
# A PR with no test leg selected uploads no manifest; the script
# accepts that only when the selection really holds no leg.
- name: Verify cross-shard union per leg
run: repo/scripts/ci/verify-shard-union.sh manifests
env:
LANES: ${{ inputs.lanes }}
run: repo/scripts/ci/verify-shard-union.sh manifests --lanes "$LANES"
# Windows product-surface regression guards. Distinct from test-windows above
# (the sanitizer C suite): these drive a real product binary + external UI pack
@@ -576,6 +660,7 @@ jobs:
# CLI arguments being mangled by the narrow-argv main() (#423/#20, fixed by the
# wide-argv entrypoint that reads GetCommandLineW).
test-windows-guards:
if: ${{ inputs.lanes == 'all' || contains(fromJSON(inputs.lanes), 'windows-guards') }}
runs-on: windows-latest
timeout-minutes: 60
steps:
+2 -2
View File
@@ -29,7 +29,7 @@ jobs:
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
- name: Initialize CodeQL
uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
with:
languages: c-cpp
build-mode: manual
@@ -38,6 +38,6 @@ jobs:
run: scripts/build.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
with:
category: "/language:c-cpp"
+199 -33
View File
@@ -1,7 +1,10 @@
# PR validation: security gates + lint + full test suite. Builds, smoke and
# soak stay maintainer-driven via the dry-run workflow_dispatch. Branch
# PR validation: security gates + lint + the test legs this PR's changes need.
# `changes` runs scripts/ci/select-lanes.sh on the PR's file list; every lane
# job below runs only when selected (unknown paths, workflows, the harness,
# Makefile.cbm and vendored code select everything). Builds, smoke and soak at
# full breadth 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.
# fails unless every PR stage succeeded and every selected lane ran green.
name: PR
on:
@@ -21,6 +24,7 @@ concurrency:
jobs:
security:
needs: [changes]
uses: ./.github/workflows/_security.yml
# The called job requests security-events:read to count code-scanning
# alerts; a reusable workflow cannot request more than its caller grants,
@@ -30,33 +34,91 @@ jobs:
security-events: read
actions: read
secrets: inherit
with:
lanes: ${{ needs.changes.outputs.lanes }}
lint:
needs: [changes]
if: ${{ contains(fromJSON(needs.changes.outputs.lanes), 'lint') || contains(fromJSON(needs.changes.outputs.lanes), 'lint-mem') }}
uses: ./.github/workflows/_lint.yml
with:
lanes: ${{ needs.changes.outputs.lanes }}
# ── Smart CI, PR CI only: run only the tests this change can reach.
# Active only while the repository variable TEST_IMPACT_MODE is `gate`;
# unset (shadow) every test runs and test-impact-shadow only reports.
# test-impact-select.sh --gate narrows only on a well-formed answer: a
# failure of this job, a run-all answer or a lane that selects nothing
# leaves `test` without a selection, and every test runs. ci-ok does not
# need this job: when it breaks, PRs cost more minutes, never coverage.
# Dry runs and releases never pass a selection.
select-tests:
needs: [changes]
if: ${{ vars.TEST_IMPACT_MODE == 'gate' }}
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
actions: read # the merge base's team-artifact bundle (test-impact-artifact.yml)
outputs:
selection: ${{ steps.select.outputs.selection }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# The PR merge commit and its base parent: the change to select for.
fetch-depth: 2
persist-credentials: false
- name: Build this checkout's engine
run: |
sudo apt-get update && sudo apt-get install -y zlib1g-dev
scripts/build.sh CC=gcc CXX=g++
- name: Select the tests
id: select
env:
GH_TOKEN: ${{ github.token }}
run: scripts/ci/test-impact-select.sh --binary build/c/codebase-memory-mcp --out "$RUNNER_TEMP/test-impact" --gate "$RUNNER_TEMP/test-selection"
- name: Upload the selection
if: ${{ steps.select.outputs.selection == 'narrowed' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-selection
path: ${{ runner.temp }}/test-selection
if-no-files-found: error
overwrite: true
test:
needs: [lint]
needs: [changes, lint, select-tests]
if: ${{ !cancelled() && needs.lint.result == 'success' }}
uses: ./.github/workflows/_test.yml
with:
lanes: ${{ needs.changes.outputs.lanes }}
# Smart CI: only a successful select-tests run that narrowed passes a
# selection; skipped (shadow), failed, run-all and nothing-selected
# answers all run every test.
test_selection: ${{ needs.select-tests.result == 'success' && needs.select-tests.outputs.selection == 'narrowed' && 'test-selection' || '' }}
# 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
# Iteration speed: split each C-suite leg across parallel shard jobs
# (ubuntu x3, windows x2; coverage re-proven every run by the
# shard-completeness job). Dry runs and releases shard identically.
# Iteration speed: split C-suite legs across parallel shard jobs
# (coverage re-proven every run by the shard-completeness job). The PR
# profile shards only arm64 (x2); x86 and Windows run whole, since each
# shard re-pays its setup. Dry runs and releases keep x3 / x2.
shard_suites: true
shard_profile: pr
# ── Which files changed? Gate the (heavier) build+smoke on product code so
# docs / CI / test-only PRs stay fast. ──
# ── Which lanes does this PR need? The changed files go through
# scripts/ci/select-lanes.sh (tier, lanes, full, reasons); docs / CI /
# test-only PRs stay fast, anything it cannot place runs everything. ──
changes:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
product: ${{ steps.f.outputs.product }}
tier: ${{ steps.select.outputs.tier }}
full: ${{ steps.select.outputs.full }}
lanes: ${{ steps.select.outputs.lanes }}
smoke: ${{ steps.select.outputs.smoke }}
steps:
- name: Detect product-code changes
id: f
- name: List the changed files
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.pull_request.number }}
@@ -64,7 +126,9 @@ jobs:
run: |
# The full .diff endpoint rejects large-but-valid PRs at 20k lines.
# The paginated files endpoint remains filename-only for this gate.
if FILES=$(gh api --paginate "repos/$REPO/pulls/$PR/files?per_page=100" --jq '.[].filename'); then
# A rename lists its old path too, as the fallback's --no-renames
# diff does: moving a file away is a change to where it was.
if FILES=$(gh api --paginate "repos/$REPO/pulls/$PR/files?per_page=100" --jq '.[] | .filename, (.previous_filename // empty)'); then
echo "file list: pulls/files API"
else
# The files endpoint renders the diff server-side too, and answers
@@ -81,31 +145,45 @@ jobs:
"https://github.com/$REPO" "refs/pull/$PR/merge"
FILES=$(git --git-dir="$CHANGES_GIT" diff --name-only --no-renames FETCH_HEAD^1 FETCH_HEAD)
fi
printf '%s\n' "$FILES"
# A here-string, not a pipe: under this shell's pipefail, grep -q
# exits on its first match, printf dies of SIGPIPE once the list
# outgrows the pipe buffer, and the MATCH is then reported as a
# failure -- product=false, smoke skipped, on the largest PRs.
if grep -qE '^(src/|internal/|install\.(sh|ps1)|scripts/build\.sh|scripts/smoke-test\.sh|scripts/smoke-local\.sh|scripts/smoke-fixture-server\.py|scripts/gen-third-party-notices\.sh|scripts/env\.sh|test-infrastructure/vm/(vm-smoke\.sh|windows-user-path-guard\.ps1)|Makefile\.cbm)' <<<"$FILES"; then
echo "product=true" >> "$GITHUB_OUTPUT"
else
echo "product=false" >> "$GITHUB_OUTPUT"
fi
printf '%s\n' "$FILES" > "$RUNNER_TEMP/changed-files.txt"
cat "$RUNNER_TEMP/changed-files.txt"
# The selector is PR code: it runs in its own step, after the
# token-bearing one, from a checkout that keeps no credentials.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# The pulls/files API lists at most 3000 files: the PR's own count lets
# the selector notice a cut list and select everything instead.
- name: Select lanes
id: select
env:
CHANGED_FILES: ${{ github.event.pull_request.changed_files }}
run: |
scripts/ci/select-lanes.sh --format github-output --expect-files "$CHANGED_FILES" "$RUNNER_TEMP/changed-files.txt" > "$RUNNER_TEMP/selection.txt"
cat "$RUNNER_TEMP/selection.txt"
cat "$RUNNER_TEMP/selection.txt" >> "$GITHUB_OUTPUT"
# pr-smoke's legs, each tagged with its lane (kept in the job name,
# where ci-ok reads it back); the job gets the selected ones.
SMOKE='[{"os":"ubuntu-latest","lane":"smoke-ubuntu"},{"os":"macos-14","lane":"smoke-mac"},{"os":"windows-latest","lane":"smoke-win"}]'
LANES=$(sed -n 's/^lanes=//p' "$RUNNER_TEMP/selection.txt")
echo "smoke={\"include\":$(jq -c --argjson sel "$LANES" 'map(select(.lane | IN($sel[])))' <<< "$SMOKE")}" >> "$GITHUB_OUTPUT"
# ── 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.
# in the dry run. Only the selected legs run (ubuntu from the extraction
# tier up, all three for platform/packaging changes); every leg gates.
# The maintained local wrappers add a race-free release fixture so the
# download/checksum/install/update phases run here as well. ──
pr-smoke:
needs: [changes]
if: ${{ !cancelled() && needs.changes.outputs.product == 'true' }}
if: ${{ !cancelled() && needs.changes.result == 'success' && (contains(fromJSON(needs.changes.outputs.lanes), 'smoke-ubuntu') || contains(fromJSON(needs.changes.outputs.lanes), 'smoke-mac') || contains(fromJSON(needs.changes.outputs.lanes), 'smoke-win')) }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-14, windows-latest]
matrix: ${{ fromJSON(needs.changes.outputs.smoke) }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
@@ -177,27 +255,115 @@ jobs:
# ── Waste sanitizer, REPORT-ONLY: memory + CPU waste on this repository and
# the per-function scaling lane (Linux). Deliberately NOT in ci-ok's needs:
# it uploads evidence and prints the ratchet, it never blocks a merge. Only
# product-code PRs pay for it. ──
# PRs whose selection holds the memwaste lane (memory core, memwaste
# tooling, full runs) pay for it. ──
memwaste:
needs: [changes]
if: ${{ !cancelled() && needs.changes.outputs.product == 'true' }}
if: ${{ !cancelled() && needs.changes.result == 'success' && contains(fromJSON(needs.changes.outputs.lanes), 'memwaste') }}
uses: ./.github/workflows/_memwaste.yml
with:
mode: pr
# ── Test-impact SHADOW, REPORT-ONLY: predicts from the code graph which C
# suites this change can affect (the latest released binary indexes the
# PR checkout; detect_changes at depth 15 against the base, mapped to
# suites, with the selector's run-all triggers) and uploads the
# prediction, so graph-based selection can be judged against what really
# failed before anything gates on it. continue-on-error and NOT in
# ci-ok's needs: it can never block or pass a merge. ──
test-impact-shadow:
runs-on: ubuntu-latest
timeout-minutes: 45
continue-on-error: true
permissions:
contents: read
actions: read # the merge base's team-artifact bundle (test-impact-artifact.yml)
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# The PR merge commit and its base parent: the diff to predict for.
fetch-depth: 2
persist-credentials: false
- name: Fetch the latest released binary
id: release
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: scripts/ci/test-impact-shadow.sh --fetch-release "$RUNNER_TEMP/cbm-bin"
- name: Build the binary from this checkout (download failed)
if: ${{ steps.release.outcome == 'failure' }}
run: |
sudo apt-get update && sudo apt-get install -y zlib1g-dev
scripts/build.sh CC=gcc CXX=g++
mkdir -p "$RUNNER_TEMP/cbm-bin"
cp build/c/codebase-memory-mcp "$RUNNER_TEMP/cbm-bin/"
- name: Predict the impacted suites
run: scripts/ci/test-impact-shadow.sh --binary "$RUNNER_TEMP/cbm-bin/codebase-memory-mcp" --out "$RUNNER_TEMP/test-impact"
# The engine (test-impact select) is this checkout's own code: the
# released binary predates it, so the checkout is built either way.
- name: Build this checkout's engine
if: ${{ steps.release.outcome != 'failure' }}
run: |
sudo apt-get update && sudo apt-get install -y zlib1g-dev
scripts/build.sh CC=gcc CXX=g++
- name: Select with the team artifact (engine shadow)
env:
GH_TOKEN: ${{ github.token }}
run: scripts/ci/test-impact-select.sh --binary build/c/codebase-memory-mcp --out "$RUNNER_TEMP/test-impact"
- name: Upload the prediction
if: ${{ always() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-impact-prediction
path: |
${{ runner.temp }}/test-impact/prediction.json
${{ runner.temp }}/test-impact/selection.json
${{ runner.temp }}/test-impact/changed-files.txt
${{ runner.temp }}/test-impact/selection-engine.json
${{ runner.temp }}/test-impact/summary-engine.md
if-no-files-found: warn
# ── Docs-only and non-product PRs (tiers T0a, T0b) run the static contract
# steps of the canonical test leg on their own: they police docs
# surfaces (language counts, version metadata), test-infrastructure/ and
# lint config too. Every test leg runs the same steps. ──
contracts:
needs: [changes]
if: ${{ !cancelled() && needs.changes.result == 'success' && contains(fromJSON(needs.changes.outputs.lanes), 'contracts') }}
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Contract steps only
run: scripts/test.sh --contracts-only
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]
# succeeded, so matrix renames can never silently deadlock merges, and
# every lane the selector chose ran green: `skipped` is OK only for a lane
# nobody selected (a selected lane that skipped is a silent gate loss).
# memwaste and the test-impact shadow are report-only and not needed here.
needs: [changes, security, lint, test, pr-smoke, contracts]
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 5
# actions:read lists this run's jobs, the lane-level truth behind `needs`.
permissions:
contents: read
actions: read
steps:
# Needs a checkout (unlike before): the gate logic lives in the canonical
# scripts/ci entry, not inline YAML (venue-parity contract).
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: All PR stages must have succeeded
- name: List this run's jobs
env:
GH_TOKEN: ${{ github.token }}
run: |
gh api --paginate "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/jobs?filter=latest&per_page=100" \
--jq '.jobs[] | {name, status, conclusion}' > "$RUNNER_TEMP/jobs.jsonl"
- name: All PR stages and every selected lane must have succeeded
env:
RESULTS: ${{ toJSON(needs) }}
LANES: ${{ needs.changes.outputs.lanes }}
JOBS: ${{ runner.temp }}/jobs.jsonl
run: scripts/ci/require-all-green.sh
+1 -1
View File
@@ -27,6 +27,6 @@ jobs:
publish_results: true
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4
uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4
with:
sarif_file: results.sarif
@@ -0,0 +1,85 @@
# Team artifact for test selection (smart CI, report-only until the oracles
# and the shadow comparison are green). Every push to main publishes the
# commit's bundle — its frozen graph, built incrementally from the previous
# bundle, and its per-test coverage map, re-run only for the suites the push
# selects — as the Actions artifact test-impact-<sha>. A weekly run rebuilds
# both from scratch and fails when the incremental graph differs from the full
# one. PR selection fetches the bundle of its merge base
# (scripts/ci/test-impact-bundle.sh) and never narrows on anything else.
name: test-impact-artifact
on:
push:
branches: [main]
schedule:
- cron: "23 3 * * 0" # weekly full refresh, Sunday 03:23 UTC
workflow_dispatch:
inputs:
mode:
description: incremental or full
type: choice
options: [incremental, full]
default: full
permissions:
contents: read
actions: read # fetch the previous bundle
concurrency:
# Bundles chain: each push builds on the previous one, so runs never overlap
# and are never cancelled half-way.
group: test-impact-artifact
cancel-in-progress: false
jobs:
publish:
runs-on: ubuntu-24.04
timeout-minutes: 120
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 2
persist-credentials: false
- name: Install build deps and LLVM 21 (coverage toolchain)
run: |
sudo apt-get update && sudo apt-get install -y zlib1g-dev
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-21 main" | sudo tee /etc/apt/sources.list.d/llvm-21.list
sudo apt-get update
sudo apt-get install -y clang-21 llvm-21 libclang-rt-21-dev
- name: Build the production binary and the coverage runner
run: |
scripts/build.sh CC=gcc CXX=g++
make -f Makefile.cbm test-runner-cov COV_LLVM_BIN=/usr/lib/llvm-21/bin -j"$(nproc)"
- name: Publish the bundle
env:
GH_TOKEN: ${{ github.token }}
EVENT: ${{ github.event_name }}
BEFORE: ${{ github.event.before }}
REQUESTED: ${{ inputs.mode }}
# The selection lane excludes @perf and PR CI skips perf, so their
# (slowest, longest under coverage) suites are neither mapped nor
# listed: the runner then neither lists nor runs them.
CBM_SKIP_PERF: "1"
run: |
mode=incremental
if [ "$EVENT" = schedule ] || [ "$REQUESTED" = full ]; then mode=full; fi
before="$(git rev-parse HEAD^1)"
if [ "$EVENT" = push ] && [ -n "$BEFORE" ] && [ "$BEFORE" != 0000000000000000000000000000000000000000 ]; then
before="$BEFORE"
fi
scripts/ci/test-impact-publish.sh --binary build/c/codebase-memory-mcp \
--runner build/cov/test-runner --llvm-bin /usr/lib/llvm-21/bin \
--out "$RUNNER_TEMP/bundle" --mode "$mode" --before "$before"
- name: Upload the bundle
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-impact-${{ github.sha }}
path: ${{ runner.temp }}/bundle
include-hidden-files: true # the graph lives in .codebase-memory/
retention-days: 30
if-no-files-found: error
+3
View File
@@ -51,6 +51,9 @@ graph-ui/dist/
# Generated by scripts/embed-frontend.sh into the source tree (the linker needs
# it beside the other UI sources). Removed by scripts/clean.sh; never committed.
src/ui/embedded_assets.c
# Raw coverage profiles. A coverage-instrumented binary (make test-runner-cov,
# cbm-cov) run without LLVM_PROFILE_FILE drops default.profraw in its cwd.
*.profraw
# Generated reports
BENCHMARK_REPORT.md
+164 -13
View File
@@ -71,13 +71,33 @@ ifeq ($(TEST_SEAMS),1)
TEST_SEAM_DEFINE := -DCBM_ENABLE_TEST_SEAMS=1
endif
# Per-test coverage for the test-impact map. TEST_COVERAGE=1 compiles clang
# source coverage into OUR code only -- the one-shot sources, the LSP unity
# object and the preprocessor -- in the test runner and in the product binary,
# and turns on the runner's per-test profile hooks (tests/test_framework.h).
# Vendored objects stay uninstrumented: a vendored change selects tests by rule,
# and every counter compiled in is written out again for every test. Like every
# other flavour it needs its own BUILD_DIR; the test-runner-cov and cbm-cov
# targets pin one.
# -fprofile-continuous: a child process started with `%c` in LLVM_PROFILE_FILE
# keeps its counters mapped onto its profile file, so a child that is killed
# still leaves a complete profile. Linux needs the flag for `%c` to work at
# all; with it macOS behaves the same way (tests/test_main.c, "Children").
TEST_COVERAGE ?= 0
TEST_COVERAGE_FLAGS :=
TEST_COVERAGE_DEFINE :=
ifeq ($(TEST_COVERAGE),1)
TEST_COVERAGE_FLAGS := -fprofile-instr-generate -fcoverage-mapping -fprofile-continuous
TEST_COVERAGE_DEFINE := -DCBM_TEST_COVERAGE=1
endif
# Production flags (CFLAGS_EXTRA allows CI to inject -DCBM_VERSION)
# CBM_BIND_TS_ALLOCATOR=1: bind the tree-sitter runtime to mimalloc (#424). Only
# the prod build uses mimalloc (MI_OVERRIDE=1); the test build is CRT+ASan, where
# binding would create an alloc/free mismatch, so the guard is prod-only.
CFLAGS_PROD = $(CFLAGS_COMMON) -O2 -DCBM_BIND_TS_ALLOCATOR=1 $(TEST_SEAM_DEFINE) \
$(GLOBAL_OVERRIDE_DEFINE) $(MEMWASTE_CFLAGS) $(CFLAGS_EXTRA)
CXXFLAGS_PROD = $(CXXFLAGS_COMMON) -O2 $(MEMWASTE_VENDOR_CFLAGS)
$(GLOBAL_OVERRIDE_DEFINE) $(MEMWASTE_CFLAGS) $(TEST_COVERAGE_FLAGS) $(CFLAGS_EXTRA)
CXXFLAGS_PROD = $(CXXFLAGS_COMMON) -O2 $(MEMWASTE_VENDOR_CFLAGS) $(TEST_COVERAGE_FLAGS)
# Test flags: debug + sanitizers (override SANITIZE= to disable on Windows)
SANITIZE = -fsanitize=address,undefined -fno-omit-frame-pointer
@@ -102,8 +122,10 @@ INCREMENTAL_TEST_DEFINE = -DCBM_INCREMENTAL_TEST_API=1
COVERAGE_MARKER_TEST_DEFINE = -DCBM_COVERAGE_MARKER_TEST_API=1
CFLAGS_TEST = $(CFLAGS_COMMON) $(EDITOR_TEST_DEFINES) $(SANITIZED_DEFINE) \
$(KOTLIN_DEDUP_TEST_DEFINE) $(CALL_REFERENCE_LOOKUP_TEST_DEFINE) \
$(INCREMENTAL_TEST_DEFINE) $(COVERAGE_MARKER_TEST_DEFINE) -g -O1 $(SANITIZE)
CXXFLAGS_TEST = $(CXXFLAGS_COMMON) $(SANITIZED_DEFINE) -g -O1 $(SANITIZE) $(CXX_STDLIB_FLAGS)
$(INCREMENTAL_TEST_DEFINE) $(COVERAGE_MARKER_TEST_DEFINE) $(TEST_COVERAGE_DEFINE) \
-g -O1 $(SANITIZE) $(TEST_COVERAGE_FLAGS)
CXXFLAGS_TEST = $(CXXFLAGS_COMMON) $(SANITIZED_DEFINE) -g -O1 $(SANITIZE) $(TEST_COVERAGE_FLAGS) \
$(CXX_STDLIB_FLAGS)
# TSan (can't combine with ASan)
TSAN_SANITIZE = -fsanitize=thread -fno-omit-frame-pointer
@@ -309,6 +331,7 @@ FOUNDATION_SRCS = \
src/foundation/str_util.c \
src/foundation/workspace.c \
src/foundation/platform.c \
src/foundation/path_syntax_internal.c \
src/foundation/system_info.c \
src/foundation/slab_alloc.c \
src/foundation/yaml.c \
@@ -352,12 +375,14 @@ EXTRACTION_SRCS = \
$(CBM_DIR)/extract_dbt.c \
$(CBM_DIR)/sql_values.c \
$(CBM_DIR)/helpers.c \
$(CBM_DIR)/cpp_branch_views.c \
$(CBM_DIR)/result_compact.c \
$(CBM_DIR)/result_spill.c \
$(CBM_DIR)/lang_specs.c \
$(CBM_DIR)/macro_table.c \
$(CBM_DIR)/iris_export_xml.c \
$(CBM_DIR)/service_patterns.c
$(CBM_DIR)/service_patterns.c \
$(CBM_DIR)/spawn_patterns.c
# LSP resolvers (compiled as one unit via lsp_all.c)
LSP_SRCS = $(CBM_DIR)/lsp_all.c
@@ -400,13 +425,15 @@ PREPROCESSOR_SRC = $(CBM_DIR)/preprocessor.cpp
SQLITE_WRITER_SRC = $(CBM_DIR)/sqlite_writer.c
# Store module (new)
STORE_SRCS = src/store/store.c
STORE_SRCS = src/store/store.c src/store/store_impact.c src/store/store_graph_digest.c src/store/store_content_digest.c
# Cypher module (new)
CYPHER_SRCS = src/cypher/cypher.c
# MCP server module (new)
MCP_SRCS = src/mcp/mcp.c src/mcp/index_supervisor.c src/mcp/compact_out.c
MCP_SRCS = src/mcp/mcp.c src/mcp/index_supervisor.c src/mcp/compact_out.c \
src/mcp/test_impact.c src/mcp/test_impact_diff.c src/mcp/test_impact_classify.c src/mcp/test_impact_source.c src/mcp/test_impact_seed.c src/mcp/test_impact_engine.c src/mcp/test_impact_engine_snapshot.c src/mcp/test_impact_engine_reach.c src/mcp/test_impact_engine_artifact.c src/mcp/test_impact_cli.c src/mcp/test_impact_artifact.c src/mcp/test_impact_changes.c src/mcp/test_impact_origins.c src/mcp/test_impact_coverage.c src/mcp/test_impact_config.c src/mcp/test_impact_select.c src/mcp/test_impact_policy.c src/mcp/test_impact_git.c src/mcp/test_impact_inventory.c src/mcp/test_impact_inventory_input.c src/mcp/test_impact_tree.c src/mcp/test_impact_tree_audit.c src/mcp/test_impact_tree_read.c src/mcp/test_impact_tree_batch.c src/mcp/test_impact_tree_digest.c src/mcp/test_impact_tree_names.c src/mcp/test_impact_tree_plan.c src/mcp/test_impact_tree_posix.c src/mcp/test_impact_tree_posix_acl.c src/mcp/test_impact_tree_posix_io.c src/mcp/test_impact_tree_util.c src/mcp/test_impact_tree_windows.c src/mcp/test_impact_tree_windows_acl.c src/mcp/test_impact_tree_windows_io.c \
src/mcp/test_impact_result.c src/mcp/test_impact_result_json.c src/mcp/test_impact_result_project.c src/mcp/test_impact_result_receipt.c src/mcp/test_impact_result_rows.c src/mcp/test_impact_result_util.c src/mcp/test_impact_result_validate.c src/mcp/test_impact_result_wire.c
# Shared MCP daemon coordination and local transport
DAEMON_SRCS = \
@@ -425,7 +452,18 @@ DAEMON_SRCS = \
DISCOVER_SRCS = \
src/discover/language.c \
src/discover/userconfig.c \
src/discover/test_conventions.c \
src/discover/gitignore.c \
src/discover/gitignore_checked.c \
src/discover/inventory_filter.c \
src/discover/inventory.c \
src/discover/inventory_guard.c \
src/discover/inventory_metadata.c \
src/discover/inventory_directories.c \
src/discover/inventory_controls.c \
src/discover/gitignore_checked_parse.c \
src/discover/gitignore_core.c \
src/discover/gitignore_guard.c \
src/discover/discover.c
# Graph buffer module (new)
@@ -445,6 +483,7 @@ PIPELINE_SRCS = \
src/pipeline/pass_parallel.c \
src/pipeline/pass_definitions.c \
src/pipeline/pass_calls.c \
src/pipeline/pass_spawns.c \
src/pipeline/pass_lsp_cross.c \
src/pipeline/pass_usages.c \
src/pipeline/pass_semantic.c \
@@ -617,6 +656,8 @@ PROJECT_HDRS = $(wildcard src/*.h src/*/*.h $(CBM_DIR)/*.h $(CBM_DIR)/lsp/*.h te
TEST_FOUNDATION_SRCS = \
tests/test_main.c \
tests/test_coverage_children.c \
tests/test_coverage_parent_failure.c \
tests/test_arena.c \
tests/test_hash_table.c \
tests/test_dyn_array.c \
@@ -636,6 +677,7 @@ TEST_FOUNDATION_SRCS = \
TEST_EXTRACTION_SRCS = \
tests/test_extraction.c \
tests/test_callable_sig.c \
tests/test_test_conventions.c \
tests/test_extraction_inheritance.c \
tests/test_extraction_imports.c \
tests/test_parse_coverage.c \
@@ -647,6 +689,9 @@ TEST_EXTRACTION_SRCS = \
TEST_STORE_SRCS = \
tests/test_store_nodes.c \
tests/test_store_edges.c \
tests/test_store_impact.c \
tests/test_store_scope.c \
tests/test_store_graph_digest.c \
tests/test_store_search.c \
tests/test_store_arch.c \
tests/test_store_bulk.c \
@@ -659,6 +704,23 @@ TEST_CYPHER_SRCS = \
TEST_MCP_SRCS = \
tests/test_mcp.c \
tests/test_test_impact.c \
tests/test_test_impact_profiles.c \
tests/test_test_impact_changes.c \
tests/test_test_impact_origins.c \
tests/test_test_impact_git.c \
tests/test_test_impact_tree.c \
tests/test_test_impact_inventory.c \
tests/test_test_impact_classify.c \
tests/test_test_impact_source.c \
tests/test_test_impact_seed.c \
tests/test_test_impact_engine.c \
tests/test_test_impact_artifact.c \
tests/test_test_impact_inventory_errors.c \
tests/test_test_impact_inventory_support.c \
tests/test_test_impact_runner_filter.c \
tests/test_test_impact_runner_filter_fixture.c \
tests/test_test_impact_runner_filter_process.c \
tests/test_index_supervisor.c
TEST_DAEMON_SRCS = \
@@ -676,12 +738,23 @@ TEST_DISCOVER_SRCS = \
tests/test_language.c \
tests/test_userconfig.c \
tests/test_gitignore.c \
tests/test_gitignore_checked.c \
tests/if_native_git_path.c \
tests/test_inventory_filter.c \
tests/test_inventory_filter_cases.c \
tests/test_inventory_filter_errors.c \
tests/test_inventory_filter_limits.c \
tests/test_inventory_filter_native.c \
tests/test_inventory_filter_native_fixture.c \
tests/test_inventory_filter_native_platform.c \
tests/test_inventory_filter_structure_extra.c \
tests/test_inventory_filter_support.c \
tests/test_git_context.c \
tests/test_discover.c
TEST_GRAPH_BUFFER_SRCS = tests/test_graph_buffer.c
TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.c tests/test_importance.c tests/test_cross_repo.c tests/test_fqn.c tests/test_route_canon.c tests/test_path_alias.c tests/test_configlink.c tests/test_doclinks.c tests/test_infrascan.c tests/test_worker_pool.c tests/test_parallel.c tests/test_index_resilience.c tests/test_index_format.c tests/test_call_reference_contract.c tests/repro/repro_call_scope_usages.c tests/repro/repro_call_argument_usages.c tests/repro/repro_reference_precision.c tests/repro/repro_lexical_binding_precision.c tests/repro/repro_call_argument_matrix_a.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_behaviors.c tests/repro/repro_language_registry.c tests/repro/repro_call_node_manifest.c tests/repro/repro_lsp_ordered_signatures.c tests/repro/repro_lsp_ordered_local.c tests/repro/repro_ts_overload_return_chains.c tests/repro/repro_harness_cleanup.c tests/repro/repro_runner_filter.c
TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.c tests/test_pipeline_conventions.c tests/test_pipeline_frozen.c tests/test_importance.c tests/test_cross_repo.c tests/test_fqn.c tests/test_route_canon.c tests/test_path_alias.c tests/test_configlink.c tests/test_doclinks.c tests/test_infrascan.c tests/test_worker_pool.c tests/test_parallel.c tests/test_index_resilience.c tests/test_index_format.c tests/test_call_reference_contract.c tests/repro/repro_call_scope_usages.c tests/repro/repro_call_argument_usages.c tests/repro/repro_reference_precision.c tests/repro/repro_lexical_binding_precision.c tests/repro/repro_call_argument_matrix_a.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_behaviors.c tests/repro/repro_language_registry.c tests/repro/repro_call_node_manifest.c tests/repro/repro_lsp_ordered_signatures.c tests/repro/repro_lsp_ordered_local.c tests/repro/repro_ts_overload_return_chains.c tests/repro/repro_harness_cleanup.c tests/repro/repro_runner_filter.c
TEST_WATCHER_SRCS = tests/test_watcher.c
@@ -720,7 +793,7 @@ TEST_JAVA_LSP_SRCS = tests/test_java_lsp.c tests/test_java_lsp_coverage.c
TEST_KOTLIN_LSP_SRCS = tests/test_kotlin_lsp.c
TEST_RUST_LSP_SRCS = tests/test_rust_lsp.c
TEST_INTEGRATION_SRCS = tests/test_integration.c tests/test_incremental.c tests/test_lang_contract.c tests/test_edge_imports.c tests/test_edge_structural.c tests/test_lsp_resolution_probe.c tests/test_node_creation_probe.c tests/test_edge_types_probe.c tests/test_convergence_probe.c tests/test_matrix_known_classes.c tests/test_matrix_new_constructs.c tests/test_grammar_probe_a.c tests/test_grammar_probe_b.c tests/test_grammar_probe_c.c tests/test_grammar_probe_d.c tests/test_grammar_probe_e.c tests/test_grammar_probe_f.c tests/test_grammar_probe_g.c
TEST_INTEGRATION_SRCS = tests/test_integration.c tests/test_incremental.c tests/test_lang_contract.c tests/test_edge_imports.c tests/test_edge_structural.c tests/test_lsp_resolution_probe.c tests/test_node_creation_probe.c tests/test_edge_types_probe.c tests/test_convergence_probe.c tests/test_matrix_known_classes.c tests/test_matrix_new_constructs.c tests/test_grammar_probe_a.c tests/test_grammar_probe_b.c tests/test_grammar_probe_c.c tests/test_grammar_probe_d.c tests/test_grammar_probe_e.c tests/test_grammar_probe_f.c tests/test_grammar_probe_g.c tests/test_conditional_variants.c tests/test_spawns.c
TEST_TRACES_SRCS = tests/test_traces.c
@@ -860,7 +933,7 @@ PP_OBJ_TSAN = $(BUILD_DIR)/tsan_preprocessor.o
# ── Targets ──────────────────────────────────────────────────────
.PHONY: test test-par test-repro test-foundation test-tsan test-daemon-smoke cbm cbm-with-ui frontend embed clean-c lint lint-tidy lint-tidy-diff lint-cppcheck lint-format security fuzz
.PHONY: test-coverage-flush test test-par test-repro test-foundation test-tsan test-daemon-smoke test-runner-cov cbm-cov cbm cbm-with-ui frontend embed clean-c lint lint-tidy lint-tidy-diff lint-cppcheck lint-format security fuzz
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
@@ -899,7 +972,7 @@ $(BUILD_DIR)/ts_runtime.o: $(TS_RUNTIME_DEPS) | $(BUILD_DIR)
$(CC) $(GRAMMAR_CFLAGS_TEST) -c -o $@ $<
$(BUILD_DIR)/lsp_all.o: $(LSP_UNITY_DEPS) | $(BUILD_DIR)
$(CC) $(GRAMMAR_CFLAGS_TEST) $(SANITIZE) -c -o $@ $<
$(CC) $(GRAMMAR_CFLAGS_TEST) $(SANITIZE) $(TEST_COVERAGE_FLAGS) -c -o $@ $<
$(BUILD_DIR)/preprocessor.o: $(CBM_DIR)/preprocessor.cpp | $(BUILD_DIR)
$(CXX) $(CXXFLAGS_TEST) -w -I$(CBM_DIR)/vendored -c -o $@ $<
@@ -1150,7 +1223,7 @@ $(BUILD_DIR)/prod_ts_runtime.o: $(TS_RUNTIME_DEPS) $(BUILD_DIR)/.build-config |
$(CC) $(GRAMMAR_CFLAGS) -c -o $@ $<
$(BUILD_DIR)/prod_lsp_all.o: $(LSP_UNITY_DEPS) $(BUILD_DIR)/.build-config | $(BUILD_DIR)
$(CC) $(GRAMMAR_CFLAGS) -c -o $@ $<
$(CC) $(GRAMMAR_CFLAGS) $(TEST_COVERAGE_FLAGS) -c -o $@ $<
$(BUILD_DIR)/prod_preprocessor.o: $(CBM_DIR)/preprocessor.cpp $(BUILD_DIR)/.build-config | $(BUILD_DIR)
$(CXX) $(CXXFLAGS_PROD) -w -I$(CBM_DIR)/vendored -c -o $@ $<
@@ -1188,7 +1261,7 @@ MAIN_SRC = src/main.c
# for trap debugging -- silently re-run the previously instrumented binary,
# which during this bisect produced a "crashes without sanitizers too" result
# that was pure artifact and sent the investigation down the wrong path.
BUILD_CONFIG_SIG := TEST_SEAMS=$(TEST_SEAMS)|CFLAGS_EXTRA=$(CFLAGS_EXTRA)|SANITIZE=$(SANITIZE)|MEMWASTE=$(MEMWASTE)|MEMWASTE_ACCESS=$(MEMWASTE_ACCESS)|MEMWASTE_PROFILE=$(MEMWASTE_PROFILE)
BUILD_CONFIG_SIG := TEST_SEAMS=$(TEST_SEAMS)|CFLAGS_EXTRA=$(CFLAGS_EXTRA)|SANITIZE=$(SANITIZE)|MEMWASTE=$(MEMWASTE)|MEMWASTE_ACCESS=$(MEMWASTE_ACCESS)|MEMWASTE_PROFILE=$(MEMWASTE_PROFILE)|TEST_COVERAGE=$(TEST_COVERAGE)
.PHONY: build-config-check
build-config-check:
$(BUILD_DIR)/.build-config: build-config-check | $(BUILD_DIR)
@@ -1384,6 +1457,84 @@ test-lsan:
cd $(CURDIR) && ASAN_OPTIONS="detect_leaks=1:halt_on_error=1" \
./build/lsan/test-runner $(LSAN_SUITES)
# ── Per-test coverage build (test-impact map) ───────────────────
# The instrumented runner and product binary (TEST_COVERAGE=1 above), in their
# own build directory. Homebrew LLVM rather than Apple clang: the raw profile
# format is versioned with the compiler, so llvm-profdata and llvm-cov have to
# come from the toolchain that built the binary.
# LLVM_PROFILE_FILE=/dev/null CBM_TEST_COVERAGE_DIR=<dir> \
# build/cov/test-runner [suite...]
# leaves one profile set per test (tests/test_main.c). The runner names every
# profile itself, but the profile runtime creates its default file before main
# runs: started without LLVM_PROFILE_FILE it leaves an empty default.profraw in
# the working directory.
# No C suite runs the product binary -- they re-exec the runner itself. The
# shell process regressions do, and they find the instrumented one the way they
# find any other: CBM_TEST_BINARY=build/cov/codebase-memory-mcp, with
# LLVM_PROFILE_FILE set by whoever collects the profiles.
COV_LLVM_BIN ?= /opt/homebrew/opt/llvm/bin
COV_BUILD_DIR ?= build/cov
COV_MAKE = $(MAKE) -f Makefile.cbm CC=$(COV_LLVM_BIN)/clang CXX=$(COV_LLVM_BIN)/clang++ \
BUILD_DIR=$(COV_BUILD_DIR) SANITIZE= TEST_COVERAGE=1
test-runner-cov:
$(COV_MAKE) $(COV_BUILD_DIR)/test-runner
cbm-cov:
$(COV_MAKE) $(COV_BUILD_DIR)/codebase-memory-mcp TEST_SEAMS=1
# Genuine LLVM write failures must fail the parent coverage run.
test-coverage-flush: test-runner-cov
@evidence_dir=$$(mktemp -d "$(COV_BUILD_DIR)/coverage-flush.XXXXXX") && \
printf 'Coverage flush evidence: %s/proof\n' "$$evidence_dir" && \
python3 tests/test_coverage_flush.py \
--runner "$(COV_BUILD_DIR)/test-runner" \
--llvm-profdata "$(COV_LLVM_BIN)/llvm-profdata" \
--output-dir "$$evidence_dir/proof"
.PHONY: test-coverage-children
# Genuine child profile/marker and setup-reducer controls; evidence is retained.
test-coverage-children: test-runner-cov
@evidence_dir=$$(mktemp -d "$(COV_BUILD_DIR)/coverage-children.XXXXXX") && \
printf 'Coverage child evidence: %s/proof\n' "$$evidence_dir" && \
python3 tests/test_coverage_children.py \
--runner "$(COV_BUILD_DIR)/test-runner" \
--llvm-profdata "$(COV_LLVM_BIN)/llvm-profdata" \
--reducer scripts/test-impact/coverage-map.py \
--output-dir "$$evidence_dir/proof"
.PHONY: test-coverage-parent-failure
# Positive default plus genuine owned EMFILE; all evidence is retained.
test-coverage-parent-failure: test-runner-cov
@evidence_dir=$$(mktemp -d "$(COV_BUILD_DIR)/coverage-parent-failure.XXXXXX") && \
printf 'Coverage parent failure evidence: %s/proof\n' "$$evidence_dir" && \
python3 tests/test_coverage_parent_failure.py \
--runner "$(COV_BUILD_DIR)/test-runner" \
--llvm-profdata "$(COV_LLVM_BIN)/llvm-profdata" \
--reducer scripts/test-impact/coverage-map.py \
--output-dir "$$evidence_dir/proof"
.PHONY: test-coverage-terminal
# A readable stale terminal profile cannot hide a failed parent write.
test-coverage-terminal: test-runner-cov
@evidence_dir=$$(mktemp -d "$(COV_BUILD_DIR)/coverage-terminal.XXXXXX") && \
printf 'Coverage terminal evidence: %s/proof\n' "$$evidence_dir" && \
python3 tests/test_coverage_terminal.py \
--runner "$(COV_BUILD_DIR)/test-runner" \
--llvm-profdata "$(COV_LLVM_BIN)/llvm-profdata" \
--helper tests/test_coverage_children.py \
--reducer scripts/test-impact/coverage-map.py \
--output-dir "$$evidence_dir/proof"
.PHONY: test-coverage-live-v2
# Native full-identity transport remains explicitly unadmitted; evidence is retained.
test-coverage-live-v2: test-runner-cov
@evidence_dir=$$(mktemp -d "$(COV_BUILD_DIR)/coverage-live-v2.XXXXXX") && \
printf 'Coverage live-v2 evidence: %s/proof\n' "$$evidence_dir" && \
PYTHONDONTWRITEBYTECODE=1 python3 tests/test_coverage_live_v2.py \
--collector scripts/test-impact/coverage-map.py \
--runner "$(COV_BUILD_DIR)/test-runner" \
--llvm-profdata "$(COV_LLVM_BIN)/llvm-profdata" \
--output "$$evidence_dir/proof" $(if $(COV_LIVE_V2_CASE),--case "$(COV_LIVE_V2_CASE)")
# ── Coverage-guided fuzzing (libFuzzer) ─────────────────────────
# One binary, three targets (tests/fuzz/cbm_fuzz.c): the per-file extractor,
# the Cypher parser, the TOML/YAML/JSON config editors. ASan+UBSan as in the
+2 -1
View File
@@ -229,6 +229,7 @@ The install script placed beside the binary is **reported, not deleted** — uni
- `USAGE` — an identifier is used, but a unique callable target is not proven (including ambiguous or complex expressions)
- `IMPORTS`, `DEFINES`, `IMPLEMENTS`, `INHERITS`
- `HTTP_CALLS`, `ASYNC_CALLS` (cross-service)
- `SPAWNS` — the caller starts another program (`subprocess.run`, `exec.Command`, `posix_spawn`, `Process.Start`, ...); the target is a `Process` node named after the program, or `<dynamic>` when the program is not a literal
- `EMITS`, `LISTENS_ON` (channels)
- `DATA_FLOWS` with arg-to-param mapping + field access chains
- `SIMILAR_TO` (MinHash + LSH near-clone detection, Jaccard scored)
@@ -740,7 +741,7 @@ mode. `index_status` keeps describing the published graph and its freshness.
### Edge Types
`CONTAINS_PACKAGE`, `CONTAINS_FOLDER`, `CONTAINS_FILE`, `DEFINES`, `DEFINES_METHOD`, `IMPORTS`, `CALLS`, `CALL_REFERENCE`, `HTTP_CALLS`, `ASYNC_CALLS`, `IMPLEMENTS`, `HANDLES`, `USAGE`, `CONFIGURES`, `REFERENCES_FILE`, `WRITES`, `MEMBER_OF`, `TESTS`, `USES_TYPE`, `FILE_CHANGES_WITH`
`CONTAINS_PACKAGE`, `CONTAINS_FOLDER`, `CONTAINS_FILE`, `DEFINES`, `DEFINES_METHOD`, `IMPORTS`, `CALLS`, `CALL_REFERENCE`, `HTTP_CALLS`, `ASYNC_CALLS`, `SPAWNS`, `IMPLEMENTS`, `HANDLES`, `USAGE`, `CONFIGURES`, `REFERENCES_FILE`, `WRITES`, `MEMBER_OF`, `TESTS`, `USES_TYPE`, `FILE_CHANGES_WITH`
### Qualified Names
+140 -6
View File
@@ -22,6 +22,7 @@
#include "lsp/rust_lsp.h"
#include "preprocessor.h"
#include "sql_values.h" // #1735: literal INSERT rows kept out of the SQL parse
#include "cpp_branch_views.h"
#include "foundation/compat.h"
#include "foundation/compat_fs.h" // cbm_fopen — crash-supervisor per-file marker write
#include "foundation/hash_table.h" // CBMHashTable — crash-supervisor quarantine set
@@ -2146,13 +2147,110 @@ static bool cbm_sql_values_exclusion_on(const char *rel_path) {
return true;
}
/* Drop the entries a branch view re-extracted from code outside the
* conditionals: a definition with a QN the file already has at the same line,
* an import with the same name and path. */
static void cpp_view_drop_seen(CBMFileResult *r, int defs_before, int imports_before) {
int w = defs_before;
for (int i = defs_before; i < r->defs.count; i++) {
const CBMDefinition *d = &r->defs.items[i];
bool seen = false;
for (int j = 0; j < w && !seen; j++) {
const CBMDefinition *e = &r->defs.items[j];
seen = e->start_line == d->start_line && e->qualified_name && d->qualified_name &&
strcmp(e->qualified_name, d->qualified_name) == 0;
}
if (!seen) {
r->defs.items[w++] = *d;
}
}
r->defs.count = w;
w = imports_before;
for (int i = imports_before; i < r->imports.count; i++) {
const CBMImport *m = &r->imports.items[i];
bool seen = false;
for (int j = 0; j < w && !seen; j++) {
const CBMImport *e = &r->imports.items[j];
seen = e->local_name && m->local_name && e->module_path && m->module_path &&
strcmp(e->local_name, m->local_name) == 0 &&
strcmp(e->module_path, m->module_path) == 0;
}
if (!seen) {
r->imports.items[w++] = *m;
}
}
r->imports.count = w;
}
/* Haskell: the scanner parses the first branch of an #if group and swallows
* the rest up to #endif as one token, so what is written once per
* configuration (a definition, an import, a call in the other branch) was
* seen in its first variant only. Every further branch is parsed as its own
* view (cpp_branch_views.h: same bytes and lines, the other branches and the
* conditional lines blanked) and extracted into the same result, under the
* raw parse's budget. A definition at another line under a QN the file
* already has is that definition's variant; the graph keeps one identity with
* both spans. Calls and usages outside the conditionals come back as
* duplicates, which the pipeline dedups by caller and callee. The raw parse
* alone decides the file's parse status. */
static void extract_cpp_branch_views(const CBMExtractCtx *raw, const TSLanguage *ts_lang,
int64_t timeout_micros) {
int views = cbm_cpp_branch_view_count(raw->arena, raw->source, raw->source_len);
for (int v = 1; v <= views; v++) {
char *view = cbm_cpp_branch_view(raw->arena, raw->source, raw->source_len, v);
TSParser *parser = view ? get_thread_parser(ts_lang, raw->language) : NULL;
if (!parser) {
return;
}
ts_parser_reset(parser);
CBMStringInput input = {view, (uint32_t)raw->source_len};
TSInput ts_input = {&input, cbm_string_read, TSInputEncodingUTF8, NULL};
TSParseOptions opts = {0};
CBMParseBudget budget = {0}; // cppcheck-suppress unreadVariable
if (timeout_micros > 0) {
uint64_t budget_ns = (uint64_t)timeout_micros * USEC_TO_NSEC;
budget.cpu_deadline_ns = cbm_thread_cpu_time_ns() + budget_ns;
budget.wall_ceiling_ns = now_ns() + budget_ns * CBM_PARSE_WALL_CEILING_FACTOR;
opts.payload = &budget;
opts.progress_callback = cbm_timeout_cb;
}
TSTree *tree = ts_parser_parse_with_options(parser, NULL, ts_input, opts);
if (!tree) {
return;
}
CBMExtractCtx vctx = {
.arena = raw->arena,
.scratch = raw->scratch,
.result = raw->result,
.source = view,
.source_len = raw->source_len,
.language = raw->language,
.project = raw->project,
.rel_path = raw->rel_path,
.module_qn = raw->module_qn,
.root = ts_tree_root_node(tree),
.macro_table = raw->macro_table,
.return_type_table = raw->return_type_table,
.walk_budget_nodes = cbm_walk_max_nodes(),
};
int defs_before = raw->result->defs.count;
int imports_before = raw->result->imports.count;
cbm_extract_definitions(&vctx);
cbm_extract_imports(&vctx);
cbm_extract_unified(&vctx);
cpp_view_drop_seen(raw->result, defs_before, imports_before);
ts_tree_delete(tree);
}
}
static CBMFileResult *extract_file_ex_body(const char *source, int source_len, CBMLanguage language,
const char *project, const char *rel_path,
int64_t timeout_micros, const char **extra_defines,
const char **include_paths,
const CBMMacroTable *macro_table,
const CBMReturnTypeTable *return_type_table,
CBMArena *scratch) {
CBMArena *scratch,
const cbm_test_declarations_t *test_declarations) {
// Allocate result on heap (arena inside for all string data)
CBMFileResult *result = cbm_result_alloc();
if (!result) {
@@ -2161,6 +2259,9 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
cbm_work_arena_take(&result->arena);
CBMArena *a = &result->arena;
if (!cbm_test_declarations_validate(result, test_declarations)) {
return result;
}
/* Crash-quarantine hard guard (Stage 3c): a file the supervisor pinned as a
* crasher must NEVER be parsed again. Return a clean empty result BEFORE the
@@ -2339,13 +2440,19 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
.macro_table = macro_table,
.return_type_table = return_type_table,
.walk_budget_nodes = cbm_walk_max_nodes(),
.test_declarations = test_declarations,
.test_declarations_raw_source = true,
};
// Run extractors: defs + imports use separate walks (unique recursion patterns),
// then a single unified cursor walk handles the remaining 7 extractors.
cbm_extract_definitions(&ctx);
cbm_test_declarations_finish(&ctx);
cbm_extract_imports(&ctx);
cbm_extract_unified(&ctx);
if (cbm_lang_needs_cpp_branch_views(language)) {
extract_cpp_branch_views(&ctx, ts_lang, timeout_micros);
}
result->tree_nodes = ts_node_descendant_count(root);
result->walk_nodes_visited = ctx.walk_nodes_visited;
if (ctx.walk_budget_exhausted) {
@@ -2378,8 +2485,20 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
cbm_run_go_lsp(a, result, source, source_len, root);
}
if (language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA) {
cbm_run_c_lsp(a, result, source, source_len, root, language != CBM_LANG_C,
CBM_SOURCE_ORIGIN_RAW);
if (!cbm_run_c_lsp_with_test_owners(a, result, source, source_len, root,
language != CBM_LANG_C, CBM_SOURCE_ORIGIN_RAW,
result)) {
result->has_error = true;
if (result->test_declarations_status == CBM_TEST_EXTRACT_OK) {
result->test_declarations_status = CBM_TEST_EXTRACT_UNSUPPORTED_FORM;
result->test_declaration_index = -1;
result->test_declaration_line = 0;
result->error_msg =
cbm_arena_strdup(a, "configured owner source identity mismatch");
if (!result->error_msg)
result->test_declarations_status = CBM_TEST_EXTRACT_OOM;
}
}
}
if (language == CBM_LANG_PHP) {
cbm_run_php_lsp(a, result, source, source_len, root);
@@ -2490,6 +2609,10 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
.rel_path = rel_path,
.module_qn = result->module_qn,
.root = pp_root,
/* Preset switches still apply, but expanded bytes
* cannot acquire a raw configured definition identity. */
.test_declarations = test_declarations,
.test_declarations_raw_source = false,
};
// Re-run unified extraction on expanded source.
// This adds macro-expanded calls; duplicates with original calls are
@@ -3051,6 +3174,16 @@ CBMFileResult *cbm_extract_file_ex(const char *source, int source_len, CBMLangua
int64_t timeout_micros, const char **extra_defines,
const char **include_paths, const CBMMacroTable *macro_table,
const CBMReturnTypeTable *return_type_table) {
return cbm_extract_file_ex_with_tests(source, source_len, language, project, rel_path,
timeout_micros, extra_defines, include_paths, macro_table,
return_type_table, NULL);
}
CBMFileResult *cbm_extract_file_ex_with_tests(
const char *source, int source_len, CBMLanguage language, const char *project,
const char *rel_path, int64_t timeout_micros, const char **extra_defines,
const char **include_paths, const CBMMacroTable *macro_table,
const CBMReturnTypeTable *return_type_table, const cbm_test_declarations_t *test_declarations) {
CBMArena scratch;
if (tl_scratch_live && tl_scratch_slot) {
scratch = *tl_scratch_slot;
@@ -3059,9 +3192,9 @@ CBMFileResult *cbm_extract_file_ex(const char *source, int source_len, CBMLangua
} else {
cbm_arena_init_lazy(&scratch, CBM_EXTRACT_SCRATCH_BLOCK);
}
CBMFileResult *result = extract_file_ex_body(source, source_len, language, project, rel_path,
timeout_micros, extra_defines, include_paths,
macro_table, return_type_table, &scratch);
CBMFileResult *result = extract_file_ex_body(
source, source_len, language, project, rel_path, timeout_micros, extra_defines,
include_paths, macro_table, return_type_table, &scratch, test_declarations);
/* !tl_scratch_live: a nested extraction (an embedded language inside this
* file) may already have parked its own; never overwrite it. */
/* Kept up to CBM_EXTRACT_SCRATCH_KEEP_BYTES, grown blocks included: the
@@ -3082,6 +3215,7 @@ CBMFileResult *cbm_extract_file_ex(const char *source, int source_len, CBMLangua
} else {
cbm_arena_destroy(&scratch);
}
cbm_test_declarations_degrade(result);
return result;
}
+85
View File
@@ -17,6 +17,27 @@ TSNode cbm_ts_child_by_field_name(TSNode node, const char *name, uint32_t name_l
* (TS_FIELD("body") expands to "body", 4), which must expand before the call. */
#define ts_node_child_by_field_name(...) cbm_ts_child_by_field_name(__VA_ARGS__)
/* Immutable, explicitly supplied test-declaration snapshot. */
typedef struct cbm_test_declarations cbm_test_declarations_t;
typedef enum {
CBM_TEST_ROLE_NONE = 0,
CBM_TEST_ROLE_CASE,
CBM_TEST_ROLE_SUITE
} CBMTestDefinitionRole;
typedef enum {
CBM_TEST_EXTRACT_OK = 0,
CBM_TEST_EXTRACT_UNSUPPORTED_LANGUAGE,
CBM_TEST_EXTRACT_UNSUPPORTED_PRESET,
CBM_TEST_EXTRACT_UNSUPPORTED_NAME,
CBM_TEST_EXTRACT_MISSING_ARGUMENT,
CBM_TEST_EXTRACT_UNSUPPORTED_ARGUMENT,
CBM_TEST_EXTRACT_AMBIGUOUS,
CBM_TEST_EXTRACT_UNSUPPORTED_FORM,
CBM_TEST_EXTRACT_OOM
} CBMTestExtractStatus;
// Language enum mirrors lang.Language in Go.
// Order must match lang_specs.c tables.
typedef enum {
@@ -254,6 +275,11 @@ typedef struct {
* the global namespace and every other language). Per type, because the
* file-level namespace_name records only a file's first namespace. */
const char *decl_namespace;
CBMTestDefinitionRole test_role; /* NONE preserves legacy helper/test-file semantics */
/* Configured raw definitions only; zero for all legacy rows. Exact spans
* are bound to the owning result's source identity before cross-file use. */
uint32_t test_name_start_byte, test_name_end_byte;
uint32_t test_body_start_byte, test_body_end_byte;
} CBMDefinition;
/* Argument captured from a call expression */
@@ -447,6 +473,10 @@ typedef struct {
typedef enum {
CBM_RESOLVED_INVOCATION = 0,
CBM_RESOLVED_CALL_REFERENCE,
/* C-family member access `a.b` / `a->b`: callee_qn is the Field `b` of the
* type of `a`. One row per (caller, field), not per occurrence, and no site
* span: the join is by enclosing function and member name. */
CBM_RESOLVED_FIELD_REFERENCE,
} CBMResolvedKind;
// LSP-resolved invocation/reference: high-confidence type-aware resolution.
@@ -651,6 +681,24 @@ typedef struct CBMFileResult {
* the Rust inner docs (//!). NULL for other languages and undocumented
* files. */
const char *module_doc;
/* A consumed configured definition could not be mapped safely. Only an
* allocation failure (OOM) still aborts publication: any other status is
* degraded per file by cbm_test_declarations_degrade. error_msg is
* arena-owned as usual. */
CBMTestExtractStatus test_declarations_status;
int test_declaration_index; /* meaningful on failure; -1 = preset */
uint32_t test_declaration_line; /* 0 = snapshot preflight */
bool has_test_definition_owners;
/* has_error was raised by a configured-definition issue alone. */
bool test_error_only;
/* The file's configured test forms could not be mapped: it carries no
* configured test roles; the status names why (no pointer: compaction
* and spill relocate only the strings they know). */
bool test_declarations_degraded;
CBMTestExtractStatus test_declarations_degraded_status;
int test_owner_source_len;
CBMLanguage test_owner_language;
char test_owner_source_sha256[65]; /* exact raw source; identity, not authentication */
} CBMFileResult;
// --- Enclosing function cache ---
@@ -742,8 +790,35 @@ typedef struct {
* POD section index. */
void *doc_memo;
void *doc_pod_index;
const cbm_test_declarations_t *test_declarations; /* borrowed for this call only */
bool test_declarations_raw_source;
struct CBMTestDefinitionMatch *test_definition_matches; /* traversal scratch */
int test_definition_match_count;
int test_definition_match_cap;
} CBMExtractCtx;
/* Internal configured-definition seams. No declarations pointer enters a result. */
/* A configured test form the extractor cannot map degrades the FILE, not
* the index (user decision 2026-10-04): its configured test roles, owner
* spans and owner flag are dropped, the status reads OK again, an error the
* issue alone raised is cleared, and test_declarations_degraded keeps the
* reason for a per-file diagnostic. OK and OOM results are left unchanged:
* an allocation failure still fails. Idempotent. */
void cbm_test_declarations_degrade(CBMFileResult *result);
/* The fixed description of a configured-definition status (static). */
const char *cbm_test_extract_status_message(CBMTestExtractStatus status);
bool cbm_test_declarations_validate(CBMFileResult *result,
const cbm_test_declarations_t *declarations);
void cbm_test_declarations_finish(CBMExtractCtx *ctx);
/* Raw AST candidate only: no declaration/configuration decisions. A C split
* invocation uses its adjacent compound body as scope; ordinary definitions
* use their function node. Callers still validate raw bytes before ownership. */
bool cbm_test_definition_candidate(TSNode scope, CBMLanguage language, TSNode *name, TSNode *body);
const char *cbm_test_definition_qn(CBMExtractCtx *ctx, TSNode function);
bool cbm_test_definition_owners_match(const CBMFileResult *owners, const char *source,
int source_len, bool cpp_mode, const char *module_qn);
const char *cbm_test_definition_owner_qn(const CBMFileResult *owners, TSNode function);
// --- Public API ---
// Bind third-party allocators (tree-sitter, sqlite3) to mimalloc as
@@ -846,6 +921,16 @@ CBMFileResult *cbm_extract_file_ex(
const CBMReturnTypeTable *return_type_table // OS return types, or NULL
);
/* Additive immutable-snapshot entry point. NULL preserves legacy behavior.
* Snapshot must outlive this call; returned data is independently result-owned.
* A non-OK result status is an explicit configured-mapping failure, not a
* successful empty inventory. error_msg may be NULL on diagnostic OOM. */
CBMFileResult *cbm_extract_file_ex_with_tests(
const char *source, int source_len, CBMLanguage language, const char *project,
const char *rel_path, int64_t timeout_micros, const char **extra_defines,
const char **include_paths, const CBMMacroTable *macro_table,
const CBMReturnTypeTable *return_type_table, const cbm_test_declarations_t *test_declarations);
// Free all memory associated with a result.
void cbm_free_result(CBMFileResult *result);
+183
View File
@@ -0,0 +1,183 @@
/*
* cpp_branch_views.c — one parse view per #if/#else branch (see the header).
*/
#include "cpp_branch_views.h"
#include <string.h>
enum { CPP_LINE_CODE, CPP_LINE_START, CPP_LINE_ELSE, CPP_LINE_END };
enum { CPP_NEST_MAX = 64 };
bool cbm_lang_needs_cpp_branch_views(CBMLanguage lang) {
return lang == CBM_LANG_HASKELL;
}
static bool cpp_word_char(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
}
// Classify the line at src[pos..end): a conditional directive is a '#' in
// column 0, optional blanks, then the directive word — the same lines the
// Haskell scanner treats as conditionals.
static int cpp_line_kind(const char *src, int pos, int end) {
if (pos >= end || src[pos] != '#') {
return CPP_LINE_CODE;
}
int p = pos + 1;
while (p < end && (src[p] == ' ' || src[p] == '\t')) {
p++;
}
int w = p;
while (w < end && cpp_word_char(src[w])) {
w++;
}
static const struct {
const char *word;
int kind;
} words[] = {
{"if", CPP_LINE_START}, {"ifdef", CPP_LINE_START}, {"ifndef", CPP_LINE_START},
{"else", CPP_LINE_ELSE}, {"elif", CPP_LINE_ELSE}, {"elifdef", CPP_LINE_ELSE},
{"elifndef", CPP_LINE_ELSE}, {"endif", CPP_LINE_END},
};
size_t n = (size_t)(w - p);
for (size_t i = 0; i < sizeof(words) / sizeof(words[0]); i++) {
if (strlen(words[i].word) == n && memcmp(src + p, words[i].word, n) == 0) {
return words[i].kind;
}
}
return CPP_LINE_CODE;
}
static int cpp_line_end(const char *src, int pos, int len) {
const char *nl = memchr(src + pos, '\n', (size_t)(len - pos));
return nl ? (int)(nl - src) : len;
}
typedef struct {
int group; // groups are numbered in order of their #if line
int branch; // branch the current line is in
bool outer; // the enclosing context is kept
} cpp_frame_t;
typedef struct {
cpp_frame_t frames[CPP_NEST_MAX];
int depth;
int overflow; // groups nested past CPP_NEST_MAX are not tracked
int next;
} cpp_nest_t;
// Track one line. Returns the innermost tracked group when the line is one of
// its #else/#elif lines, else NULL.
static cpp_frame_t *cpp_nest_step(cpp_nest_t *n, int kind, bool active) {
switch (kind) {
case CPP_LINE_START:
if (n->depth < CPP_NEST_MAX && n->overflow == 0) {
n->frames[n->depth++] = (cpp_frame_t){n->next, 0, active};
} else {
n->overflow++;
}
n->next++;
return NULL;
case CPP_LINE_ELSE:
if (n->overflow > 0 || n->depth == 0) {
return NULL;
}
n->frames[n->depth - 1].branch++;
return &n->frames[n->depth - 1];
case CPP_LINE_END:
if (n->overflow > 0) {
n->overflow--;
} else if (n->depth > 0) {
n->depth--;
}
return NULL;
default:
return NULL;
}
}
// Branch count per group. Returns the group count, or -1 on allocation failure.
static int cpp_group_branches(CBMArena *a, const char *src, int len, int **out) {
int groups = 0;
for (int pos = 0; pos < len;) {
int end = cpp_line_end(src, pos, len);
groups += cpp_line_kind(src, pos, end) == CPP_LINE_START;
pos = end + 1;
}
*out = NULL;
if (groups == 0) {
return 0;
}
int *branches = cbm_arena_alloc(a, sizeof(int) * (size_t)groups);
if (!branches) {
return -1;
}
for (int g = 0; g < groups; g++) {
branches[g] = 1;
}
cpp_nest_t nest = {.depth = 0};
for (int pos = 0; pos < len;) {
int end = cpp_line_end(src, pos, len);
cpp_frame_t *frame = cpp_nest_step(&nest, cpp_line_kind(src, pos, end), true);
if (frame) {
branches[frame->group] = frame->branch + 1;
}
pos = end + 1;
}
*out = branches;
return groups;
}
int cbm_cpp_branch_view_count(CBMArena *a, const char *src, int len) {
int *branches = NULL;
int groups = cpp_group_branches(a, src, len, &branches);
int most = 1;
for (int g = 0; g < groups; g++) {
if (branches[g] > most) {
most = branches[g];
}
}
int views = most - 1;
return views > CBM_CPP_BRANCH_VIEWS_MAX ? CBM_CPP_BRANCH_VIEWS_MAX : views;
}
// The current line belongs to the view: every enclosing group sits in the
// branch the view keeps.
static bool cpp_view_keeps(const cpp_nest_t *n, const int *branches, int view) {
if (n->depth == 0) {
return true;
}
const cpp_frame_t *top = &n->frames[n->depth - 1];
int nb = branches[top->group];
int kept = 0;
if (nb >= 2) {
kept = view < nb - 1 ? view : nb - 1;
}
return top->outer && top->branch == kept;
}
char *cbm_cpp_branch_view(CBMArena *a, const char *src, int len, int view) {
int *branches = NULL;
int groups = cpp_group_branches(a, src, len, &branches);
char *buf = groups >= 0 ? cbm_arena_alloc(a, (size_t)len + 1) : NULL;
if (!buf) {
return NULL;
}
memcpy(buf, src, (size_t)len);
buf[len] = '\0';
cpp_nest_t nest = {.depth = 0};
for (int pos = 0; pos < len;) {
int end = cpp_line_end(src, pos, len);
bool kept = cpp_view_keeps(&nest, branches, view);
int kind = cpp_line_kind(src, pos, end);
cpp_nest_step(&nest, kind, kept);
if (kind != CPP_LINE_CODE || !kept) {
for (int i = pos; i < end; i++) {
buf[i] = buf[i] == '\r' ? '\r' : ' ';
}
}
pos = end + 1;
}
return buf;
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef CBM_CPP_BRANCH_VIEWS_H
#define CBM_CPP_BRANCH_VIEWS_H
#include "cbm.h"
/*
* Branch views of a source whose grammar parses only the first branch of an
* #if/#else group (the Haskell scanner swallows everything from #else to
* #endif as one token, so a definition written once per configuration is seen
* in its first variant only).
*
* A view keeps the source byte for byte and line for line: the branch it
* selects in every group stays, the other branches and all conditional lines
* (#if/#ifdef/#ifndef/#else/#elif.../#endif) become spaces, newlines stay. So
* every span extracted from a view is a raw-source span.
*
* View v (1-based) selects branch min(v, branches - 1) of a group with at
* least two branches, and the only branch of a group without one.
*/
#define CBM_CPP_BRANCH_VIEWS_MAX 3
// True when `lang`'s grammar parses only the first branch of a conditional.
bool cbm_lang_needs_cpp_branch_views(CBMLanguage lang);
// Number of views the source needs (0 when no group has a second branch),
// at most CBM_CPP_BRANCH_VIEWS_MAX.
int cbm_cpp_branch_view_count(CBMArena *a, const char *src, int len);
// View `view` (1..count) as an arena buffer of exactly `len` bytes plus a NUL,
// or NULL on allocation failure.
char *cbm_cpp_branch_view(CBMArena *a, const char *src, int len, int view);
#endif
File diff suppressed because it is too large Load Diff
+32 -9
View File
@@ -13,6 +13,8 @@
enum {
USE_PREFIX_LEN = 4, /* strlen("use ") */
MIN_WOLFRAM_CHILDREN = 2,
/* Nested `#if` branches followed while collecting C includes. */
C_INCLUDE_NEST_LIMIT = 64,
SECOND_IDX = 1,
};
@@ -45,7 +47,7 @@ static void parse_lisp_imports(CBMExtractCtx *ctx);
static void parse_starlark_imports(CBMExtractCtx *ctx);
static void parse_tcl_imports(CBMExtractCtx *ctx);
static void parse_teal_imports(CBMExtractCtx *ctx);
static void parse_zsh_imports(CBMExtractCtx *ctx);
static void parse_shell_source_imports(CBMExtractCtx *ctx);
static void parse_css_imports(CBMExtractCtx *ctx);
static void parse_html_imports(CBMExtractCtx *ctx);
static void parse_cmake_imports(CBMExtractCtx *ctx);
@@ -663,10 +665,22 @@ static char *strip_angle_brackets(CBMArena *a, char *path) {
return path;
}
static void parse_c_imports(CBMExtractCtx *ctx) {
/* Node kinds that only group file-level items: the branches of `#if` /
* `#ifdef` (an include guard is one) and the body of `extern "C" { ... }`. */
static bool is_c_include_container(const char *kind) {
return strcmp(kind, "preproc_if") == 0 || strcmp(kind, "preproc_ifdef") == 0 ||
strcmp(kind, "preproc_else") == 0 || strcmp(kind, "preproc_elif") == 0 ||
strcmp(kind, "preproc_elifdef") == 0 || strcmp(kind, "linkage_specification") == 0 ||
strcmp(kind, "declaration_list") == 0;
}
/* Collect the includes among `parent`'s children, descending into
* preprocessor branches: every header keeps its includes inside its include
* guard, so the top level alone gave a guarded header no imports at all. */
static void parse_c_imports_in(CBMExtractCtx *ctx, TSNode parent, int depth) {
CBMArena *a = ctx->arena;
TSTreeCursor cursor = ts_tree_cursor_new(ctx->root);
TSTreeCursor cursor = ts_tree_cursor_new(parent);
if (!ts_tree_cursor_goto_first_child(&cursor)) {
ts_tree_cursor_delete(&cursor);
return;
@@ -675,6 +689,9 @@ static void parse_c_imports(CBMExtractCtx *ctx) {
TSNode node = ts_tree_cursor_current_node(&cursor);
const char *kind = ts_node_type(node);
if (strcmp(kind, "preproc_include") != 0 && strcmp(kind, "preproc_import") != 0) {
if (depth < C_INCLUDE_NEST_LIMIT && is_c_include_container(kind)) {
parse_c_imports_in(ctx, node, depth + SKIP_ONE);
}
continue;
}
@@ -695,6 +712,10 @@ static void parse_c_imports(CBMExtractCtx *ctx) {
ts_tree_cursor_delete(&cursor);
}
static void parse_c_imports(CBMExtractCtx *ctx) {
parse_c_imports_in(ctx, ctx->root, 0);
}
// --- Ruby imports ---
// call nodes: require("X"), require_relative("X")
@@ -2166,10 +2187,13 @@ static void parse_teal_imports(CBMExtractCtx *ctx) {
}
}
// --- Zsh imports ---
// --- Bash / Zsh imports ---
// source file / . file — `command` nodes whose command_name is "source" or ".".
// The argument field carries the sourced path.
static void parse_zsh_imports(CBMExtractCtx *ctx) {
// The argument field carries the sourced path. Every other command is a call,
// not an import: read as an import, `set -e` imported "set", which the import
// resolver then bound to whatever project symbol is named set (a TSX method),
// and every `set` call in the script followed it.
static void parse_shell_source_imports(CBMExtractCtx *ctx) {
CBMArena *a = ctx->arena;
TSNodeStack stack;
ts_nstack_init(&stack, ctx, CBM_SZ_512);
@@ -3028,8 +3052,7 @@ void cbm_extract_imports(CBMExtractCtx *ctx) {
parse_generic_imports(ctx, "call");
break;
case CBM_LANG_BASH:
// source/. commands
parse_generic_imports(ctx, "command");
parse_shell_source_imports(ctx);
break;
case CBM_LANG_ZIG:
parse_zig_imports(ctx);
@@ -3099,7 +3122,7 @@ void cbm_extract_imports(CBMExtractCtx *ctx) {
parse_teal_imports(ctx);
break;
case CBM_LANG_ZSH:
parse_zsh_imports(ctx);
parse_shell_source_imports(ctx);
break;
case CBM_LANG_CMAKE:
parse_cmake_imports(ctx);
+20 -4
View File
@@ -918,6 +918,13 @@ static const char *objectscript_get_method_qn(CBMExtractCtx *ctx, TSNode node,
// Compute function QN for scope tracking (mirrors cbm_enclosing_func_qn logic).
static const char *compute_func_qn(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
WalkState *state) {
/* Raw configured definitions already own the canonical QN, including
* namespace scope. Never rederive it from the bare macro identifier. */
const char *configured_qn = cbm_test_definition_qn(ctx, node);
if (configured_qn) {
return configured_qn;
}
(void)spec;
if (ctx->language == CBM_LANG_WOLFRAM) {
return compute_wolfram_func_qn(ctx, node);
@@ -1103,9 +1110,13 @@ static const char *compute_func_qn(CBMExtractCtx *ctx, TSNode node, const CBMLan
return cbm_arena_sprintf(ctx->arena, "%s.%s", state->enclosing_class_qn, qn_name);
}
/* Java/Go: directory-based module so this enclosing-func QN matches the def
* QN and the LSP caller_qn (the lsp_resolve join keys on exact equality). */
return cbm_fqn_compute_source_lang(ctx->arena, ctx->project, ctx->rel_path, qn_name,
ctx->language);
* QN and the LSP caller_qn (the lsp_resolve join keys on exact equality).
* C-family platform variants share their platform-neutral QN — the same
* rule extract_func_def applies, so in-body calls source to the def. */
const char *qn = cbm_fqn_compute_source_lang(ctx->arena, ctx->project, ctx->rel_path, qn_name,
ctx->language);
return cbm_platform_variant_qn(ctx->arena, ctx->language, ctx->project, ctx->rel_path, qn_name,
qn, node);
}
// Compute class QN for scope tracking.
@@ -2370,7 +2381,12 @@ static void push_lexical_boundary(TSNode node, WalkState *state, uint32_t depth)
static void push_boundary_scopes(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
WalkState *state, uint32_t depth,
const CBMInvocationDescriptor *invocation) {
if (spec->function_node_types && cbm_kind_in_set(node, spec->function_node_types)) {
const char *configured_body_qn = NULL;
if (ctx->language == CBM_LANG_C && strcmp(ts_node_type(node), "compound_statement") == 0)
configured_body_qn = cbm_test_definition_qn(ctx, node);
if (configured_body_qn) {
(void)push_function_scope(state, depth, configured_body_qn, node);
} else if (spec->function_node_types && cbm_kind_in_set(node, spec->function_node_types)) {
/* OCaml: a nested local `let x = e in ...` is itself a value_definition,
* but the def walk does not descend into function bodies, so it emits no
* node for it. Pushing a func scope here would attribute in-body calls to
+152
View File
@@ -1300,6 +1300,18 @@ const char *cbm_enclosing_func_qn(CBMArena *a, TSNode node, CBMLanguage lang, co
// --- Cached enclosing function QN ---
const char *cbm_enclosing_func_qn_cached(CBMExtractCtx *ctx, TSNode node) {
if (ctx->test_definition_match_count > 0) {
const char **function_kinds = func_kinds_for_lang(ctx->language);
for (TSNode current = node; !ts_node_is_null(current); current = ts_node_parent(current)) {
const char *configured_qn = cbm_test_definition_qn(ctx, current);
if (configured_qn)
return configured_qn;
/* A nested native function owns its own body; do not inherit a
* surrounding configured macro's QN through that boundary. */
if (cbm_kind_in_set(current, function_kinds))
break;
}
}
uint32_t pos = ts_node_start_byte(node);
// Check cache: find a function range that contains this position.
@@ -1666,6 +1678,146 @@ char *cbm_fqn_compute_source_lang(CBMArena *a, const char *project, const char *
return cbm_arena_sprintf(a, "%s.%s", module, name);
}
/* Operating systems and CPU architectures only: a token names a platform, so a
* path segment or stem suffix made of one marks a platform alternative. Vague
* words (generic, default, common, stub) are deliberately absent. */
static const char *const platform_tokens[] = {
"win", "win32", "win64", "windows", "wince", "nt", "posix", "unix",
"linux", "darwin", "mac", "macos", "macosx", "osx", "apple", "ios",
"android", "bsd", "freebsd", "openbsd", "netbsd", "dragonfly", "solaris", "sunos",
"aix", "hpux", "haiku", "fuchsia", "cygwin", "mingw", "msvc", "wasm",
"wasi", "emscripten", "x86", "x86_64", "x64", "amd64", "i386", "i686",
"ia32", "arm", "arm64", "aarch64", "armv7", "riscv", "riscv64", "mips",
"mips64", "ppc", "ppc64", "powerpc", "s390", "s390x", "sparc", "sparc64",
"loongarch", "loongarch64", NULL};
static bool platform_token_eq(const char *s, size_t len) {
for (int i = 0; platform_tokens[i]; i++) {
const char *t = platform_tokens[i];
size_t tl = strlen(t);
if (tl != len) {
continue;
}
size_t k = 0;
while (k < len && tolower((unsigned char)s[k]) == t[k]) {
k++;
}
if (k == len) {
return true;
}
}
return false;
}
/* Length of `stem` without a trailing `_<token>` / `-<token>` / `.<token>`
* platform suffix (the longest one), or `len` when it has none. */
static size_t platform_stem_len(const char *stem, size_t len) {
size_t best = len;
for (size_t sep = 1; sep < len; sep++) {
char c = stem[sep - 1];
if ((c == '_' || c == '-' || c == '.') && platform_token_eq(stem + sep, len - sep)) {
if (sep - 1 < best) {
best = sep - 1;
}
}
}
return best;
}
static bool c_family_variant_lang(CBMLanguage lang) {
return lang == CBM_LANG_C || lang == CBM_LANG_CPP || lang == CBM_LANG_CUDA ||
lang == CBM_LANG_OBJC;
}
static bool func_is_static(TSNode func_node) {
uint32_t n = ts_node_child_count(func_node);
for (uint32_t i = 0; i < n; i++) {
TSNode child = ts_node_child(func_node, i);
if (strcmp(ts_node_type(child), "storage_class_specifier") == 0 &&
ts_node_child_count(child) > 0 &&
strcmp(ts_node_type(ts_node_child(child, 0)), "static") == 0) {
return true;
}
}
return false;
}
const char *cbm_platform_variant_qn(CBMArena *a, CBMLanguage lang, const char *project,
const char *rel_path, const char *name, const char *qn,
TSNode func_node) {
if (!c_family_variant_lang(lang) || !rel_path || !name || !name[0] || !qn ||
ts_node_is_null(func_node) || func_is_static(func_node)) {
return qn;
}
char *plain = cbm_fqn_compute(a, project, rel_path, name);
if (!plain || strcmp(plain, qn) != 0) {
return qn; /* namespaced or otherwise scoped: not a file-scope function */
}
/* Rebuild the path without platform tokens: a directory segment that is
* one is dropped; a filename stem loses its platform suffix, and a stem
* that IS a token leaves the function at its directory. */
size_t len = strlen(rel_path);
char *neutral = (char *)cbm_arena_alloc(a, len + SKIP_ONE);
if (!neutral) {
return qn;
}
size_t out = 0;
bool changed = false;
bool dir_level = false;
const char *seg = rel_path;
while (*seg) {
const char *slash = strchr(seg, '/');
size_t seg_len = slash ? (size_t)(slash - seg) : strlen(seg);
if (slash) {
if (platform_token_eq(seg, seg_len)) {
changed = true;
} else {
memcpy(neutral + out, seg, seg_len);
out += seg_len;
neutral[out++] = '/';
}
seg = slash + SKIP_ONE;
continue;
}
const char *dot = NULL;
for (const char *p = seg + seg_len; p > seg; p--) {
if (p[-1] == '.') {
dot = p - 1;
break;
}
}
size_t stem_len = dot && dot > seg ? (size_t)(dot - seg) : seg_len;
size_t keep = platform_token_eq(seg, stem_len) ? 0 : platform_stem_len(seg, stem_len);
if (keep != stem_len) {
changed = true;
}
if (keep == 0) {
dir_level = true; /* the function lives at the directory */
if (out > 0 && neutral[out - SKIP_ONE] == '/') {
out--;
}
} else {
memcpy(neutral + out, seg, keep);
out += keep;
memcpy(neutral + out, seg + stem_len, seg_len - stem_len);
out += seg_len - stem_len;
}
break;
}
neutral[out] = '\0';
if (!changed) {
return qn;
}
if (dir_level) {
/* No file stem left: the folder form keeps every directory segment
* (cbm_fqn_compute would strip a dotted segment as an extension). */
char *folder = cbm_fqn_folder(a, project ? project : "", neutral);
return folder ? cbm_arena_sprintf(a, "%s.%s", folder, name) : qn;
}
char *variant = cbm_fqn_compute(a, project, neutral, name);
return variant ? variant : qn;
}
char *cbm_fqn_folder(CBMArena *a, const char *project, const char *rel_dir) {
// project.dir1.dir2
size_t proj_len = strlen(project);
+11
View File
@@ -250,6 +250,17 @@ char *cbm_fqn_module_source_lang(CBMArena *a, const char *project, const char *r
char *cbm_fqn_compute_source_lang(CBMArena *a, const char *project, const char *rel_path,
const char *name, CBMLanguage lang);
// C-family platform variants: the same external-linkage function defined in
// files that differ only by a platform token (foo_win.c / foo_posix.c,
// src/unix/fs.c / src/win/fs.c) is ONE function with platform variants — two
// external definitions of one name cannot link into one program, so they are
// alternatives by construction. Returns the platform-neutral QN (the path with
// its platform tokens removed) for a non-static file-scope function of
// C/C++/CUDA/ObjC whose `qn` is the plain file-stem form; otherwise `qn`.
const char *cbm_platform_variant_qn(CBMArena *a, CBMLanguage lang, const char *project,
const char *rel_path, const char *name, const char *qn,
TSNode func_node);
// Folder QN: project.dir_parts
char *cbm_fqn_folder(CBMArena *a, const char *project, const char *rel_dir);
+22
View File
@@ -2737,6 +2737,28 @@ static const CBMLangSpec lang_specs[CBM_LANG_COUNT] = {
_Static_assert(sizeof(lang_specs) / sizeof(lang_specs[0]) == CBM_LANG_COUNT,
"lang_specs array size must match CBM_LANG_COUNT");
/* Conditional-compilation blocks per grammar (the `*_in_field_declaration_list`
* / `*_in_top_level` forms are aliased to these names by the grammars). */
static const char *c_family_conditional_types[] = {
"preproc_if", "preproc_ifdef", "preproc_else", "preproc_elif", "preproc_elifdef", NULL};
const char **cbm_conditional_block_types(CBMLanguage lang) {
switch (lang) {
case CBM_LANG_C:
case CBM_LANG_CPP:
case CBM_LANG_CUDA:
case CBM_LANG_OBJC:
case CBM_LANG_CSHARP:
case CBM_LANG_GLSL:
case CBM_LANG_HLSL:
case CBM_LANG_ISPC:
case CBM_LANG_SLANG:
return c_family_conditional_types;
default:
return NULL;
}
}
const CBMLangSpec *cbm_lang_spec(CBMLanguage lang) {
if (lang < 0 || lang >= CBM_LANG_COUNT) {
return NULL;
+7
View File
@@ -48,6 +48,13 @@ typedef struct {
// language rows; the table lives in extract_calls.c next to the dispatch code.
const char **cbm_string_dispatch_suffixes(CBMLanguage lang);
// Returns a NULL-terminated list of node types that wrap conditionally
// compiled alternatives (#if/#ifdef/#else/#elif blocks) for a language, or
// NULL when the grammar has none. A definition inside one is still a member
// of its enclosing scope; its #else twin is a variant of the same definition.
// Side table for the same -Wmissing-field-initializers reason as above.
const char **cbm_conditional_block_types(CBMLanguage lang);
// Get the language spec for a given language. Returns NULL for unsupported.
const CBMLangSpec *cbm_lang_spec(CBMLanguage lang);
+800 -125
View File
File diff suppressed because it is too large Load Diff
+44
View File
@@ -63,9 +63,28 @@ typedef struct {
int neg_memo_cap; // power-of-two; 0 until first insert
int neg_memo_count; // live entries (grow by rehash at 70% load)
// Field references already emitted for this file, keyed by (enclosing
// function, owner type, field). The function -> Field edge needs one row
// per pair; a row per occurrence would cost a record for every member
// access in the corpus. Same uint64 hash-set shape and ownership as
// neg_memo; freed at end of c_lsp_process_file.
uint64_t *field_ref_seen;
int field_ref_seen_cap;
int field_ref_seen_count;
// The type an initializer_list is about to initialize, handed from the
// declarator / compound literal / enclosing list that knows it to the walk
// of that list. Bound to the list's node id: a hint nothing consumed must
// never type a later, unrelated list.
const CBMType *init_list_type;
const void *init_list_id;
// Output
CBMResolvedCallArray *resolved_calls;
CBMSourceOrigin source_origin; // source buffer represented by emitted occurrence spans
const CBMFileResult *test_definition_owners; /* borrowed, prevalidated raw source identity */
int test_owner_seen_count;
bool test_owner_error;
// Function pointer targets: lexical binding -> exact target function QN.
// A NULL target is an explicitly unknown/ambiguous binding and must shadow
@@ -154,6 +173,31 @@ void cbm_run_c_lsp_cross_with_registry(CBMArena *arena, const char *source, int
TSTree *cached_tree, // NULL = parse internally
CBMResolvedCallArray *out);
/* Additive configured-owner entrypoints. Owners must outlive the call. With
* owners, RAW origin, exact source identity and module identity are required.
* Configured owners fresh-parse verified source: supplied roots/cached trees
* have no source provenance and are not trusted. NULL owners retain caching.
* False leaves output untouched when identity validation fails. A later
* mapping/OOM failure may leave partial output that callers must discard. QNs
* in successful output belong to the supplied output arena. Callers must
* abort publication on false. Legacy wrappers pass NULL and retain previous behavior. */
bool cbm_run_c_lsp_with_test_owners(
CBMArena *arena, CBMFileResult *result, const char *source, int source_len,
TSNode root, bool cpp_mode, CBMSourceOrigin source_origin,
const CBMFileResult *test_owners);
bool cbm_run_c_lsp_cross_with_test_owners(
CBMArena *arena, const char *source, int source_len, const char *module_qn,
bool cpp_mode, CBMLSPDef *defs, int def_count, const char **include_paths,
const char **include_ns_qns, int include_count, TSTree *cached_tree,
CBMResolvedCallArray *out, CBMSourceOrigin source_origin,
const CBMFileResult *test_owners);
bool cbm_run_c_lsp_cross_with_registry_with_test_owners(
CBMArena *arena, const char *source, int source_len, const char *module_qn,
bool cpp_mode, CBMTypeRegistry *reg, const char **include_paths,
const char **include_ns_qns, int include_count, TSTree *cached_tree,
CBMResolvedCallArray *out, CBMSourceOrigin source_origin,
const CBMFileResult *test_owners);
// Register C stdlib types and functions into a registry.
void cbm_c_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena);
+3 -3
View File
@@ -757,9 +757,9 @@ static bool has_filesystem_extension(const char *path) {
ext[ext_len] = '\0';
static const char *const hard_file_exts[] = {
".cfg", ".conf", ".credentials", ".crt", ".db", ".env",
".ini", ".key", ".pem", ".pid", ".properties", ".service",
".sock", ".socket", ".sqlite", ".toml", NULL};
".cfg", ".conf", ".credentials", ".crt", ".db", ".env", ".ini", ".key",
".log", ".md", ".pdf", ".pem", ".pid", ".properties", ".rst", ".service",
".sock", ".socket", ".sqlite", ".toml", ".txt", NULL};
for (int i = 0; hard_file_exts[i]; i++) {
if (path_ext_matches(ext, hard_file_exts[i])) {
return true;
+744
View File
@@ -0,0 +1,744 @@
/*
* spawn_patterns.c — process-spawning call sites, per language (see the header).
*
* Spellings come from what the extractor records as the callee (checked per
* language against real calls): Rust `Command::new`, Go `exec.Command`, Java
* `Runtime.getRuntime().exec`, a constructor as its class name.
*/
#include "spawn_patterns.h"
#include "foundation/constants.h"
#include <stdio.h>
#include <string.h>
#define Q CBM_SPAWN_QUALIFIED
#define U CBM_SPAWN_BARE
#define I CBM_SPAWN_BARE_IMPORTED
#define S CBM_SPAWN_STRING_RECEIVER
/* libc / POSIX / Win32. posix_spawn and _spawn* take the path second;
* CreateProcess names the program first or only in its command line. */
static const cbm_spawn_api_t c_family[] = {
{"system", NULL, U, 0, -1}, {"std::system", NULL, U, 0, -1},
{"_wsystem", NULL, U, 0, -1}, {"popen", NULL, U, 0, -1},
{"_popen", NULL, U, 0, -1}, {"_wpopen", NULL, U, 0, -1},
{"execl", NULL, U, 0, -1}, {"execle", NULL, U, 0, -1},
{"execlp", NULL, U, 0, -1}, {"execv", NULL, U, 0, -1},
{"execve", NULL, U, 0, -1}, {"execvp", NULL, U, 0, -1},
{"execvpe", NULL, U, 0, -1}, {"fexecve", NULL, U, 0, -1},
{"posix_spawn", NULL, U, 1, -1}, {"posix_spawnp", NULL, U, 1, -1},
{"_execl", NULL, U, 0, -1}, {"_execv", NULL, U, 0, -1},
{"_execlp", NULL, U, 0, -1}, {"_execvp", NULL, U, 0, -1},
{"_spawnl", NULL, U, 1, -1}, {"_spawnle", NULL, U, 1, -1},
{"_spawnlp", NULL, U, 1, -1}, {"_spawnv", NULL, U, 1, -1},
{"_spawnve", NULL, U, 1, -1}, {"_spawnvp", NULL, U, 1, -1},
{"_wspawnv", NULL, U, 1, -1}, {"_wspawnvp", NULL, U, 1, -1},
{"CreateProcess", NULL, U, 0, 1}, {"CreateProcessA", NULL, U, 0, 1},
{"CreateProcessW", NULL, U, 0, 1}, {"ShellExecute", NULL, U, 2, -1},
{"ShellExecuteA", NULL, U, 2, -1}, {"ShellExecuteW", NULL, U, 2, -1},
{"ShellExecuteExA", NULL, U, 0, -1}, {"ShellExecuteExW", NULL, U, 0, -1},
{"WinExec", NULL, U, 0, -1}, {NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t python[] = {
{"subprocess.run", NULL, Q, 0, -1},
{"subprocess.call", NULL, Q, 0, -1},
{"subprocess.check_call", NULL, Q, 0, -1},
{"subprocess.check_output", NULL, Q, 0, -1},
{"subprocess.Popen", NULL, Q, 0, -1},
{"subprocess.getoutput", NULL, Q, 0, -1},
{"subprocess.getstatusoutput", NULL, Q, 0, -1},
{"os.system", NULL, Q, 0, -1},
{"os.popen", NULL, Q, 0, -1},
{"os.startfile", NULL, Q, 0, -1},
{"os.execl", NULL, Q, 0, -1},
{"os.execle", NULL, Q, 0, -1},
{"os.execlp", NULL, Q, 0, -1},
{"os.execlpe", NULL, Q, 0, -1},
{"os.execv", NULL, Q, 0, -1},
{"os.execve", NULL, Q, 0, -1},
{"os.execvp", NULL, Q, 0, -1},
{"os.execvpe", NULL, Q, 0, -1},
{"os.posix_spawn", NULL, Q, 0, -1},
{"os.posix_spawnp", NULL, Q, 0, -1},
{"os.spawnl", NULL, Q, 1, -1},
{"os.spawnle", NULL, Q, 1, -1},
{"os.spawnlp", NULL, Q, 1, -1},
{"os.spawnlpe", NULL, Q, 1, -1},
{"os.spawnv", NULL, Q, 1, -1},
{"os.spawnve", NULL, Q, 1, -1},
{"os.spawnvp", NULL, Q, 1, -1},
{"os.spawnvpe", NULL, Q, 1, -1},
{"asyncio.create_subprocess_exec", NULL, Q, 0, -1},
{"asyncio.create_subprocess_shell", NULL, Q, 0, -1},
{"pexpect.spawn", NULL, Q, 0, -1},
{"pexpect.run", NULL, Q, 0, -1},
{NULL, NULL, 0, 0, -1},
};
/* Node's child_process (also behind `node:`), the destructured-require form,
* execa / cross-spawn, and the Bun and Deno globals. */
static const cbm_spawn_api_t js_family[] = {
{"child_process.spawn", NULL, Q, 0, -1},
{"child_process.spawnSync", NULL, Q, 0, -1},
{"child_process.exec", NULL, Q, 0, -1},
{"child_process.execSync", NULL, Q, 0, -1},
{"child_process.execFile", NULL, Q, 0, -1},
{"child_process.execFileSync", NULL, Q, 0, -1},
{"child_process.fork", NULL, Q, 0, -1},
{"spawn", "child_process", I, 0, -1},
{"spawnSync", "child_process", I, 0, -1},
{"exec", "child_process", I, 0, -1},
{"execSync", "child_process", I, 0, -1},
{"execFile", "child_process", I, 0, -1},
{"execFileSync", "child_process", I, 0, -1},
{"fork", "child_process", I, 0, -1},
{"execa.execa", NULL, Q, 0, -1},
{"execa.execaSync", NULL, Q, 0, -1},
{"execa.execaCommand", NULL, Q, 0, -1},
{"execa.execaCommandSync", NULL, Q, 0, -1},
{"cross-spawn.spawn", NULL, Q, 0, -1},
{"cross-spawn.sync", NULL, Q, 0, -1},
{"Bun.spawn", NULL, Q, 0, -1},
{"Bun.spawnSync", NULL, Q, 0, -1},
{"Deno.Command", NULL, Q, 0, -1},
{"Deno.run", NULL, Q, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t go[] = {
{"os/exec.Command", NULL, Q, 0, -1},
{"os/exec.CommandContext", NULL, Q, 1, -1},
{"golang.org/x/sys/execabs.Command", NULL, Q, 0, -1},
{"golang.org/x/sys/execabs.CommandContext", NULL, Q, 1, -1},
{"os.StartProcess", NULL, Q, 0, -1},
{"syscall.Exec", NULL, Q, 0, -1},
{"syscall.ForkExec", NULL, Q, 0, -1},
{"syscall.StartProcess", NULL, Q, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t rust[] = {
{"std::process::Command::new", NULL, Q, 0, -1},
{"tokio::process::Command::new", NULL, Q, 0, -1},
{"async_process::Command::new", NULL, Q, 0, -1},
{"async_std::process::Command::new", NULL, Q, 0, -1},
{"smol::process::Command::new", NULL, Q, 0, -1},
{"duct::cmd", NULL, Q, 0, -1},
{NULL, NULL, 0, 0, -1},
};
/* java.lang is implicit, so the class name alone is the spelling. */
static const cbm_spawn_api_t jvm[] = {
{"ProcessBuilder", NULL, U, 0, -1},
{"java.lang.ProcessBuilder", NULL, Q, 0, -1},
{"Runtime.getRuntime().exec", NULL, U, 0, -1},
{".execute", NULL, S, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t dotnet[] = {
{"Process.Start", NULL, U, 0, -1}, {"System.Diagnostics.Process.Start", NULL, Q, 0, -1},
{"ProcessStartInfo", NULL, U, 0, -1}, {"System.Diagnostics.ProcessStartInfo", NULL, Q, 0, -1},
{"Cli.Wrap", NULL, U, 0, -1}, {NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t ruby[] = {
{"system", NULL, U, 0, -1}, {"exec", NULL, U, 0, -1},
{"spawn", NULL, U, 0, -1}, {"Kernel.system", NULL, U, 0, -1},
{"Kernel.exec", NULL, U, 0, -1}, {"Kernel.spawn", NULL, U, 0, -1},
{"Process.spawn", NULL, U, 0, -1}, {"Process.exec", NULL, U, 0, -1},
{"IO.popen", NULL, U, 0, -1}, {"PTY.spawn", NULL, U, 0, -1},
{"Open3.popen2", NULL, U, 0, -1}, {"Open3.popen2e", NULL, U, 0, -1},
{"Open3.popen3", NULL, U, 0, -1}, {"Open3.capture2", NULL, U, 0, -1},
{"Open3.capture2e", NULL, U, 0, -1}, {"Open3.capture3", NULL, U, 0, -1},
{"Open3.pipeline", NULL, U, 0, -1}, {NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t php[] = {
{"exec", NULL, U, 0, -1},
{"shell_exec", NULL, U, 0, -1},
{"system", NULL, U, 0, -1},
{"passthru", NULL, U, 0, -1},
{"proc_open", NULL, U, 0, -1},
{"popen", NULL, U, 0, -1},
{"pcntl_exec", NULL, U, 0, -1},
{"Symfony\\Component\\Process\\Process", NULL, Q, 0, -1},
{"Symfony\\Component\\Process\\Process::fromShellCommandline", NULL, Q, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t perl[] = {
{"system", NULL, U, 0, -1},
{"exec", NULL, U, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t lua[] = {
{"os.execute", NULL, U, 0, -1},
{"io.popen", NULL, U, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t r_lang[] = {
{"system", NULL, U, 0, -1},
{"system2", NULL, U, 0, -1},
{"shell", NULL, U, 0, -1},
{"processx::run", NULL, Q, 0, -1},
{"sys::exec_wait", NULL, Q, 0, -1},
{"sys::exec_internal", NULL, Q, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t elixir[] = {
{"System.cmd", NULL, U, 0, -1},
{"System.shell", NULL, U, 0, -1},
{"Port.open", NULL, U, 0, CBM_SPAWN_ANY_LITERAL},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t erlang[] = {
{"os:cmd", NULL, Q, 0, -1},
{"open_port", NULL, U, 0, -1},
{"erlang:open_port", NULL, Q, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t haskell[] = {
{"callProcess", "System.Process", I, 0, -1},
{"callCommand", "System.Process", I, 0, -1},
{"readProcess", "System.Process", I, 0, -1},
{"readProcessWithExitCode", "System.Process", I, 0, -1},
{"readCreateProcess", "System.Process", I, 0, -1},
{"readCreateProcessWithExitCode", "System.Process", I, 0, -1},
{"createProcess", "System.Process", I, 0, -1},
{"spawnProcess", "System.Process", I, 0, -1},
{"spawnCommand", "System.Process", I, 0, -1},
{"system", "System.Process", I, 0, -1},
{"rawSystem", "System.Process", I, 0, -1},
{"runProcess", "System.Process", I, 0, -1},
{"runCommand", "System.Process", I, 0, -1},
{"runInteractiveProcess", "System.Process", I, 0, -1},
{"runInteractiveCommand", "System.Process", I, 0, -1},
{"runProcess", "System.Process.Typed", I, 0, -1},
{"readProcess", "System.Process.Typed", I, 0, -1},
{"startProcess", "System.Process.Typed", I, 0, -1},
{"system", "System.Cmd", I, 0, -1},
{"rawSystem", "System.Cmd", I, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t dlang[] = {
{"execute", "std.process", I, 0, -1},
{"executeShell", "std.process", I, 0, -1},
{"spawnProcess", "std.process", I, 0, -1},
{"spawnShell", "std.process", I, 0, -1},
{"pipeProcess", "std.process", I, 0, -1},
{"pipeShell", "std.process", I, 0, -1},
{NULL, NULL, 0, 0, -1},
};
/* Foundation's Process: the program is a property set after construction,
* so a bare construction is a spawn site with a dynamic program. */
static const cbm_spawn_api_t swift[] = {
{"Process.launchedProcess", NULL, U, 0, -1},
{"Process.run", NULL, U, 0, -1},
{"Process", "Foundation", I, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t crystal[] = {
{"Process.run", NULL, U, 0, -1}, {"Process.new", NULL, U, 0, -1},
{"Process.exec", NULL, U, 0, -1}, {"system", NULL, U, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t zig[] = {
{"std.process.Child.init", NULL, Q, 0, -1},
{"std.process.Child.run", NULL, Q, 0, CBM_SPAWN_ANY_LITERAL},
{"std.ChildProcess.init", NULL, Q, 0, -1},
{"std.ChildProcess.run", NULL, Q, 0, CBM_SPAWN_ANY_LITERAL},
{"std.ChildProcess.exec", NULL, Q, 0, CBM_SPAWN_ANY_LITERAL},
{"std.process.execv", NULL, Q, 1, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t ocaml[] = {
{"Sys.command", NULL, U, 0, -1},
{"Unix.system", NULL, U, 0, -1},
{"Unix.create_process", NULL, U, 0, -1},
{"Unix.create_process_env", NULL, U, 0, -1},
{"Unix.open_process", NULL, U, 0, -1},
{"Unix.open_process_in", NULL, U, 0, -1},
{"Unix.open_process_out", NULL, U, 0, -1},
{"Unix.open_process_full", NULL, U, 0, -1},
{"Unix.open_process_args", NULL, U, 0, -1},
{"Unix.execv", NULL, U, 0, -1},
{"Unix.execvp", NULL, U, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t clojure[] = {
{"clojure.java.shell/sh", NULL, Q, 0, -1},
{"sh", "clojure.java.shell", I, 0, -1},
{"babashka.process/process", NULL, Q, 0, -1},
{"babashka.process/shell", NULL, Q, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t tcl[] = {
{"exec", NULL, U, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t fortran[] = {
{"execute_command_line", NULL, U, 0, -1},
{"system", NULL, U, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t julia[] = {
{"run", NULL, U, 0, -1},
{NULL, NULL, 0, 0, -1},
};
static const cbm_spawn_api_t powershell[] = {
{"Start-Process", NULL, U, 0, -1},
{NULL, NULL, 0, 0, -1},
};
#undef Q
#undef U
#undef I
#undef S
static const cbm_spawn_api_t *spawn_table(CBMLanguage lang) {
switch (lang) {
case CBM_LANG_C:
case CBM_LANG_CPP:
case CBM_LANG_CUDA:
case CBM_LANG_OBJC:
return c_family;
case CBM_LANG_PYTHON:
return python;
case CBM_LANG_JAVASCRIPT:
case CBM_LANG_TYPESCRIPT:
case CBM_LANG_TSX:
case CBM_LANG_ARKTS:
return js_family;
case CBM_LANG_GO:
return go;
case CBM_LANG_RUST:
return rust;
case CBM_LANG_JAVA:
case CBM_LANG_KOTLIN:
case CBM_LANG_SCALA:
case CBM_LANG_GROOVY:
return jvm;
case CBM_LANG_CSHARP:
case CBM_LANG_FSHARP:
return dotnet;
case CBM_LANG_RUBY:
return ruby;
case CBM_LANG_PHP:
return php;
case CBM_LANG_PERL:
return perl;
case CBM_LANG_LUA:
case CBM_LANG_LUAU:
case CBM_LANG_TEAL:
return lua;
case CBM_LANG_R:
return r_lang;
case CBM_LANG_ELIXIR:
return elixir;
case CBM_LANG_ERLANG:
return erlang;
case CBM_LANG_HASKELL:
return haskell;
case CBM_LANG_DLANG:
return dlang;
case CBM_LANG_SWIFT:
return swift;
case CBM_LANG_CRYSTAL:
return crystal;
case CBM_LANG_ZIG:
return zig;
case CBM_LANG_OCAML:
return ocaml;
case CBM_LANG_CLOJURE:
return clojure;
case CBM_LANG_TCL:
return tcl;
case CBM_LANG_FORTRAN:
return fortran;
case CBM_LANG_JULIA:
return julia;
case CBM_LANG_POWERSHELL:
return powershell;
default:
return NULL;
}
}
/* An import value without Node's `node:` scheme. */
static const char spawn_node_scheme[] = "node:";
static const char *spawn_module(const char *val) {
size_t n = sizeof(spawn_node_scheme) - 1;
return strncmp(val, spawn_node_scheme, n) == 0 ? val + n : val;
}
static const char *const spawn_separators[] = {"::", ".", ":", "/", "\\", NULL};
/* Length of the callee's head (before its first separator) and the
* separator's offset, or 0 when the callee has none. */
static size_t spawn_head_len(const char *callee) {
size_t best = 0;
for (int i = 0; spawn_separators[i]; i++) {
const char *at = strstr(callee, spawn_separators[i]);
if (at && at > callee && (best == 0 || (size_t)(at - callee) < best)) {
best = (size_t)(at - callee);
}
}
return best;
}
/* True when `val` names `name` as its last segment ("subprocess.run" for
* run, "java.lang.ProcessBuilder" for ProcessBuilder). */
static bool spawn_val_names(const char *val, const char *name) {
size_t vl = strlen(val);
size_t nl = strlen(name);
if (vl <= nl || strcmp(val + vl - nl, name) != 0) {
return false;
}
char sep = val[vl - nl - 1];
return sep == '.' || sep == ':' || sep == '/' || sep == '\\';
}
/* The callee as its import makes it: `sp.run` -> subprocess.run (an alias),
* `run` -> subprocess.run (a member import), `spawn` -> child_process.spawn
* (a named import mapped to its module). Empty when no import applies. */
static void spawn_expand(const char *callee, const CBMImport *imports, int count, char *out,
size_t n) {
out[0] = '\0';
size_t head = spawn_head_len(callee);
size_t key_len = head ? head : strlen(callee);
for (int i = 0; i < count; i++) {
const char *key = imports[i].local_name;
if (!key || !imports[i].module_path || strlen(key) != key_len ||
strncmp(key, callee, key_len) != 0) {
continue;
}
const char *val = spawn_module(imports[i].module_path);
if (head) {
snprintf(out, n, "%s%s", val, callee + head);
} else if (spawn_val_names(val, callee)) {
snprintf(out, n, "%s", val);
} else {
snprintf(out, n, "%s.%s", val, callee);
}
return;
}
}
static bool spawn_imports_module(const CBMImport *imports, int count, const char *module) {
size_t ml = strlen(module);
for (int i = 0; i < count; i++) {
if (!imports[i].module_path) {
continue;
}
const char *val = spawn_module(imports[i].module_path);
if (strncmp(val, module, ml) == 0 &&
(val[ml] == '\0' || val[ml] == '.' || val[ml] == ':' || val[ml] == '/')) {
return true;
}
}
return false;
}
/* Groovy `"git status".execute`: a quoted receiver, then the api suffix. */
static bool spawn_string_receiver(const char *callee, const char *suffix) {
size_t cl = strlen(callee);
size_t sl = strlen(suffix);
return (callee[0] == '"' || callee[0] == '\'') && cl > sl + 1 &&
strcmp(callee + cl - sl, suffix) == 0 && callee[cl - sl - 1] == callee[0];
}
static bool spawn_entry_matches(const cbm_spawn_api_t *e, const char *callee, const char *expanded,
const CBMImport *imports, int count) {
switch (e->rule) {
case CBM_SPAWN_QUALIFIED:
return strcmp(callee, e->api) == 0 || (expanded[0] && strcmp(expanded, e->api) == 0);
case CBM_SPAWN_BARE:
return strcmp(callee, e->api) == 0;
case CBM_SPAWN_BARE_IMPORTED:
return strcmp(callee, e->api) == 0 && spawn_imports_module(imports, count, e->module);
case CBM_SPAWN_STRING_RECEIVER:
return spawn_string_receiver(callee, e->api);
default:
return false;
}
}
const cbm_spawn_api_t *cbm_spawn_match(CBMLanguage lang, const char *callee,
const CBMImport *imports, int count) {
const cbm_spawn_api_t *table = spawn_table(lang);
if (!table || !callee || !callee[0]) {
return NULL;
}
if (!imports) {
count = 0;
}
char expanded[CBM_SZ_512];
spawn_expand(callee, imports, count, expanded, sizeof(expanded));
for (const cbm_spawn_api_t *e = table; e->api; e++) {
if (spawn_entry_matches(e, callee, expanded, imports, count)) {
return e;
}
}
return NULL;
}
/* The first quoted literal in `text` ("..", '..' or `..`), copied without
* its quotes. False when there is none. */
static bool spawn_first_literal(const char *text, char *out, size_t n) {
for (const char *p = text; *p; p++) {
if (*p != '"' && *p != '\'' && *p != '`') {
continue;
}
const char *end = p + 1;
while (*end && *end != *p) {
end += (*end == '\\' && end[1]) ? 2 : 1;
}
if (!*end) {
return false;
}
size_t len = (size_t)(end - p - 1);
if (len >= n) {
len = n - 1;
}
memcpy(out, p + 1, len);
out[len] = '\0';
return true;
}
return false;
}
static const char *spawn_arg_expr(const CBMCall *call, int index) {
for (int i = 0; i < call->arg_count; i++) {
if (call->args[i].index == index && call->args[i].expr) {
return call->args[i].expr;
}
}
return NULL;
}
static bool spawn_word_char(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
}
static const char *spawn_skip_space(const char *p) {
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
p++;
}
return p;
}
/* Past a keyword label in front of an argument (`launchPath: `, `args=`). */
static const char *spawn_skip_label(const char *p) {
const char *q = p;
while (spawn_word_char(*q)) {
q++;
}
if (q == p) {
return p;
}
q = spawn_skip_space(q);
if ((*q == ':' && q[1] != ':') || (*q == '=' && q[1] != '=')) {
return spawn_skip_space(q + 1);
}
return p;
}
/* A string literal at the start of `p`, after a prefix such as r, b, f, L
* or @ (r"..", L"..", @".."). */
static bool spawn_leading_literal(const char *p, char *out, size_t n) {
for (int i = 0; i < 2 && *p && strchr("rRbBuUfFL@$", *p); i++) {
p++;
}
return (*p == '"' || *p == '\'' || *p == '`') && spawn_first_literal(p, out, n);
}
/* What may stand before a collection's bracket: nothing, Zig's `&.` / `.`,
* Java's `new String[]`, or a collection constructor. A name before `[` is a
* subscript (`record["argv"]`) and any other call's arguments are not the
* command (`os.path.join("bin", tool)`). */
static bool spawn_collection_prefix(const char *p, size_t len) {
static const char *const ctors[] = {
"listOf", "arrayOf", "mutableListOf", "List.of", "Arrays.asList", "vec!", "c", "Cmd", NULL};
if (len == 0 || (len <= 2 && strspn(p, "&.") == len) ||
(len > 2 && strncmp(p + len - 2, "[]", 2) == 0 && strncmp(p, "new ", 4) == 0)) {
return true;
}
for (int i = 0; ctors[i]; i++) {
if (strlen(ctors[i]) == len && strncmp(p, ctors[i], len) == 0) {
return true;
}
}
return false;
}
/* The first element of a collection written at `p` ([..], (..), {..},
* listOf(..), &.{..}), copied into `out`. False when `p` is no collection. */
static bool spawn_first_element(const char *p, char *out, size_t n) {
const char *open = p;
while (*open && (spawn_word_char(*open) || strchr("!.&:<> ", *open) ||
(open[0] == '[' && open[1] == ']'))) {
open += (open[0] == '[') ? 2 : 1;
}
if ((*open != '[' && *open != '(' && *open != '{') ||
!spawn_collection_prefix(p, (size_t)(open - p))) {
return false;
}
p = open;
const char *start = spawn_skip_space(p + 1);
int depth = 0;
const char *end = start;
for (; *end; end++) {
if (*end == '"' || *end == '\'' || *end == '`') {
const char *close = strchr(end + 1, *end);
if (!close) {
break;
}
end = close;
} else if (strchr("[({", *end)) {
depth++;
} else if (strchr("])}", *end)) {
if (depth-- == 0) {
break;
}
} else if (*end == ',' && depth == 0) {
break;
}
}
size_t len = (size_t)(end - start);
if (len == 0 || len >= n) {
return false;
}
memcpy(out, start, len);
out[len] = '\0';
return true;
}
/* The program literal an argument spells: a leading literal, or the first
* element of a collection when that element is one. A program held in a
* variable (`[binary, "--version"]`) is no literal: the arguments after it
* are not the program. */
static bool spawn_expr_literal(const char *expr, char *out, size_t n) {
char element[CBM_SZ_256];
for (int depth = 0; depth < 3; depth++) {
expr = spawn_skip_label(spawn_skip_space(expr));
if (spawn_leading_literal(expr, out, n)) {
return true;
}
if (!spawn_first_element(expr, element, sizeof(element))) {
return false;
}
expr = element;
}
return false;
}
/* A literal from the argument at `index`. Tcl words are literals unquoted;
* elsewhere an unquoted argument counts only when the extractor itself read
* it as the call's string (Perl's `system("make")`). */
static bool spawn_arg_literal(const CBMCall *call, CBMLanguage lang, int index, bool anywhere,
char *out, size_t n) {
const char *expr = spawn_arg_expr(call, index);
if (!expr) {
if (call->arg_count == 0 && index == 0 && call->first_string_arg) {
snprintf(out, n, "%s", call->first_string_arg);
return true;
}
return false;
}
if (anywhere ? spawn_first_literal(expr, out, n) : spawn_expr_literal(expr, out, n)) {
return true;
}
bool bareword = lang == CBM_LANG_TCL && strpbrk(expr, "$[") == NULL;
if (bareword || (call->first_string_arg && strcmp(expr, call->first_string_arg) == 0)) {
snprintf(out, n, "%s", expr);
return true;
}
return false;
}
/* A shell word that is not the program: an environment assignment
* (`LC_ALL=C make`) or the `env` that runs the program after them
* (`/usr/bin/env make`). */
static bool spawn_prefix_word(const char *word, size_t len) {
static const char env[] = "env";
size_t env_len = sizeof(env) - 1;
if (len >= env_len && strncmp(word + len - env_len, env, env_len) == 0 &&
(len == env_len || word[len - env_len - 1] == '/')) {
return true;
}
size_t i = 0;
while (i < len && spawn_word_char(word[i])) {
i++;
}
return i > 0 && i < len && word[i] == '=';
}
/* The program word of a command line: its first word that is not an
* environment prefix, without directories. Anything built at run time is not
* a program name. */
static void spawn_program_word(const char *literal, char *buf, size_t n) {
size_t len = 0;
for (;;) {
while (*literal == ' ' || *literal == '\t') {
literal++;
}
len = strcspn(literal, " \t\r\n");
if (!spawn_prefix_word(literal, len) || !literal[len]) {
break;
}
literal += len;
}
const char *start = literal;
for (size_t i = 0; i < len; i++) {
if (literal[i] == '/' || literal[i] == '\\') {
start = literal + i + 1;
}
}
len -= (size_t)(start - literal);
bool dynamic = len == 0 || len >= CBM_SPAWN_PROGRAM_MAX || len >= n;
for (size_t i = 0; i < len && !dynamic; i++) {
dynamic = strchr("$%{}`()<>|;&*?[]\"'", start[i]) != NULL;
}
if (dynamic) {
snprintf(buf, n, "%s", CBM_SPAWN_DYNAMIC);
return;
}
memcpy(buf, start, len);
buf[len] = '\0';
}
void cbm_spawn_program(const cbm_spawn_api_t *api, CBMLanguage lang, const CBMCall *call, char *buf,
size_t n) {
char literal[CBM_SZ_256];
bool found = false;
if (api->rule == CBM_SPAWN_STRING_RECEIVER) {
found = spawn_first_literal(call->callee_name, literal, sizeof(literal));
} else {
bool anywhere = api->alt == CBM_SPAWN_ANY_LITERAL;
found = spawn_arg_literal(call, lang, api->arg, anywhere, literal, sizeof(literal)) ||
(api->alt >= 0 &&
spawn_arg_literal(call, lang, api->alt, false, literal, sizeof(literal)));
}
if (!found) {
snprintf(buf, n, "%s", CBM_SPAWN_DYNAMIC);
return;
}
spawn_program_word(literal, buf, n);
}
+71
View File
@@ -0,0 +1,71 @@
/*
* spawn_patterns.h — process-spawning call sites, per language.
*
* Like the HTTP client tables in service_patterns.h: a call that starts
* another program (subprocess.run, exec.Command, posix_spawn, Process.Start,
* Command::new, ...) becomes a SPAWNS edge to a Process node named after the
* program it starts. Nothing resolves the program to code: the edge records
* that the caller leaves the graph through a process boundary, and which
* program it starts when the call spells it as a literal.
*/
#ifndef CBM_SPAWN_PATTERNS_H
#define CBM_SPAWN_PATTERNS_H
#include "cbm.h"
#include <stdbool.h>
#include <stddef.h>
/* How a table spelling matches a call. */
typedef enum {
/* The full spelling, written out or reached through the file's imports
* (`sp.run` with `import subprocess as sp`, `run` from
* `from subprocess import run`, Go `exec.Command` from "os/exec"). */
CBM_SPAWN_QUALIFIED = 0,
/* A bare name (`system`, `popen`, `exec`): the spelling alone. A
* definition of that name in the calling module (or one the call reaches
* through an import) still makes it an ordinary call. */
CBM_SPAWN_BARE,
/* A bare name that counts when the file imports `module`
* (Haskell `callProcess` with System.Process, a destructured
* `const { spawn } = require("child_process")`). */
CBM_SPAWN_BARE_IMPORTED,
/* Groovy `"git status".execute()`: the receiver is the command line. */
CBM_SPAWN_STRING_RECEIVER,
} cbm_spawn_rule_t;
typedef struct {
const char *api; /* canonical spelling: "subprocess.run", "os/exec.Command", "system" */
const char *module; /* the import a CBM_SPAWN_BARE_IMPORTED name needs, else NULL */
unsigned char rule; /* cbm_spawn_rule_t */
signed char arg; /* argument holding the program or command line */
signed char alt; /* second place to look, -1 for none (CreateProcess: name or line),
* or CBM_SPAWN_ANY_LITERAL */
} cbm_spawn_api_t;
/* `alt` value: the program is the first literal anywhere in the argument,
* which is an options tuple or struct (Elixir {:spawn, "cat"}, Zig
* .{ .argv = &.{"ls"} }). Elsewhere only a leading literal or a collection's
* first element is the program. */
#define CBM_SPAWN_ANY_LITERAL (-2)
/* The spawn API a call names, or NULL. `imports` are the calling file's own
* import statements (local name -> module path) as extracted, external
* modules included: subprocess or os/exec never resolve to project nodes, so
* the resolver's import map does not hold them. Whether the call is a spawn
* after all is the resolver's to decide: an evidence-backed resolution onto
* project code (an import, the same module, the LSP; never a name guess)
* makes it an ordinary call (src/pipeline/pass_spawns.c). */
const cbm_spawn_api_t *cbm_spawn_match(CBMLanguage lang, const char *callee,
const CBMImport *imports, int count);
/* The program a matched spawn starts: the first literal in its program
* argument, that literal's first word, without directories
* ("/usr/bin/git status" -> "git"). CBM_SPAWN_DYNAMIC when the program is
* not a literal. Writes into buf (at least CBM_SPAWN_PROGRAM_MAX bytes). */
#define CBM_SPAWN_DYNAMIC "<dynamic>"
#define CBM_SPAWN_PROGRAM_MAX 64
void cbm_spawn_program(const cbm_spawn_api_t *api, CBMLanguage lang, const CBMCall *call, char *buf,
size_t n);
#endif
+1 -1
View File
@@ -17,7 +17,7 @@ codes) and rejects unknown flags with exit 2 + `Please consult --help.`
| leg | entry | what a run gives you |
|---|---|---|
| **test** | `test.sh` | DEFAULT = the venue leg: static contracts (Step 0a–0r) + CLEAN ASan+UBSan build + all suites via the parallel harness + prod-binary guards. `--suites a,b` = iteration mode (incremental rebuild, subset, seconds). `--tsan` = the ThreadSanitizer leg. CLANGARM64 gets CI's trap-UBSan flags automatically. |
| **test** | `test.sh` | DEFAULT = the venue leg: static contracts (Step 0a–0r) + CLEAN ASan+UBSan build + all suites via the parallel harness + prod-binary guards. `--suites a,b` = iteration mode (incremental rebuild, subset, seconds). `--tsan` = the ThreadSanitizer leg. `--contracts-only` = the static contract steps alone (PR CI's contracts job for docs-only and non-product changes). CLANGARM64 gets CI's trap-UBSan flags automatically. |
| **package wrappers** | `ci/test-package-wrappers.sh` | Go, npm, and PyPI runtime-set publication/lock suites on the current host. CI runs the same entry on Linux and Windows so both platform lock implementations gate. |
| **build** | `build.sh` | CLEAN production runtime set (native executable + authenticated integration asset; `--with-ui` adds one content-addressed UI pack). ccache via `env.sh` makes repeats fast; `CCACHE_COMPILERCHECK=content` guarantees a hit is byte-identical to a cold compile — never stale. `--version`, `STATIC=1`, `BUILD_DIR=`. |
| **lint** | `lint.sh` | clang-tidy + cppcheck + clang-format (+ no-skips policy). `--ci` = the CI gate set (no clang-tidy). Drives the same make targets as `make lint`/`lint-ci`. |
+4 -2
View File
@@ -13,8 +13,10 @@ CI and the local infrastructure — both of which the venue-parity contract
| `preflight-docker.sh` | Same idea for Colima/docker: prune runner-unlike residue, assert free space on the filesystem backing the docker data root (not the VM's `/`). Build cache + named volumes KEPT (the local analogue of actions/cache); `--deep` drops them. | `test-infrastructure/run.sh` |
| `check-glibc-compat.sh` | Run a linux binary in debian:bullseye (glibc 2.31) — the portable binary must start on old glibc. | `_smoke.yml` portable legs |
| `generate-sbom.py` | The release SPDX SBOM (vendored versions reviewable here, diffable by vendoring PRs — was inline YAML). | `release.yml` |
| `require-all-green.sh` | The aggregate gate: fail unless every needed job succeeded or legitimately skipped (was inline YAML). | `pr.yml ci-ok` |
| `verify-shard-union.sh` | Prove sharded test legs lost nothing: shard count agreement, indices 1..n, identical suite lists, union of slices == full list (was inline YAML). | `_test.yml` shard-completeness |
| `require-all-green.sh` | The aggregate gate: fail unless every needed job succeeded or legitimately skipped (was inline YAML) — and, under lane selection, every SELECTED lane ran and succeeded (skipped/cancelled/missing = red; unselected lanes may skip). | `pr.yml ci-ok` |
| `select-lanes.sh` | Decide which PR lanes a change set runs (tier, lanes, full, reasons) from its file list — the R1 rule set, fail-safe to every lane. Pinned by `tests/test_select_lanes.sh` and the September 2026 history replay (`scripts/test-impact/replay-selector.sh`). | `pr.yml changes` |
| `test-impact-shadow.sh` | The report-only test-impact shadow: fetch the latest released binary (checksum-verified) or take a given one, index this checkout into a throwaway cache, run `detect_changes` against the base and map it to suites (`scripts/test-impact/predict.py`). Never gates. | `pr.yml test-impact-shadow` |
| `verify-shard-union.sh` | Prove sharded test legs lost nothing: shard count agreement, indices 1..n, identical suite lists, union of slices == full list (was inline YAML). No manifests is green only when the lane selection holds no test leg. | `_test.yml` shard-completeness |
| `prepare-release-candidates.sh` | Copy one linker output into stripped/unstripped candidates, finalize signatures, composition-check them without execution, and record their hashes. | `_build.yml`, local artifact smoke |
| `stage-release-candidates.py` | Admit exactly eight candidate artifacts / sixteen byte-distinct binaries into the content-addressed VirusTotal scan set. | `_build.yml` |
| `select-release-candidates.py` | Apply the reviewed tuple-local VT truth table, or the explicit dry-run stripped default, and atomically copy one content-bound binary per target. | `_build.yml` |
+23 -12
View File
@@ -26,14 +26,23 @@ CI venue would never see.
.PARAMETER SkipGate
Sweep and report, but do not fail when free space is short. For inspection only.
.PARAMETER CheckOnly
Assert the free-space floor without enumerating or removing residue. Isolated
runs use fresh checkout/build/temp roots and must not sweep another run's files.
Cannot be combined with SkipGate.
#>
[CmdletBinding()]
param(
[double]$MinFreeGB = 14,
[switch]$SkipGate
[switch]$SkipGate,
[switch]$CheckOnly
)
$ErrorActionPreference = 'Stop'
if ($CheckOnly -and $SkipGate) {
throw 'CheckOnly cannot be combined with SkipGate: the disk floor is required.'
}
function Get-FreeGB {
[math]::Round((Get-PSDrive C).Free / 1GB, 2)
@@ -56,21 +65,23 @@ function Remove-ResidueDir([string]$Path) {
$before = Get-FreeGB
$removed = 0
$failed = @()
$roots = @($env:USERPROFILE, $env:TEMP) | Where-Object { $_ } | Select-Object -Unique
foreach ($root in $roots) {
if (-not (Test-Path -LiteralPath $root)) { continue }
Get-ChildItem -LiteralPath $root -Directory -Filter 'cbm-*' -ErrorAction SilentlyContinue |
ForEach-Object {
if (Remove-ResidueDir $_.FullName) {
$removed++
} else {
$failed += $_.FullName
if (-not $CheckOnly) {
$roots = @($env:USERPROFILE, $env:TEMP) | Where-Object { $_ } | Select-Object -Unique
foreach ($root in $roots) {
if (-not (Test-Path -LiteralPath $root)) { continue }
Get-ChildItem -LiteralPath $root -Directory -Filter 'cbm-*' -ErrorAction SilentlyContinue |
ForEach-Object {
if (Remove-ResidueDir $_.FullName) {
$removed++
} else {
$failed += $_.FullName
}
}
}
}
}
$after = Get-FreeGB
if ($CheckOnly) { Write-Output 'preflight-mode=check-only' }
Write-Output "residue-dirs-removed=$removed residue-dirs-failed=$($failed.Count) free-gb-before=$before free-gb-after=$after"
foreach ($f in $failed) { Write-Warning "could not sweep: $f" }
+100 -10
View File
@@ -1,31 +1,121 @@
#!/usr/bin/env bash
# require-all-green.sh — the aggregate gate step: fail unless every needed job
# succeeded (or was legitimately skipped).
# succeeded (or was legitimately skipped) and every SELECTED lane ran green.
#
# Canonical CI step (called by pr.yml ci-ok with RESULTS = toJSON(needs)).
# Lived inline in workflow YAML until 2026-07-26; the venue-parity contract
# forbids logic in workflow run-blocks. `skipped` counts as OK because gated
# jobs (e.g. pr-smoke on docs-only PRs) skip by design — a matrix rename can
# still never silently deadlock a merge, which is this gate's purpose.
# forbids logic in workflow run-blocks. `skipped` counts as OK at the needs
# level because gated jobs (e.g. pr-smoke on docs-only PRs) skip by design — a
# matrix rename can still never silently deadlock a merge, which is this
# gate's purpose.
#
# Usage: RESULTS='<json of the needs context>' scripts/ci/require-all-green.sh
# Lane selection (LANES + JOBS, both set by pr.yml): a PR runs only the lanes
# scripts/ci/select-lanes.sh selected, and there `skipped` is no longer a free
# pass — a selected lane whose jobs were skipped, cancelled, never created or
# are anything but success is a silent gate loss, so it fails. Lanes nobody
# selected may be skipped or absent (but may not fail); memwaste is
# report-only and never gates. The selection itself must be trustworthy: a
# list of known lanes that holds lint or contracts (every tier runs one).
#
# Usage: RESULTS='<json of the needs context>' \
# [LANES='<selector lanes, JSON list>' JOBS=<file>] \
# scripts/ci/require-all-green.sh
# JOBS: this run's jobs as JSON lines {"name","status","conclusion"}
# (gh api .../actions/runs/<id>/jobs?filter=latest --jq '.jobs[] | ...').
set -euo pipefail
case "${1:-}" in
-h | --help)
sed -n '2,11p' "$0" | sed 's/^# \{0,1\}//'
sed -n '2,24p' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
esac
: "${RESULTS:?set RESULTS to the toJSON(needs) context}"
UNIVERSE=""
if [ -n "${LANES+set}" ]; then
UNIVERSE=$(bash "$(dirname "$0")/select-lanes.sh" --list-lanes)
fi
printf '%s' "$RESULTS" | python3 -c "
import json, sys
import json, os, re, 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)
problems = [f'needed job {k}: {v}' for k, v in sorted(bad.items())]
if 'LANES' in os.environ:
universe = set(sys.argv[1].split())
lanes = None
try:
lanes = json.loads(os.environ['LANES'])
except ValueError:
pass
if not isinstance(lanes, list) or not all(isinstance(l, str) for l in lanes):
problems.append('LANES is not a JSON list of lane names: ' + repr(os.environ['LANES']))
lanes = []
selected = set(lanes)
if selected - universe:
problems.append('unknown lane(s) selected: ' + ', '.join(sorted(selected - universe)))
if not selected & {'lint', 'contracts'}:
problems.append('selection has neither lint nor contracts -- refusing to trust it')
# job name -> lane. Matrix jobs carry their lane among the values in
# parentheses; everything else is named by its (caller /) job id.
single = {
'lint / lint': 'lint', 'lint / lint-mem': 'lint-mem',
'security / security-static': 'security-static',
'security / license-gate': 'license-gate', 'security / codeql-gate': 'codeql-gate',
'test / test-diag': 'diag', 'test / test-msan': 'msan',
'test / test-lsan-macos': 'lsan-macos', 'test / test-windows-guards': 'windows-guards',
'test / shard-completeness': 'shard-completeness', 'contracts': 'contracts',
}
matrix = ('test / test-unix (', 'test / test-windows (', 'test / test-tsan (', 'pr-smoke (')
def lane_of(name):
if name in single:
return single[name]
if name.startswith('test / test-package-wrappers'):
return 'pkg-wrappers'
if name == 'memwaste' or name.startswith('memwaste / '):
return 'memwaste'
if name.startswith(matrix) and name.endswith(')'):
values = name[name.index('(') + 1:-1].split(', ')
hits = [v for v in values if v in universe]
return hits[0] if len(hits) == 1 else None
return None
jobs_by_lane = {}
path = os.environ.get('JOBS', '')
if not path or not os.path.isfile(path):
problems.append('JOBS (the run\'s job list) is missing: cannot verify the selected lanes')
else:
for line in open(path, encoding='utf-8'):
if line.strip():
job = json.loads(line)
lane = lane_of(job.get('name', ''))
if lane:
jobs_by_lane.setdefault(lane, []).append(job)
for lane in sorted(universe - {'memwaste'}):
jobs = jobs_by_lane.get(lane, [])
if lane in selected:
if not jobs:
problems.append(f'selected lane {lane}: no job ran')
for job in jobs:
state = job.get('conclusion') if job.get('status') == 'completed' else job.get('status')
if state != 'success':
problems.append(f'selected lane {lane}: {job[\"name\"]} is {state}')
else:
for job in jobs:
if job.get('status') == 'completed' and job.get('conclusion') not in ('success', 'skipped'):
problems.append(f'unselected lane {lane}: {job[\"name\"]} is {job[\"conclusion\"]}')
if not problems:
print('selected lanes green:', ', '.join(sorted(selected)))
if problems:
print('CI NOT OK:')
for p in problems:
print(' ' + p)
sys.exit(1)
print('CI OK:', ', '.join(needs))
"
" "$UNIVERSE"
+377
View File
@@ -0,0 +1,377 @@
#!/usr/bin/env bash
# select-lanes.sh — decide which PR CI lanes a change set must run.
#
# Canonical CI step (called by pr.yml's `changes` job on the PR's file list).
# This is the R1 rule set, measured against every PR push of September 2026
# (767 pushes; scripts/test-impact/replay-selector.sh replays them, plus the 22
# real failures, as a contract): each changed path gets a category, the
# categories give a tier, the tier gives a lane set, and a few categories add
# lanes of their own. `tier` is the classification the analysis measured;
# `full` overrides it with every lane.
#
# It FAILS SAFE: full=true for tier T5, a path no rule knows, an empty file
# list, a truncated one (file_list_truncated: the pulls/files API stops at
# 3000 files), this script, any workflow, the test harness, Makefile.cbm
# (source registration and flag changes look identical in a file list) and
# vendored code. It is a pure function of the file SET (and the expected
# count): order, duplicates, blank lines and CRLF never change a byte of the
# output.
#
# Usage: scripts/ci/select-lanes.sh [--format json|github-output]
# [--expect-files N] [FILE]
# FILE (default stdin): changed paths, one per line.
# --expect-files N: the PR's changed_files; fewer distinct paths, or
# N or the list at the 3000-file API cap, means truncated -> full.
# json (default): {"full","lanes","reasons","tier"} on one line.
# github-output: tier=, full=, lanes=, reasons= lines ($GITHUB_OUTPUT).
# scripts/ci/select-lanes.sh --batch FILE
# FILE: JSON lines {"id":..., "files":[...], "expect_files"?: N};
# prints one json result per line with its "id" (the history
# replay's single process).
# scripts/ci/select-lanes.sh --list-lanes
# Every lane name, one per line (the universe ci-ok checks against).
# Exit: 0 = decided · 2 = usage error.
set -euo pipefail
usage() { sed -n '2,33p' "$0" | sed 's/^# \{0,1\}//'; }
FORMAT=json
MODE="select"
INPUT=""
EXPECT=""
while [ $# -gt 0 ]; do
case "$1" in
-h | --help)
usage
exit 0
;;
--format)
FORMAT="${2:-}"
[ $# -gt 1 ] && shift
;;
--format=*) FORMAT="${1#--format=}" ;;
--batch)
MODE="batch"
INPUT="${2:-}"
[ $# -gt 1 ] && shift
;;
--list-lanes) MODE="list" ;;
--expect-files)
# An empty value (a workflow whose count went missing) must fail
# loudly, never quietly skip the truncation check.
EXPECT="${2:-}"
[ -n "$EXPECT" ] || EXPECT="missing"
[ $# -gt 1 ] && shift
;;
-*)
echo "select-lanes.sh: unknown option '$1'. Please consult --help." >&2
exit 2
;;
*)
if [ -n "$INPUT" ]; then
echo "select-lanes.sh: more than one input file. Please consult --help." >&2
exit 2
fi
INPUT="$1"
;;
esac
shift
done
case "$FORMAT" in
json | github-output) ;;
*)
echo "select-lanes.sh: --format must be json or github-output. Please consult --help." >&2
exit 2
;;
esac
case "${EXPECT:-0}" in
*[!0-9]*)
echo "select-lanes.sh: --expect-files takes a file count. Please consult --help." >&2
exit 2
;;
esac
if [ "$MODE" = batch ] && [ -z "$INPUT" ]; then
echo "select-lanes.sh: --batch needs a file. Please consult --help." >&2
exit 2
fi
if [ -n "$INPUT" ] && [ ! -f "$INPUT" ]; then
echo "select-lanes.sh: no such file '$INPUT'. Please consult --help." >&2
exit 2
fi
# The heredoc below is the program, so stdin input is staged in a file first.
STAGED=""
if [ "$MODE" = select ] && [ -z "$INPUT" ]; then
STAGED=$(mktemp "${TMPDIR:-/tmp}/cbm-select-lanes.XXXXXX")
trap 'rm -f "$STAGED"' EXIT
cat >"$STAGED"
INPUT="$STAGED"
fi
python3 - "$MODE" "$FORMAT" "$INPUT" "$EXPECT" <<'PY'
import json
import re
import sys
mode, fmt, source = sys.argv[1:4]
expect = int(sys.argv[4]) if sys.argv[4] else None
# ── Path -> category (first matching rule wins; order is part of the rule) ──
GATE_WORKFLOWS = {"pr.yml", "_test.yml", "_lint.yml", "_security.yml",
"_smoke.yml", "_memwaste.yml", "codeql.yml", "dco.yml"}
BUILD = {"Makefile.cbm", "scripts/test.sh", "scripts/run-tests-parallel.sh",
"scripts/env.sh", "scripts/build.sh", "scripts/clean.sh",
"scripts/path-safety.sh", "scripts/test-runtime.sh", "scripts/msan.sh",
"scripts/lint.sh", ".clang-format", ".clang-tidy",
"scripts/vendored-checksums.txt"}
SMOKE = {"scripts/smoke-test.sh", "scripts/smoke-local.sh",
"scripts/smoke-fixture-server.py", "scripts/smoke-invariants.sh",
"test-infrastructure/vm/vm-smoke.sh",
"test-infrastructure/vm/windows-user-path-guard.ps1",
"scripts/test-windows.ps1"}
LINT_CONFIG = {"scripts/memory-core-baseline.txt", "scripts/lint-memory-core.py",
"scripts/hooks/pre-commit"}
NON_GATING = {"scripts/package-release.sh", "scripts/benchmark-index.sh",
"scripts/benchmark-search-graph.sh", "scripts/benchmark-streaming.py",
"scripts/soak-test.sh", ".cbmignore", ".node-version", "flake.nix"}
PKG_FILES = {"install.sh", "install.ps1", "server.json", "package.json", "manifest.json"}
DOC_SUFFIXES = (".md", ".png", ".svg", ".jpg", ".gif", ".txt")
DOC_FILES = {"LICENSE", "CODEOWNERS", ".gitignore", ".gitattributes"}
def category(path):
base = path.rsplit("/", 1)[-1]
if path == "Makefile.cbm":
return "build"
if path in LINT_CONFIG:
return "lint-cfg"
if path.startswith(("scripts/memwaste", "scripts/memlab")):
return "memwaste-tools"
if path in NON_GATING or path.startswith("scripts/test-impact/"):
return "ci-other"
if path == "scripts/test_mcp_in_process.py":
return "test-contract"
if path == "scripts/fuzz.sh":
return "fuzz"
if path == "scripts/audit-license-provenance.py":
return "ci-gate"
if path.startswith(("internal/cbm/vendored/grammars/", "tools/tree-sitter-")):
return "grammar"
if path.startswith(".github/workflows/"):
return "ci-gate" if base in GATE_WORKFLOWS else "ci-other"
if path.startswith(".github/"):
if path.endswith(".md") or ("ISSUE_TEMPLATE" in path and path.endswith((".yml", ".yaml"))):
return "docs"
return "ci-other"
if path.startswith("scripts/ci/"):
return "ci-gate"
if path in BUILD:
return "build"
if path in SMOKE:
return "smoke"
if path.startswith("tests/fixtures/"):
return "test"
if path.startswith("tests/windows/"):
return "test-win"
if path.startswith("tests/fuzz/"):
return "fuzz"
if path.startswith("tests/"):
return "test-contract" if path.endswith((".sh", ".py", ".ps1")) else "test"
if path.startswith(("docs/", "eval-results/")) or base in DOC_FILES or (
path.endswith(DOC_SUFFIXES) and not path.startswith(("src/", "internal/", "scripts/"))):
return "docs"
if path.startswith("graph-ui/"):
return "ui-frontend"
if path.startswith(("pkg/", "scripts/setup")) or base in PKG_FILES:
return "pkg"
if path.startswith(("internal/cbm/vendored/", "vendored/")):
return "vendored"
m = re.match(r"internal/cbm/lsp/([a-z]+)_", path)
if m and m.group(1) not in ("lsp", "type", "scope"):
return "lsp:" + m.group(1)
if path.startswith("internal/cbm/lsp/"):
return "lsp-core"
if path.startswith("internal/cbm/"):
return "extract"
if path == "src/main.c":
return "cli"
if path.startswith("src/foundation/"):
return "mem-core" if re.search(r"/(mem|slab|arena|alloc|mimalloc)", path) else "foundation"
if path.startswith("src/"):
return "src:" + path.split("/")[1]
if path.startswith("test-infrastructure/"):
return "local-infra"
if path.startswith("scripts/"):
return "scripts-other"
return "other"
# ── Categories -> tier (highest wins) ──
EXTRACT = {"grammar", "lsp-core", "extract"}
MID = {"pipeline", "mcp", "store", "cypher", "discover", "git", "graph_buffer",
"semantic", "simhash", "traces", "personal_memory"}
PLATFORM = {"daemon", "cli", "watcher", "ui", "foundation", "smoke", "pkg", "test-win"}
CORE = {"mem-core", "build", "vendored", "ci-gate", "other", "scripts-other"}
LOWER = {"test", "test-contract", "fuzz", "ui-frontend", "ci-other",
"local-infra", "docs", "lint-cfg", "memwaste-tools"}
def normalized(cat):
# src/<dir>/... categories are the directory name; a directory no tier
# names stays unknown (and therefore T5 + full).
if cat.startswith("src:"):
name = cat[4:]
return name if name in MID | PLATFORM else cat
return cat
def known(cat):
return cat in EXTRACT | MID | PLATFORM | CORE | LOWER or cat.startswith("lsp:")
def tier_of(cats):
if cats & CORE or not all(known(c) for c in cats):
return "T5-core"
if cats & PLATFORM:
return "T4-platform"
if cats & MID:
return "T3-mid"
if cats & EXTRACT or any(c.startswith("lsp:") for c in cats):
return "T2-extract"
if cats & {"test", "test-contract", "fuzz"}:
return "T1-tests"
if cats & {"ui-frontend", "ci-other", "local-infra", "lint-cfg", "memwaste-tools"}:
return "T0b-nonproduct"
return "T0a-docs"
# ── Tier -> lanes (R1) ──
FULL = {"codeql-gate", "diag", "license-gate", "lint", "lint-mem", "lsan-macos",
"memwaste", "msan", "pkg-wrappers", "security-static", "shard-completeness",
"smoke-mac", "smoke-ubuntu", "smoke-win", "tsan-arm", "tsan-mac", "tsan-x86",
"unix-arm64", "unix-macos-intel", "unix-macos14", "unix-x86", "windows",
"windows-guards"}
UNIVERSE = FULL | {"contracts"} # contracts: the T0a/T0b job; every test leg runs them too
LINT = {"lint", "lint-mem"}
CORE_LEGS = {"unix-x86", "unix-macos14", "windows", "shard-completeness"}
TEST_LEGS = {"unix-x86", "unix-arm64", "unix-macos14", "unix-macos-intel", "windows"}
ALWAYS = {"security-static"}
R1 = {
"T0a-docs": {"contracts"},
# The contract steps police test-infrastructure/ and lint config too.
"T0b-nonproduct": LINT | {"contracts"},
"T1-tests": LINT | CORE_LEGS,
"T2-extract": LINT | CORE_LEGS | {"codeql-gate", "tsan-x86", "smoke-ubuntu"},
"T3-mid": LINT | CORE_LEGS | {"codeql-gate", "tsan-x86", "smoke-ubuntu"},
"T4-platform": FULL - {"msan", "diag", "memwaste"},
"T5-core": FULL,
}
# Category add-ons: (categories, lanes). pkg adds nothing to a docs-only set
# because a pkg path is never docs-only (it lifts the tier to T4).
ADD_ONS = [
({"pkg"}, {"pkg-wrappers", "smoke-ubuntu", "smoke-mac", "smoke-win"}),
({"vendored", "pkg", "ci-gate", "build"}, {"license-gate"}),
({"ui-frontend"}, {"windows-guards", "smoke-ubuntu"}),
({"memwaste-tools", "mem-core"}, {"memwaste"}),
({"test-win"}, {"windows", "windows-guards"}),
# the contract steps run inside test.sh's default leg
({"ci-other", "test-contract", "fuzz"}, {"unix-x86"}),
]
HARNESS = {"scripts/test.sh", "scripts/run-tests-parallel.sh", "scripts/run-test-wave.py",
"tests/test_main.c", "tests/test_framework.h"}
def triggers(path, cat):
found = []
if path == "scripts/ci/select-lanes.sh":
found.append("the lane selector itself")
if path.startswith(".github/workflows/"):
found.append("workflow")
if path in HARNESS:
found.append("test harness")
if path == "Makefile.cbm":
found.append("Makefile.cbm (registration and flag changes are indistinguishable by name)")
if cat == "vendored":
found.append("vendored code")
if cat in ("other", "scripts-other") or not known(cat):
found.append("path matched by no rule")
return found
LIST_CAP = 3000 # the pulls/files API lists at most this many files
def truncated(paths, expect):
"""A list that may be missing files: at or over the API cap, or shorter
than the PR's changed_files (a complete list is never shorter: every
changed file is listed, renames add their old path on top)."""
if len(paths) >= LIST_CAP:
return f"{len(paths)} paths listed, at the {LIST_CAP}-file API cap"
if expect is not None and expect >= LIST_CAP:
return f"the PR changes {expect} files, over the {LIST_CAP}-file API cap"
if expect is not None and len(paths) < expect:
return f"{len(paths)} paths listed for {expect} changed files"
return None
def select(paths, expect=None):
paths = sorted({p for p in paths if p})
by_cat, by_trigger = {}, {}
for path in paths:
cat = normalized(category(path))
by_cat.setdefault(cat, []).append(path)
for trigger in triggers(path, cat):
by_trigger.setdefault(trigger, []).append(path)
cats = set(by_cat)
tier = tier_of(cats)
lanes = R1[tier] | ALWAYS
reasons = [f"category {c}: {len(p)} file(s), e.g. {p[0]}" for c, p in by_cat.items()]
for when, extra in ADD_ONS:
hit = sorted(cats & when)
if hit and tier != "T0a-docs" and not extra <= lanes:
reasons.append(f"add-on {'/'.join(hit)}: +{' +'.join(sorted(extra - lanes))}")
lanes = lanes | extra
if lanes & TEST_LEGS:
lanes = lanes | {"shard-completeness"}
for trigger, hits in by_trigger.items():
more = f" (+{len(hits) - 1} more)" if len(hits) > 1 else ""
reasons.append(f"full: {trigger}: {hits[0]}{more}")
if not paths:
reasons.append("full: empty file list (fail safe)")
if tier == "T5-core":
reasons.append("full: tier T5-core")
short = truncated(paths, expect)
if short:
reasons.append(f"full: file_list_truncated: {short} (fail safe)")
full = tier == "T5-core" or bool(by_trigger) or not paths or bool(short)
if full:
lanes = set(FULL)
return {"full": full, "lanes": sorted(lanes), "reasons": sorted(reasons), "tier": tier}
def dump(obj):
return json.dumps(obj, sort_keys=True, separators=(",", ":"))
def read_paths(path):
with open(path, encoding="utf-8") as fh: # universal newlines: CRLF == LF
return [line.strip() for line in fh]
if mode == "list":
print("\n".join(sorted(UNIVERSE)))
elif mode == "batch":
with open(source, encoding="utf-8") as fh:
for line in fh:
if line.strip():
row = json.loads(line)
print(dump({"id": row["id"], **select(row["files"], row.get("expect_files"))}))
elif fmt == "github-output":
decision = select(read_paths(source), expect)
print(f"tier={decision['tier']}")
print(f"full={'true' if decision['full'] else 'false'}")
print(f"lanes={dump(decision['lanes'])}")
print(f"reasons={dump(decision['reasons'])}")
else:
print(dump(select(read_paths(source), expect)))
PY
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
# test-impact-bundle.sh — fetch the team artifact bundle of one main commit.
#
# The bundle (codebase-memory-mcp test-impact publish; src/mcp/test_impact_artifact.h)
# is published by .github/workflows/test-impact-artifact.yml as the Actions
# artifact `test-impact-<sha>`. A selection may pass --artifact-verified ONLY
# for a bundle this script fetched: it accepts nothing but a SUCCESSFUL run of
# that workflow, in THIS repository, on the main branch, whose head is exactly
# the requested commit — the provenance the receipt itself can never assert.
#
# Usage: scripts/ci/test-impact-bundle.sh --fetch SHA DIR
# Download the bundle of SHA into DIR (created). Needs GH_TOKEN with
# actions:read; $GH overrides the gh command, $GITHUB_REPOSITORY the
# repository (OWNER/NAME).
# Exit: 0 = fetched and verified · 3 = no admissible bundle for SHA ·
# 1 = the tooling broke · 2 = usage error.
set -euo pipefail
GH="${GH:-gh}"
REPO="${GITHUB_REPOSITORY:-DeusData/codebase-memory-mcp}"
WORKFLOW="test-impact-artifact.yml"
usage() { sed -n '2,19p' "$0" | sed 's/^# \{0,1\}//'; }
[ $# -eq 3 ] && [ "$1" = "--fetch" ] || { usage >&2; exit 2; }
SHA="$2"
DIR="$3"
case "$SHA" in
*[!0-9a-f]* | "") echo "test-impact-bundle: not a lowercase commit id: $SHA" >&2; exit 2 ;;
esac
[ ${#SHA} -eq 40 ] || [ ${#SHA} -eq 64 ] || { echo "test-impact-bundle: bad id length" >&2; exit 2; }
# The newest successful main-branch run of the producer for exactly this head.
run=$("$GH" api -X GET "repos/$REPO/actions/workflows/$WORKFLOW/runs" \
-f branch=main -f status=success -f head_sha="$SHA" -f per_page=20 \
--jq '[.workflow_runs[]
| select(.head_sha == "'"$SHA"'" and .head_branch == "main"
and .repository.full_name == "'"$REPO"'"
and .head_repository.full_name == "'"$REPO"'"
and (.event == "push" or .event == "schedule"
or .event == "workflow_dispatch"))]
| sort_by(.run_started_at) | last | .id // empty') || exit 1
if [ -z "$run" ]; then
echo "test-impact-bundle: no successful producer run for $SHA" >&2
exit 3
fi
mkdir -p "$DIR"
if ! "$GH" run download "$run" --repo "$REPO" --name "test-impact-$SHA" --dir "$DIR"; then
echo "test-impact-bundle: run $run has no artifact test-impact-$SHA" >&2
exit 3
fi
# The receipt must name the commit the run was for; the engine checks the rest.
if ! python3 - "$DIR/receipt.json" "$SHA" <<'PY'
import json, sys
receipt = json.load(open(sys.argv[1]))
sys.exit(0 if receipt.get("commit") == sys.argv[2] else 1)
PY
then
echo "test-impact-bundle: the receipt does not name $SHA" >&2
exit 3
fi
echo "test-impact-bundle: run $run, bundle of $SHA in $DIR"
+114
View File
@@ -0,0 +1,114 @@
#!/usr/bin/env bash
# test-impact-publish.sh — publish the team artifact bundle of the checked-out
# main commit (.github/workflows/test-impact-artifact.yml).
#
# Incremental (a push): the previous commit's bundle is the graph base and its
# coverage map the starting point; only the suites the push's own selection
# picks are re-run with coverage and merged in (scripts/test-impact/
# coverage-merge.py). The oldest observation is carried forward, so an
# incremental map ages out unless a full refresh runs.
# Full (weekly, manual, or no usable previous bundle): every suite is re-run
# and the graph is built from scratch. A weekly run ALSO builds the graph
# incrementally from the previous bundle and fails when the two graphs'
# content digests differ: an incremental index must equal a full one.
#
# Usage: scripts/ci/test-impact-publish.sh --binary PATH --runner PATH --out DIR
# --mode incremental|full [--before SHA] [--llvm-bin DIR] [--platform LABEL]
# Exit: 0 = published · 1 = failed · 2 = usage error.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
BINARY="" RUNNER="" OUT="" MODE="" BEFORE="" LLVM_BIN="" PLATFORM="linux-x86_64-clang21"
while [ $# -gt 0 ]; do
case "$1" in
--binary) BINARY="$2"; shift 2 ;;
--runner) RUNNER="$2"; shift 2 ;;
--out) OUT="$2"; shift 2 ;;
--mode) MODE="$2"; shift 2 ;;
--before) BEFORE="$2"; shift 2 ;;
--llvm-bin) LLVM_BIN="$2"; shift 2 ;;
--platform) PLATFORM="$2"; shift 2 ;;
*) sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//' >&2; exit 2 ;;
esac
done
[ -n "$BINARY" ] && [ -n "$RUNNER" ] && [ -n "$OUT" ] || exit 2
case "$MODE" in incremental | full) ;; *) exit 2 ;; esac
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
# A private daemon rendezvous: the socket path must fit sun_path.
export CBM_RUNTIME_DIR="$(mktemp -d /tmp/cbm-rt.XXXXXX)"
export CBM_CACHE_DIR="$WORK/cache"
mkdir -p "$WORK/engine" && chmod 700 "$WORK/engine"
HEAD_SHA="$(git -C "$ROOT" rev-parse HEAD)"
PREVIOUS=""
if [ -n "$BEFORE" ]; then
if scripts/ci/test-impact-bundle.sh --fetch "$BEFORE" "$WORK/previous"; then
PREVIOUS="$WORK/previous"
fi
fi
if [ "$MODE" = incremental ] && [ -z "$PREVIOUS" ]; then
echo "test-impact-publish: no previous bundle for ${BEFORE:-?}: full refresh"
MODE=full
fi
map() { # out [suite...]
local out="$1"; shift
local args=()
for suite in "$@"; do args+=(--suite "$suite"); done
python3 "$ROOT/scripts/test-impact/coverage-map.py" --runner "$RUNNER" --out "$out" \
${LLVM_BIN:+--llvm-bin "$LLVM_BIN"} ${args[@]+"${args[@]}"}
}
OBSERVED=""
if [ "$MODE" = full ]; then
OBSERVED="$(date +%s)"
map "$WORK/coverage"
else
# The push's own selection decides what to re-run (whole suites).
"$BINARY" test-impact select --repo "$ROOT" --base "$BEFORE" --work "$WORK/engine" \
--artifact "$PREVIOUS" --artifact-commit "$BEFORE" --artifact-verified \
--platform "$PLATFORM" > "$WORK/selection.json"
python3 "$ROOT/scripts/test-impact/selection.py" suites "$WORK/selection.json" > "$WORK/suites.txt"
"$RUNNER" --list-suites > "$WORK/listed.txt"
if grep -qx '\*' "$WORK/suites.txt"; then
OBSERVED="$(date +%s)"
map "$WORK/coverage"
else
mapfile -t rerun < <(sort -u "$WORK/suites.txt")
map "$WORK/fresh" ${rerun[@]+"${rerun[@]}"}
python3 "$ROOT/scripts/test-impact/coverage-merge.py" --previous "$PREVIOUS/coverage" \
--fresh "$WORK/fresh" --suites "$WORK/listed.txt" --commit "$HEAD_SHA" \
--out "$WORK/coverage"
fi
fi
publish() { # out [previous]
"$BINARY" test-impact publish --repo "$ROOT" --out "$1" --work "$WORK/engine" \
--coverage "$WORK/coverage" ${OBSERVED:+--observed-at "$OBSERVED"} \
--platform "$PLATFORM" ${2:+--previous "$2"}
}
if [ "$MODE" = full ]; then
publish "$OUT"
if [ -n "$PREVIOUS" ]; then
publish "$WORK/incremental" "$PREVIOUS"
field() { python3 -c 'import json,sys;print(json.load(open(sys.argv[1]))[sys.argv[2]])' "$1" "$2"; }
# Topology (what selection reads) must be equal; edge properties and the
# corpus-statistical edges are reported, not gated (recorded defects).
full_topology="$(field "$OUT/receipt.json" graph_topology_sha256)"
incr_topology="$(field "$WORK/incremental/receipt.json" graph_topology_sha256)"
if [ "$full_topology" != "$incr_topology" ]; then
echo "test-impact-publish: incremental topology differs from the full one ($incr_topology != $full_topology)" >&2
exit 1
fi
if [ "$(field "$OUT/receipt.json" graph_content_sha256)" != \
"$(field "$WORK/incremental/receipt.json" graph_content_sha256)" ]; then
echo "::warning::incremental graph content (edge properties, similarity edges) differs from the full one; topology is equal"
fi
echo "test-impact-publish: incremental topology equals the full one ($full_topology)"
fi
else
publish "$OUT" "$PREVIOUS"
fi
echo "test-impact-publish: bundle of $HEAD_SHA in $OUT ($MODE)"
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env bash
# test-impact-select.sh — the PR selection with the team artifact: what
# `codebase-memory-mcp test-impact select` runs for this change.
#
# Shadow (default): REPORT-ONLY; pr.yml's test-impact-shadow job runs it
# continue-on-error and ci-ok does not need it.
# --gate DIR (pr.yml's select-tests job, only when the repository variable
# TEST_IMPACT_MODE is `gate`): also turns the answer into what the test legs
# run (scripts/test-impact/selection.py) — DIR/test-only.txt and
# DIR/optional-suites.txt for a narrowed selection — and writes
# `selection=narrowed` or `selection=all` to $GITHUB_OUTPUT. Anything but a
# well-formed narrowed answer, a lane that selects nothing included, is `all`.
#
# The base is the base side of the PR merge commit (HEAD^1 by default). Its
# bundle is fetched with scripts/ci/test-impact-bundle.sh, which only accepts
# the main-branch producer's own run for exactly that commit; only then does
# the selection pass --artifact-verified. Without a bundle the engine builds
# the graph itself and admits no coverage: every reached suite runs whole.
#
# Usage: scripts/ci/test-impact-select.sh --binary PATH --out DIR [--base REV]
# [--platform LABEL] [--gate DIR]
# Writes DIR/selection-engine.json and DIR/summary-engine.md (appended
# to $GITHUB_STEP_SUMMARY when set).
# Exit: 0 = selection written · 1 = the tooling broke · 2 = usage error.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
BINARY="" OUT="" BASE="HEAD^1" PLATFORM="linux-x86_64-clang21" GATE=""
while [ $# -gt 0 ]; do
case "$1" in
--binary) BINARY="$2"; shift 2 ;;
--out) OUT="$2"; shift 2 ;;
--base) BASE="$2"; shift 2 ;;
--platform) PLATFORM="$2"; shift 2 ;;
--gate) GATE="$2"; shift 2 ;;
*) sed -n '2,24p' "$0" | sed 's/^# \{0,1\}//' >&2; exit 2 ;;
esac
done
[ -n "$BINARY" ] && [ -n "$OUT" ] || exit 2
mkdir -p "$OUT"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
export CBM_RUNTIME_DIR="$(mktemp -d /tmp/cbm-rt.XXXXXX)" # sun_path must fit
export CBM_CACHE_DIR="$WORK/cache"
mkdir -p "$WORK/engine" && chmod 700 "$WORK/engine"
base="$(git -C "$ROOT" rev-parse "$BASE")"
artifact=()
bundle_rc=0
"$ROOT/scripts/ci/test-impact-bundle.sh" --fetch "$base" "$WORK/bundle" || bundle_rc=$?
if [ "$bundle_rc" -eq 0 ]; then
artifact=(--artifact "$WORK/bundle" --artifact-commit "$base" --artifact-verified)
elif [ "$bundle_rc" -ne 3 ]; then
# No bundle means no coverage: more tests run, never fewer. Say so.
echo "::warning::test-impact bundle fetch failed (exit $bundle_rc): selecting without coverage"
fi
started=$(date +%s)
"$BINARY" test-impact select --repo "$ROOT" --base "$base" --work "$WORK/engine" \
--platform "$PLATFORM" ${artifact[@]+"${artifact[@]}"} > "$OUT/selection-engine.json"
elapsed=$(( $(date +%s) - started ))
python3 - "$OUT/selection-engine.json" "$elapsed" "${#artifact[@]}" > "$OUT/summary-engine.md" <<'PY'
import json, sys
answer = json.load(open(sys.argv[1]))
coverage = answer.get("receipt", {}).get("coverage", {})
lines = ["### test-impact engine", "",
f"- decision: `{answer.get('decision')}` in {sys.argv[2]} s",
f"- team artifact: {'fetched' if sys.argv[3] != '0' else 'none'}; coverage "
f"`{coverage.get('state')}` {coverage.get('rejection_reasons') or ''}"]
for lane in answer.get("lanes") or []:
suites = lane.get("suites") or []
whole = [s["suite"] for s in suites if s.get("mode") == "whole"]
narrowed = {s["suite"]: len(s.get("tests") or []) for s in suites if s.get("mode") == "tests"}
lines.append(f"- lane `{lane.get('lane')}` ({lane.get('decision')}): {len(whole)} whole, "
f"{len(narrowed)} narrowed ({sum(narrowed.values())} tests)")
print("\n".join(lines))
PY
if [ -n "$GATE" ]; then
mkdir -p "$GATE"
selection=all
filter_rc=0
python3 "$ROOT/scripts/test-impact/selection.py" filter "$OUT/selection-engine.json" \
--lane unit --out "$GATE/test-only.txt" --optional-out "$GATE/optional-suites.txt" ||
filter_rc=$?
if [ "$filter_rc" -eq 0 ]; then
selection=narrowed
echo "- PR CI runs $(wc -l < "$GATE/test-only.txt" | tr -d ' ') selection tokens" \
>> "$OUT/summary-engine.md"
else
rm -f "$GATE/test-only.txt" "$GATE/optional-suites.txt"
echo "- PR CI runs every test (selection exit $filter_rc)" >> "$OUT/summary-engine.md"
fi
if [ -n "${GITHUB_OUTPUT:-}" ]; then
echo "selection=$selection" >> "$GITHUB_OUTPUT"
fi
fi
cat "$OUT/summary-engine.md"
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
cat "$OUT/summary-engine.md" >> "$GITHUB_STEP_SUMMARY"
fi
+168
View File
@@ -0,0 +1,168 @@
#!/usr/bin/env bash
# test-impact-shadow.sh — the PR test-impact SHADOW: predict from the code graph
# which C test suites a change can affect, and publish the prediction. It is
# REPORT-ONLY: pr.yml runs it continue-on-error and ci-ok does not need it, so
# it can never block or pass a merge. Its predictions accumulate next to the
# lanes that actually ran and failed, which is how graph-based selection gets
# evaluated before anyone gates on it.
#
# The binary is the latest PUBLISHED release, not this checkout's build: a few
# seconds of download instead of minutes of compile, and a PR that breaks the
# indexer cannot corrupt its own prediction. pr.yml builds the checkout only
# if the download fails.
#
# Usage: scripts/ci/test-impact-shadow.sh --fetch-release DIR [--repo OWNER/REPO]
# Download the latest release's archive for this host + checksums.txt
# into DIR, verify the SHA-256, extract DIR/codebase-memory-mcp.
# Needs GH_TOKEN (read-only); $GH overrides the gh command.
# scripts/ci/test-impact-shadow.sh --binary PATH --out DIR [--base REV] [--depth N]
# Index this checkout into a throwaway cache (HOME, XDG dirs and
# CBM_CACHE_DIR under DIR/work, a private CBM_RUNTIME_DIR under /tmp:
# the daemon's socket path must fit sun_path), run
# detect_changes against REV (default HEAD^1, the base side of a PR
# merge commit) at depth N (default 15), and let
# scripts/test-impact/predict.py map it to suites with the lane
# selector's run-all triggers. Writes DIR/prediction.json and
# DIR/summary.md (appended to $GITHUB_STEP_SUMMARY when set). The
# graph is only consulted when C sources changed.
# Exit: 0 = prediction written · 1 = the tooling broke · 2 = usage error.
set -euo pipefail
usage() { sed -n '2,28p' "$0" | sed 's/^# \{0,1\}//'; }
FETCH_DIR=""
REPO="${GITHUB_REPOSITORY:-DeusData/codebase-memory-mcp}"
BINARY=""
OUT=""
BASE="HEAD^1"
DEPTH=15
while [ $# -gt 0 ]; do
case "$1" in
-h | --help)
usage
exit 0
;;
--fetch-release | --repo | --binary | --out | --base | --depth)
if [ $# -lt 2 ]; then
echo "test-impact-shadow.sh: $1 needs a value. Please consult --help." >&2
exit 2
fi
case "$1" in
--fetch-release) FETCH_DIR="$2" ;;
--repo) REPO="$2" ;;
--binary) BINARY="$2" ;;
--out) OUT="$2" ;;
--base) BASE="$2" ;;
--depth) DEPTH="$2" ;;
esac
shift
;;
*)
echo "test-impact-shadow.sh: unknown argument '$1'. Please consult --help." >&2
exit 2
;;
esac
shift
done
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
sha256_check() { # file with "<sha> <name>" lines, run in the archive's dir
if command -v sha256sum >/dev/null 2>&1; then
sha256sum -c "$1"
else
shasum -a 256 -c "$1"
fi
}
if [ -n "$FETCH_DIR" ]; then
case "$(uname -s)/$(uname -m)" in
Linux/x86_64) asset=codebase-memory-mcp-linux-amd64.tar.gz ;;
Linux/aarch64 | Linux/arm64) asset=codebase-memory-mcp-linux-arm64.tar.gz ;;
Darwin/arm64) asset=codebase-memory-mcp-darwin-arm64.tar.gz ;;
Darwin/x86_64) asset=codebase-memory-mcp-darwin-amd64.tar.gz ;;
*)
echo "test-impact-shadow.sh: no release archive for $(uname -s)/$(uname -m)" >&2
exit 1
;;
esac
read -r -a gh_cmd <<<"${GH:-gh}"
mkdir -p "$FETCH_DIR"
# No tag: the latest release.
"${gh_cmd[@]}" release download --repo "$REPO" --pattern "$asset" \
--pattern checksums.txt --dir "$FETCH_DIR" --clobber
grep " $asset\$" "$FETCH_DIR/checksums.txt" >"$FETCH_DIR/asset.sha256"
(cd "$FETCH_DIR" && sha256_check asset.sha256)
tar -xzf "$FETCH_DIR/$asset" -C "$FETCH_DIR" codebase-memory-mcp
echo "release binary: $FETCH_DIR/codebase-memory-mcp ($asset, SHA-256 verified)"
exit 0
fi
if [ -z "$BINARY" ] || [ -z "$OUT" ]; then
echo "test-impact-shadow.sh: --binary and --out are required. Please consult --help." >&2
exit 2
fi
case "$DEPTH" in
'' | *[!0-9]*)
echo "test-impact-shadow.sh: --depth must be a number. Please consult --help." >&2
exit 2
;;
esac
BINARY="$(cd "$(dirname "$BINARY")" && pwd)/$(basename "$BINARY")"
PREDICT="$ROOT/scripts/test-impact/predict.py"
mkdir -p "$OUT"
OUT="$(cd "$OUT" && pwd)"
WORK="$OUT/work"
rm -rf "$WORK"
mkdir -p "$WORK/home/.config" "$WORK/home/.cache" "$WORK/cache"
# <runtime>/cbm-daemon-<uid>/cbm-<key>.sock adds ~42 bytes to this path, and
# sun_path holds 104 (macOS) / 108: a deep $OUT or $TMPDIR does not fit.
RUNTIME=$(mktemp -d /tmp/cbm-rt.XXXXXX)
trap 'rm -rf "$RUNTIME"' EXIT
BASE_SHA=$(git -C "$ROOT" rev-parse --verify "$BASE^{commit}")
git -C "$ROOT" diff --name-only --no-renames "$BASE_SHA" HEAD >"$OUT/changed-files.txt"
bash "$ROOT/scripts/ci/select-lanes.sh" "$OUT/changed-files.txt" >"$OUT/selection.json"
# From here on nothing may touch the host's real config, cache or daemon.
export HOME="$WORK/home"
export XDG_CONFIG_HOME="$WORK/home/.config"
export XDG_CACHE_HOME="$WORK/home/.cache"
export CBM_CACHE_DIR="$WORK/cache"
export CBM_RUNTIME_DIR="$RUNTIME"
VERSION=$("$BINARY" --version 2>/dev/null | head -1 || true)
DETECT_ARGS=()
T_INDEX="-"
T_DETECT="-"
if [ "$(python3 "$PREDICT" --needs-graph --changed "$OUT/changed-files.txt")" = yes ]; then
python3 -c 'import json, sys; json.dump({"repo_path": sys.argv[1]}, open(sys.argv[2], "w"))' \
"$ROOT" "$WORK/index-args.json"
t0=$(date +%s)
"$BINARY" cli --quiet index_repository --args-file "$WORK/index-args.json" >"$WORK/index.json"
t1=$(date +%s)
python3 -c '
import json, sys
project = json.load(open(sys.argv[1]))["project"]
json.dump({"project": project, "base_branch": sys.argv[2], "depth": int(sys.argv[3]),
"limit": 5000, "changed_limit": 5000, "module_limit": 0,
"max_output_tokens": 1000000, "format": "json"}, open(sys.argv[4], "w"))
' "$WORK/index.json" "$BASE_SHA" "$DEPTH" "$WORK/detect-args.json"
"$BINARY" cli --quiet detect_changes --args-file "$WORK/detect-args.json" >"$WORK/detect.json"
t2=$(date +%s)
# The CLI started a private daemon under $RUNTIME; stop it (best effort).
"$BINARY" daemon stop >/dev/null 2>&1 || true
T_INDEX="$((t1 - t0))s"
T_DETECT="$((t2 - t1))s"
DETECT_ARGS=(--detect "$WORK/detect.json")
fi
META=$(python3 -c 'import json, sys; print(json.dumps(dict(zip(sys.argv[1::2], sys.argv[2::2]))))' \
binary "${VERSION:-unknown}" base "$BASE_SHA" "index time" "$T_INDEX" "detect time" "$T_DETECT")
python3 "$PREDICT" --root "$ROOT" --changed "$OUT/changed-files.txt" \
--selection "$OUT/selection.json" ${DETECT_ARGS[@]+"${DETECT_ARGS[@]}"} --depth "$DEPTH" \
--meta "$META" --out "$OUT/prediction.json" --summary "$OUT/summary.md"
cat "$OUT/summary.md"
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
cat "$OUT/summary.md" >>"$GITHUB_STEP_SUMMARY"
fi
+49 -7
View File
@@ -8,24 +8,56 @@
# For every leg it asserts: all shards agree on the shard count, indices form
# exactly 1..n, every shard saw the same full suite list, and the UNION of the
# shard slices equals that list — a rename/re-shard can never silently drop a
# suite (gate-quality loss) without failing here.
# suite (gate-quality loss) without failing here. A narrowed leg (PR CI test
# selection) records the selected list and its selection_sha256; all shards
# must agree on that too.
#
# Usage: scripts/ci/verify-shard-union.sh <manifests-dir>
# No manifests at all is a failure — unless the PR's lane selection (--lanes,
# the JSON list from scripts/ci/select-lanes.sh) holds no test leg, in which
# case no leg ran by design. Without --lanes (or with "all": dry run,
# release) every leg is expected, exactly as before lane selection.
#
# Usage: scripts/ci/verify-shard-union.sh <manifests-dir> [--lanes <json-list>|all]
set -eu
case "${1:-}" in
-h | --help)
sed -n '2,13p' "$0" | sed 's/^# \{0,1\}//'
sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
esac
MANIFEST_DIR="${1:?usage: verify-shard-union.sh <manifests-dir> (see --help)}"
LANES=all
if [ "${2:-}" = "--lanes" ]; then
LANES="${3:-}"
elif [ -n "${2:-}" ]; then
echo "verify-shard-union.sh: unknown argument '$2'. Please consult --help." >&2
exit 2
fi
case "$LANES" in
all | \[*\]) ;;
*)
echo "verify-shard-union.sh: --lanes must be 'all' or a JSON list. Please consult --help." >&2
exit 2
;;
esac
files=$(find "$MANIFEST_DIR" -name shard-manifest.txt | sort)
files=""
if [ -d "$MANIFEST_DIR" ]; then
files=$(find "$MANIFEST_DIR" -name shard-manifest.txt | sort)
fi
if [ -z "$files" ]; then
echo "FAIL: no shard manifests were uploaded" >&2
exit 1
# The test legs are the sharded ones: unix-* and windows (matched with its
# quotes, so the windows-guards lane never reads as the windows leg).
case "$LANES" in
all | *\"unix-* | *\"windows\"*)
echo "FAIL: no shard manifests were uploaded" >&2
exit 1
;;
esac
echo "OK: no test leg was selected ($LANES) — no manifests expected"
exit 0
fi
rc=0
for leg in $(grep -h '^leg=' $files | sort -u | sed 's/^leg=//'); do
@@ -48,9 +80,19 @@ for leg in $(grep -h '^leg=' $files | sort -u | sed 's/^leg=//'); do
rc=1
continue
fi
# A narrowed leg: every shard must have applied the same test selection
# (two selections can name the same suites and different tests).
sel_sha=$(for f in $leg_files; do
grep -h '^selection_sha256=' "$f" || echo "selection_sha256=none"
done | sort -u)
if [ "$(printf '%s\n' "$sel_sha" | wc -l)" -ne 1 ]; then
echo "FAIL: $leg shards applied different test selections" >&2
rc=1
continue
fi
union_sha=$(for f in $leg_files; do
sed -n '/^--- slice ---$/,$p' "$f" | tail -n +2
done | sort | sha256sum | awk '{print $1}')
done | sort | { sha256sum 2>/dev/null || shasum -a 256; } | awk '{print $1}')
if [ "$union_sha" != "$list_sha" ]; then
echo "FAIL: $leg union of shard slices != full suite list (GATE-QUALITY LOSS)" >&2
rc=1
+7 -8
View File
@@ -22,7 +22,7 @@ src/cli/client_adapter.c 3
src/cli/config_edit_path.c 2
src/cli/config_json_like.c 142
src/cli/config_text_edit.c 72
src/cli/config_toml_edit.c 85
src/cli/config_toml_edit.c 73
src/cli/config_yaml_edit.c 117
src/cli/hook_augment.c 31
src/cypher/cypher.c 132
@@ -44,25 +44,24 @@ src/foundation/platform.c 11
src/foundation/platform.h 5
src/foundation/private_file_lock.c 56
src/foundation/str_intern.c 7
src/foundation/subprocess.c 24
src/foundation/win_utf8.h 17
src/foundation/subprocess.c 17
src/foundation/win_utf8.h 7
src/foundation/workspace.c 3
src/foundation/yaml.c 10
src/git/git_context.c 18
src/main.c 32
src/mcp/compact_out.c 39
src/mcp/index_supervisor.c 8
src/mcp/mcp.c 778
src/mcp/mcp.c 773
src/pipeline/artifact.c 38
src/pipeline/fqn.c 23
src/pipeline/lsp_resolve.h 4
src/pipeline/lsp_surface.c 5
src/pipeline/pass_calls.c 22
src/pipeline/pass_compile_commands.c 17
src/pipeline/pass_complexity.c 15
src/pipeline/pass_configlink.c 3
src/pipeline/pass_cross_repo.c 8
src/pipeline/pass_definitions.c 14
src/pipeline/pass_definitions.c 11
src/pipeline/pass_enrichment.c 15
src/pipeline/pass_ensemble_routing.c 10
src/pipeline/pass_envscan.c 2
@@ -70,7 +69,7 @@ src/pipeline/pass_githistory.c 27
src/pipeline/pass_importance.c 4
src/pipeline/pass_infrascan.c 2
src/pipeline/pass_k8s.c 6
src/pipeline/pass_lsp_cross.c 50
src/pipeline/pass_lsp_cross.c 49
src/pipeline/pass_parallel.c 31
src/pipeline/pass_pkgmap.c 82
src/pipeline/pass_semantic.c 3
@@ -79,7 +78,7 @@ src/pipeline/pass_usages.c 4
src/pipeline/path_alias.c 42
src/pipeline/pipeline.c 134
src/pipeline/pipeline_delta.c 19
src/pipeline/pipeline_incremental.c 144
src/pipeline/pipeline_incremental.c 143
src/pipeline/registry.c 17
src/pipeline/worker_pool.c 2
src/store/store.c 358
+58 -2
View File
@@ -35,7 +35,10 @@ if [ -z "$JOBS" ]; then
fi
fi
LOGDIR="$(dirname "$RUNNER")/test-logs"
# CBM_TEST_LOG_DIR: a private log directory (tests/test_harness_selection.sh
# drives this harness before the real run and must not touch its logs). Keep
# it relative: the native Windows runner reads the selection file below it.
LOGDIR="${CBM_TEST_LOG_DIR:-$(dirname "$RUNNER")/test-logs}"
rm -rf "$LOGDIR"
mkdir -p "$LOGDIR"
@@ -129,6 +132,54 @@ if [ "$NSUITES" -lt 1 ] || grep -qvE '^[a-z0-9_]+$' "$SUITES_FILE"; then
exit 1
fi
# Test selection (smart CI; PR CI only, scripts/test-impact/selection.py).
# CBM_TEST_SELECTION_FILE lists `suite` / `suite:test` tokens. Only the suites
# it names are launched, and every launched runner judges its own tokens
# through CBM_TEST_ONLY_FILE, so a selected test the runner cannot find fails
# that suite. A selected suite this build does not register fails the run,
# unless CBM_TEST_SELECTION_OPTIONAL names it (registered only in some builds,
# e.g. the coverage runner): then it is dropped with a notice, exactly as a
# full run of this build never runs it. Everything below — sharding, the
# manifest, the union guard — then works on the selected list. Unset: the
# full list, unchanged.
SELECTION="none"
if [ -n "${CBM_TEST_SELECTION_FILE:-}" ]; then
SEL_TOKENS="$LOGDIR/selection-tokens.txt"
if ! tr -d '\r' < "$CBM_TEST_SELECTION_FILE" > "$SEL_TOKENS"; then
echo "FAIL: cannot read the test selection $CBM_TEST_SELECTION_FILE" >&2
exit 1
fi
if [ ! -s "$SEL_TOKENS" ] || grep -qvE '^[A-Za-z0-9_]+(:[A-Za-z0-9_]+)?$' "$SEL_TOKENS"; then
echo "FAIL: test selection empty or malformed: $CBM_TEST_SELECTION_FILE" >&2
exit 1
fi
cut -d: -f1 "$SEL_TOKENS" | sort -u > "$LOGDIR/selection-suites.txt"
if [ -n "${CBM_TEST_SELECTION_OPTIONAL:-}" ]; then
tr -d '\r' < "$CBM_TEST_SELECTION_OPTIONAL" | sort -u > "$LOGDIR/selection-optional.txt"
else
: > "$LOGDIR/selection-optional.txt"
fi
ABSENT=$(comm -23 "$LOGDIR/selection-suites.txt" <(sort -u "$SUITES_FILE"))
REQUIRED_ABSENT=$(comm -23 <(printf '%s\n' $ABSENT | sed '/^$/d' | sort -u) "$LOGDIR/selection-optional.txt")
if [ -n "$REQUIRED_ABSENT" ]; then
echo "FAIL: the test selection names suites this build does not register:" $REQUIRED_ABSENT >&2
exit 1
fi
[ -z "$ABSENT" ] || echo "selection: conditional suites not in this build, not run:" $ABSENT
# The runner's filter keeps only this build's suites (an unknown suite's
# token would fail every process); the suite list keeps list order.
awk 'NR == FNR { listed[$0] = 1; next } { split($0, p, ":") } p[1] in listed' \
"$SUITES_FILE" "$SEL_TOKENS" > "$LOGDIR/selection.txt"
awk 'NR == FNR { selected[$0] = 1; next } $0 in selected' \
"$LOGDIR/selection-suites.txt" "$SUITES_FILE" > "$SUITES_FILE.selected"
mv "$SUITES_FILE.selected" "$SUITES_FILE"
SELECTION="$(sort "$LOGDIR/selection.txt" | { sha256sum 2>/dev/null || shasum -a 256; } | awk '{print $1}')"
echo "selection: $(wc -l < "$SUITES_FILE" | tr -d ' ') of $NSUITES suites, $(wc -l < "$LOGDIR/selection.txt" | tr -d ' ') tokens ($SELECTION)"
NSUITES=$(wc -l < "$SUITES_FILE" | tr -d ' ')
export CBM_TEST_ONLY_FILE="$LOGDIR/selection.txt"
unset CBM_TEST_ONLY
fi
# CBM_TEST_SHARD="i/N" runs this invocation's deterministic slice of the
# suite list so CI can spread one platform's suites across N runner jobs.
# Unset (or "1/1") selects everything — the sharding path is inert unless a
@@ -246,6 +297,7 @@ NSHARD=$(wc -l < "$SHARD_EXPECT" | tr -d ' ')
echo "leg=${CBM_TEST_LEG:-local}"
echo "shard=${SHARD_INDEX}/${SHARD_TOTAL}"
echo "list_sha256=$(sort "$SUITES_FILE" | { sha256sum 2>/dev/null || shasum -a 256; } | awk '{print $1}')"
echo "selection_sha256=$SELECTION"
echo "--- slice ---"
cat "$SHARD_EXPECT"
} > "$LOGDIR/shard-manifest.txt"
@@ -344,7 +396,11 @@ for f in $(grep -v ' rc=0 ' "$RESULTS_FILE" | awk '{print $1}'); do
done
echo "────────────────────────────────────────────"
echo " $TOTAL_PASS passed, $TOTAL_FAIL failed, $TOTAL_SKIP skipped ($NSUITES suites, $JOBS jobs)"
if [ "$SELECTION" = none ]; then
echo " $TOTAL_PASS passed, $TOTAL_FAIL failed, $TOTAL_SKIP skipped ($NSUITES suites, $JOBS jobs)"
else
echo " $TOTAL_PASS passed, $TOTAL_FAIL failed, $TOTAL_SKIP skipped ($NSUITES selected suites, $JOBS jobs; selection $SELECTION)"
fi
echo "────────────────────────────────────────────"
if [ "$TOTAL_FAIL" -gt 0 ] || [ "$BAD_RC" -gt 0 ]; then
+351
View File
@@ -0,0 +1,351 @@
#!/usr/bin/env python3
"""coverage-map.py — build the per-test coverage map of the C test runner: for
every test, the set of functions it executed, children included. The test-impact
engine uses it to narrow the suites whose tests cross a process boundary, where
the call graph alone cannot say which test reaches which function.
Input: the instrumented runner (make -f Makefile.cbm test-runner-cov). Run with
CBM_TEST_COVERAGE_DIR it leaves, per suite, raw profiles named after the test
(tests/test_main.c, "Per-test coverage profiles"):
<test>.parent.profraw the runner inside the test
<test>.<pid>.profraw one per child process of the test
<test>.<pid>.forked an unaccounted forked child
_setup.<n>.profraw the runner between tests
_setup.child.<pid>.profraw children started between tests
_setup.child.<pid>.forked unaccounted children started between tests
Output directory:
functions.tsv <id> TAB <source file> TAB <name>
The file is where the function body stands, relative to the
repository root, read from the coverage mapping of the
runner itself (llvm-cov export). The profile only names the
translation unit: a function of a unity build or a static
inline of a header would otherwise carry the wrong file,
and the engine joins the map to the graph by file and name.
tests.tsv <suite>:<test> TAB <complete|incomplete> TAB <reason> TAB <ids>
ids ascending, space separated. <suite>:* is what ran BETWEEN
the tests of a suite (its setup); a change there concerns
every test of the suite.
meta.json commit, toolchain, and per suite: tests, incomplete tests,
exit code, wall seconds
A test is `incomplete` when its coverage cannot be trusted to be whole: a
forked child left its marker, a profile is unreadable, the parent profile is
missing, or the suite did not exit 0. An incomplete test must always be
selected; the map never claims less than it knows.
Raw profiles are about 7 MiB each, so every suite is reduced and deleted before
the next one runs.
Usage:
coverage-map.py --runner build/cov/test-runner --out DIR
[--suite NAME ...] [--llvm-bin DIR] [--root DIR]
[--keep-raw] [--timeout SECONDS]
Exit status: 0 map written; 1 a suite failed or timed out (map still written,
its tests incomplete); 2 usage or toolchain error.
"""
import argparse
import json
import os
import re
import shutil
import subprocess
import sys
import time
PROFILE = re.compile(r"^(?P<stem>.+?)\.(?P<who>parent|\d+)\.profraw$")
MARKER = re.compile(r"^(?P<stem>.+?)\.(?P<pid>\d+)\.forked$")
SETUP = re.compile(r"^_setup\.(?:\d+|child\.\d+)\.profraw$")
# `llvm-profdata show --all-functions` prints one block per function, opened by
# the name at two spaces of indent and a trailing colon.
FUNCTION_LINE = re.compile(r"^ (?P<name>\S.*):$")
SETUP_TEST = "*"
def fail(message):
print(f"coverage-map: {message}", file=sys.stderr)
sys.exit(2)
def executed_functions(profdata, profile):
"""Names of the functions `profile` counted at least once, or None when the
profile cannot be read."""
proc = subprocess.run([profdata, "show", "--all-functions", "--value-cutoff=1", profile],
capture_output=True, text=True)
if proc.returncode != 0:
return None
names = set()
for line in proc.stdout.splitlines():
match = FUNCTION_LINE.match(line)
if match:
names.add(match.group("name"))
return names
def source_table(llvm_cov, profdata_tool, runner, profile, root, work):
"""{profile name: (source file, function name)} for every function compiled
into the runner, from its coverage mapping. `profile` is any raw profile of
that runner: llvm-cov wants one, its counts are not used."""
indexed = os.path.join(work, "table.profdata")
merged = subprocess.run([profdata_tool, "merge", "-sparse", "-o", indexed, profile],
capture_output=True, text=True)
if merged.returncode != 0:
fail(f"llvm-profdata merge failed: {merged.stderr.strip()[-300:]}")
exported = subprocess.run([llvm_cov, "export", "-format=text", "-skip-expansions",
"-skip-branches", f"-instr-profile={indexed}", runner],
capture_output=True, text=True)
os.remove(indexed)
if exported.returncode != 0:
fail(f"llvm-cov export failed: {exported.stderr.strip()[-300:]}")
base = os.path.realpath(root) + os.sep
table = {}
for record in json.loads(exported.stdout)["data"][0]["functions"]:
files = record.get("filenames") or []
if not files:
continue
path = os.path.realpath(files[0])
if path.startswith(base):
path = path[len(base):]
table[record["name"]] = (path.replace(os.sep, "/"), record["name"].rpartition(":")[2])
return table
class Functions:
"""Stable ids for (source file, function name), in order of first
appearance. Several profile names can be one function: a static inline of a
header is recorded once per file that includes it."""
def __init__(self, table):
self.table = table
self.ids = {}
def key(self, name):
if name in self.table:
return self.table[name]
# Not in the coverage mapping: keep what the profile says, the
# translation unit for a function with internal linkage.
source, _, short = name.rpartition(":")
return (source, short)
def ids_of(self, names):
out = set()
for name in names:
key = self.key(name)
if key not in self.ids:
self.ids[key] = len(self.ids)
out.add(self.ids[key])
return sorted(out)
def rows(self):
for (source, short), ident in sorted(self.ids.items(), key=lambda item: item[1]):
yield ident, source, short
def reduce_suite(suite, suite_dir, profdata, suite_ok):
"""Turn one suite's raw profiles into {test: (status, reason, names)}."""
tests = {}
markers = {}
setup = set()
setup_unreadable = False
setup_markers = []
entries = sorted(os.listdir(suite_dir)) if os.path.isdir(suite_dir) else []
for entry in entries:
path = os.path.join(suite_dir, entry)
marker = MARKER.match(entry)
if marker:
if marker.group("stem") == "_setup.child":
setup_markers.append(marker.group("pid"))
else:
markers.setdefault(marker.group("stem"), []).append(marker.group("pid"))
continue
if SETUP.match(entry):
names = executed_functions(profdata, path)
if names is None:
setup_unreadable = True
else:
setup |= names
continue
profile = PROFILE.match(entry)
if not profile:
continue
test = tests.setdefault(profile.group("stem"),
{"names": set(), "parent": False, "unreadable": []})
names = executed_functions(profdata, path)
if names is None:
test["unreadable"].append(entry)
continue
test["names"] |= names
test["parent"] = test["parent"] or profile.group("who") == "parent"
result = {}
for stem, pids in markers.items():
tests.setdefault(stem, {"names": set(), "parent": False, "unreadable": []})
for stem, test in sorted(tests.items()):
reasons = []
if stem in markers:
reasons.append("unaccounted forked child: " + ",".join(sorted(markers[stem])))
if test["unreadable"]:
reasons.append("unreadable profile: " + ",".join(sorted(test["unreadable"])))
if not test["parent"]:
reasons.append("no parent profile")
if not suite_ok:
reasons.append("suite did not exit 0")
result[stem] = ("incomplete" if reasons else "complete", "; ".join(reasons), test["names"])
setup_reasons = []
if setup_markers:
setup_reasons.append("unaccounted forked child: " + ",".join(sorted(setup_markers)))
if setup_unreadable:
setup_reasons.append("unreadable setup profile")
if not suite_ok:
setup_reasons.append("suite did not exit 0")
result[SETUP_TEST] = ("incomplete" if setup_reasons else "complete",
"; ".join(setup_reasons), setup)
return result
def tool_version(tool):
proc = subprocess.run([tool, "--version"], capture_output=True, text=True)
lines = [line.strip() for line in proc.stdout.splitlines() if line.strip()]
return lines[0] if lines else "unknown"
def git_commit(root):
proc = subprocess.run(["git", "-C", root, "rev-parse", "HEAD"], capture_output=True, text=True)
return proc.stdout.strip() if proc.returncode == 0 else "unknown"
def add_v2_arguments(parser):
parser.add_argument("--format", type=int, choices=(1, 2), default=1,
help="wire format (2 is experimental and unadmitted)")
parser.add_argument("--registry", help="format2 image-bound expected registry JSON")
for flag, default, help_text in (
("max-output-bytes", 32 * 1024 * 1024, "combined stdout/stderr bytes per command"),
("max-total-output-bytes", 512 * 1024 * 1024, "aggregate command stdout/stderr bytes"),
("max-profile-bytes", 64 * 1024 * 1024, "bytes per raw input profile"),
("max-total-profile-bytes", 512 * 1024 * 1024, "aggregate raw and separately converted bytes"),
("max-profile-files", 4096, "raw entries including directories, markers and listing profile"),
("conversion-timeout", 60, "seconds per tool command"),
("reduction-timeout", 600, "seconds per strict parse or final pure reduction"),
("max-map-bytes", 64 * 1024 * 1024, "combined producer wire bytes"),
):
parser.add_argument("--" + flag, type=int, default=default,
help="format2: %s (default: %s)" % (help_text, default))
parser.epilog = ((parser.epilog or "") +
" Format2 always retains bounded raw and diagnostic evidence; "
"--keep-raw only changes format1. Format2 supports the observed "
"LLVM 22.1.8 frontend text dialect and POSIX process groups.")
def main():
parser = argparse.ArgumentParser(description="Build the per-test coverage map.")
parser.add_argument("--runner", required=True, help="instrumented test runner")
parser.add_argument("--out", required=True, help="output directory")
parser.add_argument("--suite", action="append", default=[], help="suite to run (repeatable)")
parser.add_argument("--llvm-bin", default=os.environ.get("COV_LLVM_BIN", ""),
help="directory holding llvm-profdata and llvm-cov of the compiler that "
"built the runner")
parser.add_argument("--root", default="",
help="repository root the source files are named relative to "
"(default: the repository this script is in)")
parser.add_argument("--keep-raw", action="store_true", help="keep the raw profiles")
parser.add_argument("--timeout", type=int, default=3600, help="seconds per suite")
add_v2_arguments(parser)
args = parser.parse_args()
if args.format == 2:
from coverage_live_v2 import collect
return collect(args)
runner = os.path.abspath(args.runner)
if not os.access(runner, os.X_OK):
fail(f"runner is not executable: {runner}")
profdata = os.path.join(args.llvm_bin, "llvm-profdata") if args.llvm_bin else "llvm-profdata"
llvm_cov = os.path.join(args.llvm_bin, "llvm-cov") if args.llvm_bin else "llvm-cov"
for tool in (profdata, llvm_cov):
if not shutil.which(tool):
fail(f"not found: {tool}")
root = args.root or os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
out = os.path.abspath(args.out)
raw = os.path.join(out, "raw")
os.makedirs(raw, exist_ok=True)
# Listing the suites is also the cheapest run of the runner: the profile it
# leaves is the one llvm-cov needs to print the coverage mapping.
listing_profile = os.path.join(out, "listing.profraw")
listed = subprocess.run([runner, "--list-suites"], capture_output=True, text=True,
env=dict(os.environ, LLVM_PROFILE_FILE=listing_profile))
if listed.returncode != 0:
fail("the runner could not list its suites")
if not os.path.exists(listing_profile):
fail("the runner wrote no profile: is it the instrumented build (make test-runner-cov)?")
table = source_table(llvm_cov, profdata, runner, listing_profile, root, out)
os.remove(listing_profile)
suites = args.suite or [line.strip() for line in listed.stdout.splitlines() if line.strip()]
if not suites:
fail("no suites to run")
functions = Functions(table)
test_rows = []
suite_meta = []
failed = False
env = dict(os.environ)
# A plain name: a runner built for continuous profiles would otherwise start
# in that mode and ignore every per-test name (tests/test_main.c).
env["LLVM_PROFILE_FILE"] = os.devnull
env["CBM_TEST_COVERAGE_DIR"] = raw
for suite in suites:
suite_dir = os.path.join(raw, suite)
shutil.rmtree(suite_dir, ignore_errors=True)
started = time.monotonic()
try:
run = subprocess.run([runner, suite], env=env, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL, timeout=args.timeout)
code = run.returncode
except subprocess.TimeoutExpired:
code = -1
wall = time.monotonic() - started
suite_ok = code == 0
failed = failed or not suite_ok
reduced = reduce_suite(suite, suite_dir, profdata, suite_ok)
incomplete = 0
for test, (status, reason, names) in sorted(reduced.items()):
if test != SETUP_TEST and status != "complete":
incomplete += 1
ids = " ".join(str(i) for i in functions.ids_of(names))
test_rows.append(f"{suite}:{test}\t{status}\t{reason}\t{ids}")
suite_meta.append({"suite": suite, "tests": len(reduced) - 1, "incomplete": incomplete,
"exit": code, "wall_s": round(wall, 2)})
print(f"{suite}: {len(reduced) - 1} tests, {incomplete} incomplete, exit {code}, "
f"{wall:.1f} s", flush=True)
if not args.keep_raw:
shutil.rmtree(suite_dir, ignore_errors=True)
if not args.keep_raw:
shutil.rmtree(raw, ignore_errors=True)
with open(os.path.join(out, "functions.tsv"), "w", encoding="utf-8", newline="\n") as handle:
for ident, source, short in functions.rows():
handle.write(f"{ident}\t{source}\t{short}\n")
with open(os.path.join(out, "tests.tsv"), "w", encoding="utf-8", newline="\n") as handle:
handle.write("\n".join(test_rows) + "\n")
meta = {
"format": 1,
"commit": git_commit(root),
"llvm_profdata": tool_version(profdata),
"platform": sys.platform,
"functions": len(functions.ids),
"functions_compiled": len(set(table.values())),
"tests": sum(entry["tests"] for entry in suite_meta),
"incomplete": sum(entry["incomplete"] for entry in suite_meta),
"suites": suite_meta,
}
with open(os.path.join(out, "meta.json"), "w", encoding="utf-8", newline="\n") as handle:
json.dump(meta, handle, indent=1)
handle.write("\n")
print(f"map: {meta['tests']} tests, {meta['incomplete']} incomplete, "
f"{meta['functions']} functions -> {out}")
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main())
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env python3
"""coverage-merge.py — the incremental coverage map of a main-branch push.
Merges FRESH (coverage-map.py output for the suites the push re-ran, made with
the new commit's instrumented runner, so its functions.tsv is the new image's
complete function table) into PREVIOUS (the previous published map):
* a suite FRESH ran keeps FRESH's rows exactly;
* every other suite the new runner still lists is carried from PREVIOUS,
each row's function ids remapped by (source file, name) onto FRESH's
table; a function the new image no longer has drops out of the row, and a
row's status (an incomplete row stays incomplete) is never improved;
* a PREVIOUS suite the new runner no longer lists is dropped.
The merged meta.json names --commit and recounts every suite, so the C reader
(cbm_coverage_map_metadata_matches) checks it like a full map. When the map was
observed is not decided here: the producer carries the oldest observation of
the previous bundle forward, so an incremental map ages out unless a full
refresh runs (the receipt's 7-day bound).
Usage: coverage-merge.py --previous DIR --fresh DIR --suites FILE --commit SHA --out DIR
Exit: 0 merged · 1 inconsistent inputs · 2 usage.
"""
from __future__ import annotations
import argparse
import json
import os
import sys
SETUP = "*"
def fail(message: str) -> None:
print(f"coverage-merge: {message}", file=sys.stderr)
sys.exit(1)
def read_functions(path: str) -> tuple[dict[int, tuple[str, str]], list[str]]:
by_id: dict[int, tuple[str, str]] = {}
lines = []
with open(path, encoding="utf-8") as handle:
for line in handle:
line = line.rstrip("\n")
if not line:
continue
fields = line.split("\t")
if len(fields) != 3 or not fields[0].isdigit():
fail(f"malformed function row in {path}: {line!r}")
by_id[int(fields[0])] = (fields[1], fields[2])
lines.append(line)
return by_id, lines
def read_tests(path: str) -> list[tuple[str, str, str, str, list[int]]]:
rows = []
with open(path, encoding="utf-8") as handle:
for line in handle:
line = line.rstrip("\n")
if not line:
continue
fields = line.split("\t")
if len(fields) != 4 or ":" not in fields[0]:
fail(f"malformed test row in {path}: {line!r}")
suite, test = fields[0].split(":", 1)
ids = [int(token) for token in fields[3].split()] if fields[3] else []
rows.append((suite, test, fields[1], fields[2], ids))
return rows
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
for flag in ("previous", "fresh", "suites", "commit", "out"):
parser.add_argument("--" + flag, required=True)
args = parser.parse_args()
_, fresh_lines = read_functions(os.path.join(args.fresh, "functions.tsv"))
fresh_by_id, _ = read_functions(os.path.join(args.fresh, "functions.tsv"))
previous_by_id, _ = read_functions(os.path.join(args.previous, "functions.tsv"))
fresh_ids = {key: ident for ident, key in fresh_by_id.items()}
with open(os.path.join(args.fresh, "meta.json"), encoding="utf-8") as handle:
fresh_meta = json.load(handle)
with open(os.path.join(args.previous, "meta.json"), encoding="utf-8") as handle:
previous_meta = json.load(handle)
with open(args.suites, encoding="utf-8") as handle:
listed = [line.strip() for line in handle if line.strip()]
fresh_rows = read_tests(os.path.join(args.fresh, "tests.tsv"))
previous_rows = read_tests(os.path.join(args.previous, "tests.tsv"))
rerun = {row[0] for row in fresh_rows}
if not rerun <= set(listed):
fail("a re-run suite is not listed by the new runner")
merged = [row for row in fresh_rows]
for suite, test, status, reason, ids in previous_rows:
if suite in rerun or suite not in listed:
continue
mapped = sorted({fresh_ids[previous_by_id[i]] for i in ids
if i in previous_by_id and previous_by_id[i] in fresh_ids})
merged.append((suite, test, status, reason, mapped))
merged.sort(key=lambda row: (row[0], row[1]))
suite_meta = {entry["suite"]: entry for entry in previous_meta.get("suites", [])}
for entry in fresh_meta.get("suites", []):
suite_meta[entry["suite"]] = entry
counts: dict[str, list[int]] = {}
for suite, test, status, _, _ in merged:
tally = counts.setdefault(suite, [0, 0])
if test != SETUP:
tally[0] += 1
tally[1] += status != "complete"
suites = []
for suite in sorted(counts):
entry = dict(suite_meta.get(suite, {"suite": suite, "exit": 0}))
entry["tests"], entry["incomplete"] = counts[suite]
suites.append(entry)
os.makedirs(args.out, exist_ok=True)
with open(os.path.join(args.out, "functions.tsv"), "w", encoding="utf-8", newline="\n") as handle:
handle.write("".join(line + "\n" for line in fresh_lines))
with open(os.path.join(args.out, "tests.tsv"), "w", encoding="utf-8", newline="\n") as handle:
for suite, test, status, reason, ids in merged:
handle.write(f"{suite}:{test}\t{status}\t{reason}\t{' '.join(map(str, ids))}\n")
meta = dict(fresh_meta)
meta.update({"commit": args.commit, "tests": sum(c[0] for c in counts.values()),
"incomplete": sum(c[1] for c in counts.values()), "suites": suites})
with open(os.path.join(args.out, "meta.json"), "w", encoding="utf-8", newline="\n") as handle:
json.dump(meta, handle, indent=1)
handle.write("\n")
print(f"merged: {len(rerun)} suite(s) re-run, "
f"{len(counts) - len(rerun & set(counts))} carried, {len(merged)} rows")
return 0
if __name__ == "__main__":
sys.exit(main())
+649
View File
@@ -0,0 +1,649 @@
"""Experimental, bounded POSIX native transport for unadmitted profile-map v2.
Only actual observations are transported. Registry/image hashes are custody
facts, not evidence that a registry, interval, runtime or image set is complete.
No RowEvidence is created here, so this bundle cannot authorize negative
coverage evidence or test narrowing. Raw evidence is retained, including on
failure. Process groups cannot prove closure of children that leave the group.
"""
import errno
import hashlib
import json
import os
import re
import selectors
import signal
import stat
import subprocess
import sys
import time
from profile_map_v2 import (
ExpectedSuite, ObservationRole, ProfileMapError, ProfileMapLimits,
ProfileMapLimitExceededError, ProfileMapCancelledError,
ProfileObservation, SuiteOutcome, build_profile_map_v2,
)
from profile_text_parser import (
ParseLimits, ProfileTextError, LimitExceededError, ParseCancelledError, parse_profile_text,
)
REGISTRY_CAP = 2 * 1024 * 1024
ROW_CAP = 65536
RECORD_CAP = 4000000
COUNTER_CAP = 32000000
NAME_CAP = 128 * 1024 * 1024
GRACE = 2.0
PROFILE = re.compile(r"^(.+?)\.(parent|[0-9]+)\.profraw$")
SETUP = re.compile(r"^_setup\.(?:[0-9]+|child\.[0-9]+)\.profraw$")
MARKER = re.compile(r"^(.+?)\.([0-9]+)\.forked$")
class CollectionError(ValueError):
def __init__(self, kind, message):
super().__init__(message)
self.kind = kind
def _error(kind, message):
raise CollectionError(kind, message)
def _directory(path):
"""Open each absolute component without following symlinks; caller closes."""
path = os.path.abspath(path)
fd = os.open(os.sep, os.O_RDONLY | os.O_DIRECTORY)
try:
for part in path.split(os.sep):
if not part:
continue
next_fd = os.open(part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW,
dir_fd=fd)
os.close(fd)
fd = next_fd
return fd
except BaseException:
os.close(fd)
raise
def _regular(path):
parent, name = os.path.split(os.path.abspath(path))
directory = _directory(parent)
try:
fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK,
dir_fd=directory)
finally:
os.close(directory)
if not stat.S_ISREG(os.fstat(fd).st_mode):
os.close(fd)
_error("input", "input is not a regular file")
return fd
def _signature(info):
return (info.st_dev, info.st_ino, info.st_size, info.st_mtime_ns, info.st_ctime_ns)
def _read(path, limit):
fd = _regular(path)
try:
before = os.fstat(fd)
if before.st_size > limit:
_error("resource_limit", "file byte limit exceeded")
data = bytearray()
while True:
part = os.read(fd, min(65536, limit + 1 - len(data)))
if not part:
break
data.extend(part)
if len(data) > limit:
_error("resource_limit", "file byte limit exceeded")
if _signature(before) != _signature(os.fstat(fd)):
_error("identity", "input changed during capture")
return bytes(data)
finally:
os.close(fd)
def _identity(path):
fd = _regular(path)
try:
before = os.fstat(fd)
if before.st_size > 1024 * 1024 * 1024:
_error("resource_limit", "executable exceeds 1GiB identity limit")
digest = hashlib.sha256()
size = 0
while True:
part = os.read(fd, 65536)
if not part:
break
size += len(part)
if size > 1024 * 1024 * 1024:
_error("resource_limit", "executable grew beyond identity limit")
digest.update(part)
if _signature(before) != _signature(os.fstat(fd)) or size != before.st_size:
_error("identity", "executable changed while hashing")
if not os.access(path, os.X_OK):
_error("input", "executable is not executable")
return {"path": path, "sha256": digest.hexdigest(), "bytes": size}
finally:
os.close(fd)
def _pairs(values):
result = {}
for key, value in values:
if key in result:
_error("input", "duplicate registry key")
result[key] = value
return result
def _identifier(value, suite=False):
if type(value) is not str or not value:
_error("input", "registry identifier must be a nonempty string")
try:
data = value.encode("utf-8", "strict")
except UnicodeError:
_error("input", "registry identifier is not valid UTF-8")
if any(char in data for char in (0, 9, 10, 13)) or (suite and b":" in data):
_error("input", "registry identifier violates producer grammar")
if not suite and value == "*":
_error("input", "setup test name is reserved")
# Native routing uses each identifier as one filesystem component. The
# pure producer accepts a wider byte grammar; those routes are unsupported.
if value in (".", "..") or "/" in value or "\\" in value:
_error("unsupported", "registry identifier cannot be a native path component")
if not suite and value in ("_setup", "_setup.child"):
_error("unsupported", "registry test collides with native setup routing")
return data
def _registry(path, image):
raw = _read(path, REGISTRY_CAP)
try:
value = json.loads(raw.decode("utf-8", "strict"), object_pairs_hook=_pairs,
parse_constant=lambda _: _error("input", "nonfinite JSON number"))
except (UnicodeError, json.JSONDecodeError, RecursionError):
_error("input", "invalid registry JSON")
if type(value) is not dict or set(value) != {"format", "image_sha256", "suites"}:
_error("input", "registry keys do not match v0 schema")
if value["format"] != "cbm.coverage.registry.v0":
_error("input", "unsupported registry format")
declared = value["image_sha256"]
if type(declared) is not str or not re.fullmatch(r"[0-9a-f]{64}", declared):
_error("input", "registry image digest must be lowercase SHA256")
if declared != image["sha256"]:
_error("identity", "registry does not match runner image")
suites = value["suites"]
if type(suites) is not list or not suites:
_error("input", "registry suites must be nonempty")
result = {}
rows = 0
for item in suites:
if type(item) is not dict or set(item) != {"name", "tests"}:
_error("input", "registry suite keys do not match schema")
name = item["name"]
_identifier(name, suite=True)
if name in result:
_error("input", "duplicate registry suite")
tests = item["tests"]
if type(tests) is not list or not tests:
_error("input", "registry tests must be nonempty")
seen = set()
for test in tests:
_identifier(test)
if test in seen:
_error("input", "duplicate registry test")
seen.add(test)
rows += 1 + len(tests)
if rows > ROW_CAP:
_error("resource_limit", "registry row limit exceeded")
result[name] = tuple(tests)
return result, hashlib.sha256(raw).hexdigest(), raw
class Evidence:
def __init__(self, path, args):
self.path = os.path.abspath(path)
self.args = args
self.records = []
self.uncertainty = []
self.converted_bytes = 0
self.captured_bytes = 0
self.allowed_suites = set()
self.directory = None
parent, name = os.path.split(self.path)
if not name:
_error("input", "output must name a fresh directory")
fd = _directory(parent)
try:
os.mkdir(name, 0o700, dir_fd=fd)
self.directory = os.open(name, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW,
dir_fd=fd)
finally:
os.close(fd)
try:
self.directory_identity = os.fstat(self.directory)
for name in ("evidence", "raw"):
os.mkdir(name, 0o700, dir_fd=self.directory)
except BaseException:
self.close()
raise
def close(self):
if self.directory is not None:
os.close(self.directory)
self.directory = None
def check(self):
fd = _directory(self.path)
try:
actual = os.fstat(fd)
if (actual.st_dev, actual.st_ino) != (self.directory_identity.st_dev,
self.directory_identity.st_ino):
_error("identity", "owned output directory was replaced")
finally:
os.close(fd)
def write(self, relative, data):
self.check()
parent, name = os.path.split(relative)
fd = _directory(os.path.join(self.path, parent))
try:
target = os.open(name, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
0o600, dir_fd=fd)
try:
with os.fdopen(target, "wb") as stream:
stream.write(data)
stream.flush()
os.fsync(stream.fileno())
except BaseException:
# Partial evidence remains; no marker is created on this path.
raise
finally:
os.close(fd)
return os.path.join(self.path, relative)
def json(self, relative, value):
data = (json.dumps(value, sort_keys=True, indent=2,
ensure_ascii=True) + "\n").encode("ascii")
if len(data) > 64 * 1024 * 1024:
_error("resource_limit", "metadata exceeds 64MiB bound")
return self.write(relative, data)
def publish(self, meta):
# A partial JSON file is never a commit marker. The pending complete
# file remains evidence, and hard-link creation cannot overwrite a
# preexisting or symlink destination.
self.json("meta.pending.json", meta)
for name in ("profiles", "tests"):
data = _read(os.path.join(self.path, name + ".tsv"), self.args.max_map_bytes)
if hashlib.sha256(data).hexdigest() != meta[name + "_sha256"]:
_error("identity", "wire changed before publication")
self.check()
os.fsync(self.directory)
os.link("meta.pending.json", "meta.json", src_dir_fd=self.directory,
dst_dir_fd=self.directory, follow_symlinks=False)
def inventory(self):
"""Bound scans while children run; never follow raw tree symlinks."""
self.check()
root = os.path.join(self.path, "raw")
pending = [root]
files, total, count = [], 0, 1 # listing counts as one profile slot
try:
info = os.stat("listing.profraw", dir_fd=self.directory, follow_symlinks=False)
except FileNotFoundError:
info = None
if info is not None:
if not stat.S_ISREG(info.st_mode):
_error("input", "listing profile is not a regular file")
if info.st_size > self.args.max_profile_bytes:
_error("resource_limit", "listing profile byte limit exceeded")
total = info.st_size
if total > self.args.max_total_profile_bytes:
_error("resource_limit", "aggregate raw byte limit exceeded")
while pending:
directory = pending.pop()
fd = _directory(directory)
try:
with os.scandir(fd) as entries:
for entry in entries:
count += 1
if count > self.args.max_profile_files:
_error("resource_limit", "raw entry count limit exceeded")
info = entry.stat(follow_symlinks=False)
path = os.path.join(directory, entry.name)
if stat.S_ISDIR(info.st_mode) and directory == root:
if entry.name not in self.allowed_suites:
self.uncertainty.append({"path": path, "reason": "unknown_suite_destination"})
_error("input", "raw suite directory is outside requested scope")
pending.append(path)
elif stat.S_ISREG(info.st_mode):
if info.st_size > self.args.max_profile_bytes:
_error("resource_limit", "raw file byte limit exceeded")
total += info.st_size
if total > self.args.max_total_profile_bytes:
_error("resource_limit", "aggregate raw byte limit exceeded")
files.append(path)
else:
self.uncertainty.append({"path": path, "reason": "unsupported_raw_entry"})
_error("input", "unsupported raw tree entry")
finally:
os.close(fd)
return sorted(files)
def _group_exists(pid):
try:
os.killpg(pid, 0)
return True
except ProcessLookupError:
return False
def _quiesce(proc):
sent = []
for sig in (signal.SIGTERM, signal.SIGKILL):
if proc.poll() is not None and not _group_exists(proc.pid):
return True, sent
try:
os.killpg(proc.pid, sig)
sent.append(signal.Signals(sig).name)
except ProcessLookupError:
pass
until = time.monotonic() + GRACE
while time.monotonic() < until:
if proc.poll() is not None and not _group_exists(proc.pid):
return True, sent
time.sleep(0.02)
return proc.poll() is not None and not _group_exists(proc.pid), sent
def _run(evidence, label, argv, environment, timeout, cwd, watch_raw=False):
selector = selectors.DefaultSelector()
streams = {"stdout": bytearray(), "stderr": bytearray()}
started = time.monotonic()
proc = None
failure = None
quiescent, cleanup = False, []
try:
proc = subprocess.Popen(argv, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, env=environment, cwd=cwd,
start_new_session=True, bufsize=0)
for name, pipe in (("stdout", proc.stdout), ("stderr", proc.stderr)):
os.set_blocking(pipe.fileno(), False)
selector.register(pipe, selectors.EVENT_READ, name)
while True:
if watch_raw:
evidence.inventory()
left = timeout - (time.monotonic() - started)
if left <= 0:
failure = "timeout"
break
for key, _ in selector.select(min(left, 0.05)):
part = os.read(key.fd, 65536)
if not part:
selector.unregister(key.fileobj)
continue
used = sum(len(data) for data in streams.values())
allowed = min(evidence.args.max_output_bytes - used,
evidence.args.max_total_output_bytes - evidence.captured_bytes - used)
streams[key.data].extend(part[:max(0, allowed)])
if len(part) > allowed:
failure = "resource_limit"
break
if failure:
break
if proc.poll() is not None:
if _group_exists(proc.pid):
failure = "quiescence"
break
if not selector.get_map():
break
except CollectionError as error:
failure = error.kind
except (OSError, ValueError):
failure = "process"
finally:
if proc is not None:
quiescent, cleanup = _quiesce(proc)
if not quiescent:
failure = "quiescence"
if cleanup and failure is None:
failure = "quiescence"
proc.stdout.close()
proc.stderr.close()
selector.close()
record = {"label": label, "argv": argv, "returncode": proc.returncode if proc else None,
"failure": failure, "quiescent": quiescent, "cleanup": cleanup,
"wall_s": round(time.monotonic() - started, 6), "timeout_s": timeout}
evidence.captured_bytes += sum(len(data) for data in streams.values())
for name, data in streams.items():
path = evidence.write("evidence/" + label + "." + name, bytes(data))
record[name] = {"path": path, "bytes": len(data),
"sha256": hashlib.sha256(data).hexdigest(),
"capture": "complete" if failure is None else "bounded_prefix"}
evidence.json("evidence/" + label + ".process.json", record)
evidence.records.append(record)
return record, bytes(streams["stdout"])
def _required(record):
if record["failure"] or record["returncode"] != 0 or not record["quiescent"]:
_error(record["failure"] or "tool", "required native command failed: " + record["label"])
def _environment():
result = dict(os.environ)
for name in ("CBM_TEST_COVERAGE_DIR", "CBM_TEST_ONLY", "CBM_TEST_ONLY_FILE"):
result.pop(name, None)
return result
def _convert(evidence, tool, path, label, environment, root, unavailable=False):
try:
raw = _read(path, evidence.args.max_profile_bytes)
except OSError as error:
if unavailable and error.errno in (errno.EACCES, errno.EPERM, errno.ENOENT):
evidence.uncertainty.append({"path": path, "reason": "profile_unreadable"})
return None
raise
snapshot = evidence.write("evidence/" + label + ".profraw", raw)
raw_digest = hashlib.sha256(raw).hexdigest()
record, text = _run(evidence, label, [tool, "merge", "--instr", "--text", "--failure-mode=any",
"--num-threads=1", "--output=-", snapshot],
environment, evidence.args.conversion_timeout, root)
record["input"] = {"path": path, "snapshot": snapshot, "sha256": raw_digest,
"bytes": len(raw)}
_required(record)
if hashlib.sha256(_read(snapshot, evidence.args.max_profile_bytes)).hexdigest() != raw_digest:
_error("identity", "conversion input changed")
evidence.converted_bytes += len(text)
if evidence.converted_bytes > evidence.args.max_total_profile_bytes:
_error("resource_limit", "aggregate converted byte limit exceeded")
# Reject malformed transport, even though the pure producer can represent
# unavailable/malformed observations conservatively. Unknown identities and
# counter-shape incompatibilities are rejected by the producer's full join.
deadline = time.monotonic() + evidence.args.reduction_timeout
parse_profile_text(text, ParseLimits(evidence.args.max_output_bytes, RECORD_CAP,
COUNTER_CAP, NAME_CAP),
cancel=lambda _: time.monotonic() >= deadline)
return text
def _scope(registry, requested, available):
selected = requested or list(registry)
if len(set(selected)) != len(selected):
_error("input", "duplicate requested suite")
for name in selected:
if name not in registry or name not in available:
_error("input", "requested suite is absent from registry or native list")
return selected
def _observations(evidence, tool, registry, selected, environment, root):
observations = []
for index, path in enumerate(evidence.inventory()):
relative = os.path.relpath(path, os.path.join(evidence.path, "raw"))
parts = relative.split(os.sep)
if len(parts) != 2 or parts[0] not in selected:
evidence.uncertainty.append({"path": path, "reason": "unknown_profile_destination"})
_error("input", "raw profile destination is outside requested scope")
suite, name = parts
marker = MARKER.fullmatch(name)
setup = SETUP.fullmatch(name)
profile = PROFILE.fullmatch(name)
if setup:
test = "*"
role = ObservationRole.CHILD if name.startswith("_setup.child.") else ObservationRole.SETUP
elif marker:
test = "*" if marker.group(1) == "_setup.child" else marker.group(1)
role = ObservationRole.CHILD
elif profile:
test = profile.group(1)
role = ObservationRole.PARENT if profile.group(2) == "parent" else ObservationRole.CHILD
else:
evidence.uncertainty.append({"path": path, "reason": "unaccounted_raw_entry"})
_error("input", "unaccounted raw profile entry")
if test != "*" and test not in registry[suite]:
evidence.uncertainty.append({"path": path, "reason": "unknown_test_identity"})
_error("input", "raw test identifier is outside expected registry")
if marker:
evidence.uncertainty.append({"path": path, "reason": "unaccounted_fork"})
text = None
else:
text = _convert(evidence, tool, path, "profile-%06d" % index,
environment, root, unavailable=True)
observations.append(ProfileObservation(suite.encode("utf-8"), test.encode("utf-8"),
name.encode("utf-8"), role, text))
return tuple(observations)
def collect(args):
evidence = None
try:
if os.name != "posix" or not all(hasattr(os, name) for name in
("O_NOFOLLOW", "O_DIRECTORY", "killpg", "set_blocking")):
_error("unsupported", "format2 requires POSIX owned process-group custody")
for name in ("timeout", "max_output_bytes", "max_total_output_bytes", "max_profile_bytes", "max_total_profile_bytes",
"max_profile_files", "conversion_timeout", "reduction_timeout", "max_map_bytes"):
value = getattr(args, name)
if type(value) is not int or value <= 0 or value > sys.maxsize // 2:
_error("input", "resource limits must be positive bounded integers")
if not args.registry or not args.llvm_bin:
_error("input", "format2 requires --registry and explicit --llvm-bin")
runner = os.path.realpath(os.path.abspath(args.runner))
tool = os.path.realpath(os.path.join(os.path.abspath(args.llvm_bin), "llvm-profdata"))
root = os.path.abspath(args.root or os.path.join(os.path.dirname(__file__), "..", ".."))
root_fd = _directory(root)
os.close(root_fd)
image = _identity(runner)
tool_identity = _identity(tool)
registry_path = os.path.abspath(args.registry)
registry, registry_digest, registry_bytes = _registry(registry_path, image)
# Reject explicit scope errors before any runner/tool action.
_scope(registry, args.suite, set(registry))
evidence = Evidence(args.out, args)
registry_snapshot = evidence.write("evidence/registry.json", registry_bytes)
environment = _environment()
environment["LLVM_PROFILE_FILE"] = os.devnull
version_record, version = _run(evidence, "tool-version", [tool, "--version"], environment,
args.conversion_timeout, root)
_required(version_record)
if not re.search(rb"LLVM version 22\.1\.8(?:\s|$)", version):
_error("unsupported", "only observed LLVM 22.1.8 proftext transport is supported")
listing_path = os.path.join(evidence.path, "listing.profraw")
listing_env = dict(environment, LLVM_PROFILE_FILE=listing_path)
listed, listing = _run(evidence, "list-suites", [runner, "--list-suites"], listing_env,
args.timeout, root, watch_raw=True)
_required(listed)
try:
available = listing.decode("utf-8", "strict").splitlines()
except UnicodeError:
_error("input", "native suite list is not UTF-8")
if not available or any(not name for name in available) or len(set(available)) != len(available):
_error("input", "native suite list is empty or ambiguous")
for name in available:
_identifier(name, suite=True)
selected = _scope(registry, args.suite, set(available))
evidence.allowed_suites = set(selected)
enumeration = _convert(evidence, tool, listing_path, "enumeration", environment, root)
evidence.inventory()
suite_env = dict(environment, CBM_TEST_COVERAGE_DIR=os.path.join(evidence.path, "raw"))
outcomes = []
expected = []
failed = False
for index, suite in enumerate(selected):
record, _ = _run(evidence, "suite-%06d" % index, [runner, suite], suite_env,
args.timeout, root, watch_raw=True)
if record["failure"] not in (None, "timeout") or not record["quiescent"]:
_error(record["failure"] or "quiescence", "native suite capture failed")
outcome = (SuiteOutcome.TIMED_OUT if record["failure"] == "timeout" else
SuiteOutcome.PASSED if record["returncode"] == 0 else SuiteOutcome.FAILED)
failed = failed or outcome is not SuiteOutcome.PASSED
expected.append(ExpectedSuite(suite.encode("utf-8"),
tuple(test.encode("utf-8") for test in registry[suite]), outcome))
outcomes.append({"suite": suite, "tests": list(registry[suite]),
"outcome": outcome.value, "exit": record["returncode"], "process": record})
observations = _observations(evidence, tool, registry, selected, environment, root)
if _identity(runner) != image or _identity(tool) != tool_identity:
_error("identity", "runner or conversion tool changed during collection")
# No affirmative runtime/interval/image/child assertions are supplied.
limits = ProfileMapLimits(args.max_total_profile_bytes + REGISTRY_CAP * 2, ROW_CAP,
args.max_profile_files, RECORD_CAP, RECORD_CAP, COUNTER_CAP,
NAME_CAP, COUNTER_CAP, args.max_map_bytes)
deadline = time.monotonic() + args.reduction_timeout
produced = build_profile_map_v2(bytes.fromhex(image["sha256"]), enumeration,
tuple(expected), observations, (), limits,
cancel=lambda _: time.monotonic() >= deadline)
profiles_path = evidence.write("profiles.tsv", produced.profiles_tsv)
tests_path = evidence.write("tests.tsv", produced.tests_tsv)
meta = {"format": 2, "admission": "unverified", "image_sha256": image["sha256"],
"registry_sha256": registry_digest, "profiles_sha256": produced.profiles_sha256.hex(),
"tests_sha256": produced.tests_sha256.hex(), "registry": {
"path": registry_path, "snapshot": registry_snapshot, "sha256": registry_digest,
"format": "cbm.coverage.registry.v0", "completeness": "unverified"},
"scope": selected,
"image": image, "llvm_profdata": dict(tool_identity, version=version.decode("utf-8", "replace")),
"profiles": {"path": profiles_path, "sha256": produced.profiles_sha256.hex()},
"tests": {"path": tests_path, "sha256": produced.tests_sha256.hex()},
"suites": outcomes, "processes": evidence.records, "uncertainty": evidence.uncertainty,
"evidence_root": os.path.join(evidence.path, "evidence"),
"raw_root": os.path.join(evidence.path, "raw"),
"row_evidence": "absent", "platform": sys.platform,
"raw_retained": True, "captured_bytes": evidence.captured_bytes,
"converted_bytes": evidence.converted_bytes,
"custody": {"method": "POSIX session and process group",
"descendant_closure": "unproved",
"compiler_runtime_compatibility": "unverified"},
"limits": {name: getattr(args, name) for name in (
"timeout", "max_output_bytes", "max_total_output_bytes", "max_profile_bytes",
"max_total_profile_bytes", "max_profile_files", "conversion_timeout",
"reduction_timeout", "max_map_bytes")},
"selection_environment": {"CBM_SKIP_PERF": environment.get("CBM_SKIP_PERF")}}
# Consumer acceptance requires this final marker AND both wire digests.
evidence.publish(meta)
return 1 if failed else 0
except (CollectionError, ProfileMapError, ProfileTextError, OSError, UnicodeError,
ValueError, MemoryError) as error:
kind = getattr(error, "kind", "input_or_conversion")
if isinstance(error, (ProfileMapLimitExceededError, LimitExceededError, MemoryError)):
kind = "resource_limit"
if isinstance(error, (ProfileMapCancelledError, ParseCancelledError)):
kind = "reduction_timeout"
print("coverage-map: format2 %s: %s" % (kind, str(error)), file=sys.stderr)
if evidence is not None:
try:
evidence.json("failure.json", {"format": 2, "error": kind,
"message": str(error), "processes": evidence.records,
"uncertainty": evidence.uncertainty})
except (OSError, ValueError, MemoryError):
pass
return 2
finally:
if evidence is not None:
evidence.close()
@@ -0,0 +1,326 @@
# Distinct changed-file sets of the September 2026 PR pushes (git diff
# --name-only merge-base..head per push), referenced by 2026-09-pushes.tsv.
# Columns (TAB): fileset, paths (space-separated; no path contains a space).
f001 README.md
f002 internal/cbm/vendored/grammars/swift/scanner.c scripts/vendored-checksums.txt tests/test_extraction.c
f003 internal/cbm/lsp/go_lsp.c src/pipeline/pass_lsp_cross.c tests/test_go_lsp.c tests/test_parallel.c
f004 src/foundation/mem.c src/foundation/mem.h tests/test_mem.c
f005 Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h src/mcp/mcp.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline_internal.h src/store/store.c tests/test_index_resilience.c tests/test_mcp.c tests/test_parse_coverage.c tests/test_pipeline.c
f006 .github/workflows/pr.yml Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h scripts/ci/README.md scripts/ci/coverage-gate-allowlist.txt scripts/ci/parse-partial-baseline.txt scripts/ci/self-index-coverage-gate.sh src/mcp/mcp.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline_internal.h src/store/store.c tests/test_index_resilience.c tests/test_mcp.c tests/test_parse_coverage.c tests/test_pipeline.c tests/test_venue_parity_contract.sh
f007 README.md scripts/smoke-test.sh src/cli/cli.c tests/test_cli.c
f008 src/ui/http_server.c tests/test_httpd.c
f009 src/daemon/application.c src/daemon/application.h src/daemon/frontend.c src/daemon/frontend.h src/daemon/runtime.c src/daemon/runtime.h src/main.c tests/test_daemon_frontend.c tests/test_daemon_runtime.c
f010 internal/cbm/lsp/c_lsp.c internal/cbm/lsp/type_registry.c internal/cbm/lsp/type_registry.h tests/test_c_lsp.c
f011 src/pipeline/fqn.c src/pipeline/pass_pkgmap.c tests/test_edge_imports.c tests/test_fqn.c
f012 internal/cbm/extract_calls.c tests/test_extraction.c tests/test_pipeline.c
f013 src/mcp/mcp.c tests/test_mcp.c
f014 src/cypher/cypher.c tests/test_cypher.c
f015 src/mcp/mcp.c src/store/store.c src/store/store.h src/ui/layout3d.c src/ui/layout3d.h tests/test_store_arch.c tests/test_ui.c
f016 Makefile.cbm README.md docs/index.html docs/llms.txt scripts/smoke-invariants.sh src/cli/cli.c src/cli/cli.h src/foundation/platform.c src/foundation/platform.h src/mcp/mcp.c src/personal_memory/memory.c src/personal_memory/memory.h tests/test_cli.c tests/test_mcp.c
f017 internal/cbm/extract_defs.c tests/test_extraction.c
f018 internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/swift/scanner.c scripts/vendored-checksums.txt tests/test_extraction.c
f019 internal/cbm/extract_calls.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/swift/scanner.c scripts/vendored-checksums.txt tests/test_extraction.c tests/test_pipeline.c
f020 src/daemon/application.c tests/test_daemon_application.c tests/windows/test_non_ascii_path.py
f021 internal/cbm/lsp/ts_lsp.c tests/repro/repro_lsp_ts.c
f022 src/graph_buffer/graph_buffer.c src/mcp/mcp.c tests/test_graph_buffer.c tests/test_mcp.c
f023 internal/cbm/cbm.h internal/cbm/extract_calls.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
f024 .github/workflows/pr.yml Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h scripts/ci/README.md scripts/ci/coverage-gate-allowlist.txt scripts/ci/parse-partial-baseline.txt scripts/ci/self-index-coverage-gate.sh scripts/test.sh src/mcp/mcp.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline_internal.h src/store/store.c tests/test_coverage_gate_contract.sh tests/test_index_resilience.c tests/test_mcp.c tests/test_parse_coverage.c tests/test_pipeline.c tests/test_venue_parity_contract.sh
f025 Makefile.cbm README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/cli/cli.h src/daemon/application.c src/discover/discover.c src/discover/discover.h src/foundation/index_policy.c src/foundation/index_policy.h src/mcp/mcp.c src/mcp/mcp_internal.h src/pipeline/pipeline.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_daemon_application.c tests/test_discover.c tests/test_index_policy.c tests/test_main.c tests/test_pipeline.c tests/test_worker_error_response.sh tests/test_worker_watchdog.sh
f026 Makefile.cbm src/cli/cli.c src/pipeline/pass_doclinks.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_doclinks.c tests/test_edge_structural.c tests/test_lang_contract.c tests/test_main.c
f027 src/mcp/mcp.c src/mcp/mcp_internal.h tests/test_mcp.c
f028 internal/cbm/extract_defs.c src/discover/language.c src/pipeline/pass_route_nodes.c tests/test_extraction.c tests/test_language.c
f029 README.md docs/CONFIGURATION.md scripts/smoke-test.sh src/cli/cli.c src/cli/cli.h src/main.c tests/test_cli.c
f030 src/main.c tests/test_parent_watchdog.sh
f031 internal/cbm/extract_imports.c tests/test_extraction.c
f032 internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_unified.c internal/cbm/helpers.h src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
f033 internal/cbm/extract_calls.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
f034 internal/cbm/cbm.c internal/cbm/preprocessor.cpp internal/cbm/preprocessor.h tests/test_extraction.c
f035 internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_unified.c internal/cbm/helpers.c internal/cbm/lsp/type_registry.c src/pipeline/lsp_resolve.h src/pipeline/registry.c tests/test_extraction.c tests/test_pipeline.c
f036 internal/cbm/cbm.h internal/cbm/extract_semantic.c internal/cbm/extract_usages.c src/pipeline/pass_parallel.c src/pipeline/pass_usages.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_pipeline.c tests/test_registry.c
f037 internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_defs.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
f038 internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_unified.c internal/cbm/extract_unified.h src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
f039 src/cli/cli.c src/ui/http_server.c tests/test_cli.c
f040 internal/cbm/extract_channels.c tests/test_extraction.c tests/test_pipeline.c
f041 internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_unified.c internal/cbm/helpers.h internal/cbm/lsp/go_lsp.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_extraction.c tests/test_parallel.c tests/test_pipeline.c tests/test_registry.c
f042 internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_unified.c internal/cbm/helpers.c internal/cbm/helpers.h internal/cbm/lsp/go_lsp.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_extraction.c tests/test_parallel.c tests/test_pipeline.c tests/test_registry.c
f043 internal/cbm/extract_defs.c tests/test_edge_types_probe.c
f044 internal/cbm/extract_calls.c tests/test_extraction.c
f045 internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_channels.c internal/cbm/extract_defs.c internal/cbm/extract_env_accesses.c internal/cbm/extract_imports.c internal/cbm/extract_node_stack.h internal/cbm/extract_semantic.c internal/cbm/extract_type_assigns.c internal/cbm/extract_type_refs.c tests/test_extraction.c tests/test_mem.c
f046 src/cli/client_adapter.c tests/test_agent_clients.c
f047 graph-ui/src/hooks/useProjects.test.tsx graph-ui/src/hooks/useProjects.ts
f048 internal/cbm/cbm.c tests/test_parse_coverage.c
f049 graph-ui/src/App.test.tsx graph-ui/src/App.tsx src/ui/http_server.c tests/test_httpd.c
f050 src/daemon/application.c src/foundation/workspace.c src/foundation/workspace.h src/mcp/mcp.c src/ui/http_server.c tests/test_workspace.c
f051 README.md docs/CONFIGURATION.md src/cli/cli.c src/cli/cli.h src/daemon/ipc.c src/foundation/compat_fs.c src/foundation/compat_fs.h src/main.c tests/test_daemon_ipc.c
f052 src/traces/traces.c src/traces/traces.h tests/test_traces.c
f053 src/pipeline/registry.c tests/test_pipeline.c
f054 src/cli/cli.h src/cli/hook_augment.c src/foundation/platform.c src/foundation/platform.h src/mcp/index_supervisor.c src/mcp/mcp.c src/mcp/mcp.h tests/test_cli.c tests/test_platform.c
f055 tests/test_cli.c
f056 src/daemon/application.c src/mcp/mcp.c src/pipeline/pipeline.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c tests/test_daemon_application.c tests/test_mcp.c tests/test_pipeline.c
f057 internal/cbm/extract_defs.c src/discover/language.c tests/test_extraction.c tests/test_language.c
f058 src/cli/hook_augment.c src/daemon/runtime.c src/daemon/runtime.h src/foundation/sha256.c src/foundation/sha256.h src/main.c tests/test_cli.c tests/test_daemon_runtime.c
f059 tests/windows/test_cli_non_ascii_arg.py tests/windows/test_daemon_stability.py tests/windows/test_hook_augment.py
f060 src/cli/cli.c
f061 .github/workflows/_test.yml
f062 scripts/smoke-test.sh
f063 src/foundation/subprocess.c src/foundation/subprocess.h src/mcp/index_supervisor.c tests/test_main.c tests/test_subprocess.c
f064 graph-ui/src/components/EdgeLines.test.ts graph-ui/src/components/EdgeLines.tsx graph-ui/src/components/GraphTab.edgeSampling.test.tsx graph-ui/src/components/GraphTab.test.ts graph-ui/src/components/GraphTab.tsx graph-ui/src/components/NodeCloud.test.ts graph-ui/src/components/NodeCloud.tsx graph-ui/src/components/NodeDetailPanel.test.tsx graph-ui/src/components/NodeDetailPanel.tsx graph-ui/src/components/NodeLabels.tsx graph-ui/src/hooks/useGraphData.test.ts graph-ui/src/hooks/useGraphData.ts graph-ui/src/lib/edgeBudget.test.ts graph-ui/src/lib/edgeBudget.ts graph-ui/src/lib/graphIndex.test.ts graph-ui/src/lib/graphIndex.ts graph-ui/src/lib/topK.test.ts graph-ui/src/lib/topK.ts src/store/store.c src/store/store.h src/ui/http_server.c src/ui/layout3d.c src/ui/layout3d.h src/ui/layout3d_internal.h tests/test_httpd.c tests/test_store_edges.c tests/test_store_search.c tests/test_ui.c
f065 graph-ui/package-lock.json
f066 src/foundation/lock_registry.c src/foundation/lock_registry_internal.h tests/test_lock_registry.c
f067 src/daemon/version_cohort.c src/daemon/version_cohort.h tests/test_version_cohort.c
f068 internal/cbm/extract_defs.c tests/test_extraction_inheritance.c
f069 internal/cbm/extract_channels.c tests/test_extraction.c
f070 test-infrastructure/Dockerfile.msan
f071 src/git/git_context.c src/ui/http_server.c tests/test_daemon_application.c tests/test_git_context.c tests/test_httpd.c
f072 scripts/hooks/pre-commit
f073 tests/test_daemon_runtime.c
f074 src/cli/agent_profiles.c src/cli/client_adapter.c tests/test_agent_clients.c tests/test_agent_profiles.c
f075 internal/cbm/sqlite_writer.c src/graph_buffer/graph_buffer.c tests/test_graph_buffer.c
f076 scripts/run-test-wave.py tests/test_parallel_harness_contract.sh
f077 internal/cbm/extract_imports.c internal/cbm/lang_specs.c tests/test_edge_structural.c tests/test_extraction.c tests/test_grammar_labels.c
f078 internal/cbm/sqlite_writer.c src/graph_buffer/graph_buffer.c tests/test_graph_buffer.c tests/test_sqlite_writer.c
f079 src/foundation/dump_verify.c src/foundation/dump_verify.h tests/test_dump_verify.c
f080 Makefile.cbm scripts/ci/check-binary-composition.sh tests/test_mcpb_bundle_contract.sh tests/test_release_candidate_derivation_contract.sh
f081 scripts/smoke-test.sh src/cli/cli.c tests/test_cli.c tests/windows/test_hook_augment.py
f082 src/main.c tests/windows/test_daemon_lifecycle.py
f083 README.md docs/CONFIGURATION.md graph-ui/src/components/NodeDetailPanel.test.tsx graph-ui/src/components/NodeDetailPanel.tsx graph-ui/src/components/StatsTab.test.tsx graph-ui/src/hooks/useProjects.test.tsx graph-ui/src/hooks/useProjects.ts scripts/smoke-test.sh src/cli/cli.c src/cli/cli.h src/cli/client_adapter.c src/cli/hook_augment.c src/cli/progress_sink.c src/cli/progress_sink.h src/cypher/cypher.c src/cypher/cypher.h src/foundation/compat_fs.c src/foundation/compat_fs.h src/foundation/log.c src/foundation/log.h src/main.c src/mcp/compact_out.c src/mcp/compact_out.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c src/store/store.c src/store/store.h src/ui/http_server.c tests/scale_contract.sh tests/test_cli.c tests/test_cypher.c tests/test_daemon_runtime.c tests/test_diagnostics.c tests/test_httpd.c tests/test_incremental.c tests/test_index_resilience.c tests/test_index_supervisor.c tests/test_log.c tests/test_main.c tests/test_mcp.c tests/test_pipeline.c tests/test_store_pragmas.c tests/windows/test_daemon_lifecycle.py tests/windows/test_daemon_stability.py
f084 README.md docs/CONFIGURATION.md graph-ui/src/components/NodeDetailPanel.test.tsx graph-ui/src/components/NodeDetailPanel.tsx graph-ui/src/components/StatsTab.test.tsx graph-ui/src/hooks/useProjects.test.tsx graph-ui/src/hooks/useProjects.ts scripts/smoke-test.sh src/cli/cli.c src/cli/cli.h src/cli/client_adapter.c src/cli/hook_augment.c src/cli/progress_sink.c src/cli/progress_sink.h src/cypher/cypher.c src/cypher/cypher.h src/foundation/compat_fs.c src/foundation/compat_fs.h src/foundation/log.c src/foundation/log.h src/main.c src/mcp/compact_out.c src/mcp/compact_out.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c src/store/store.c src/store/store.h src/ui/http_server.c tests/scale_contract.sh tests/test_cli.c tests/test_cypher.c tests/test_daemon_runtime.c tests/test_diagnostics.c tests/test_httpd.c tests/test_incremental.c tests/test_index_resilience.c tests/test_index_supervisor.c tests/test_log.c tests/test_main.c tests/test_mcp.c tests/test_pipeline.c tests/test_store_pragmas.c tests/windows/test_daemon_lifecycle.py tests/windows/test_daemon_stability.py tests/windows/test_hook_augment.py
f085 src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_pipeline.c tests/test_registry.c
f086 src/cli/config_yaml_edit.c tests/test_config_yaml_edit.c
f087 src/foundation/compat_fs.c tests/test_cli.c tests/test_platform.c
f088 .github/workflows/release.yml
f089 .github/workflows/pages.yml
f090 src/store/store.c tests/test_store_nodes.c
f091 src/discover/discover.c tests/test_discover.c
f092 Makefile.cbm internal/cbm/extract_defs.c internal/cbm/helpers.c internal/cbm/helpers.h tests/repro/repro_issue1692.c tests/repro/repro_main.c
f093 src/mcp/mcp.c src/store/store.c tests/test_mcp.c tests/test_store_nodes.c
f094 tests/test_mcp.c
f095 src/mcp/mcp.c src/store/store.c src/store/store.h tests/test_mcp.c
f096 scripts/msan.sh scripts/test.sh tests/test_main.c
f097 src/daemon/ipc.c src/daemon/ipc.h tests/test_cli.c tests/test_daemon_ipc.c
f098 src/daemon/application.c src/mcp/mcp.c tests/test_daemon.c
f099 Makefile.cbm README.md graph-ui/index.html graph-ui/src/App.tsx graph-ui/src/components/ChangesTab.tsx graph-ui/src/components/CommandK.tsx graph-ui/src/components/ControlTab.tsx graph-ui/src/components/DashboardTab.tsx graph-ui/src/components/DisplaySettingsMenu.tsx graph-ui/src/components/ErrorBoundary.tsx graph-ui/src/components/FilterPanel.tsx graph-ui/src/components/FlowsTab.observed.test.tsx graph-ui/src/components/FlowsTab.tsx graph-ui/src/components/FlowsTab.zh.test.tsx graph-ui/src/components/GraphLoader.tsx graph-ui/src/components/GraphScene.tsx graph-ui/src/components/GraphTab.deadcode.test.tsx graph-ui/src/components/GraphTab.filters.test.tsx graph-ui/src/components/GraphTab.regions.test.tsx graph-ui/src/components/GraphTab.selection.test.tsx graph-ui/src/components/GraphTab.tsx graph-ui/src/components/HaloLayer.tsx graph-ui/src/components/MetricChip.tsx graph-ui/src/components/Minimap.tsx graph-ui/src/components/MissedCallout.tsx graph-ui/src/components/ModulesTab.tsx graph-ui/src/components/NodeDetailPanel.showmore.test.tsx graph-ui/src/components/NodeDetailPanel.tsx graph-ui/src/components/NodeTooltip.tsx graph-ui/src/components/OverviewTab.tsx graph-ui/src/components/ProjectCard.tsx graph-ui/src/components/ProjectSwitcher.tsx graph-ui/src/components/PromptBasket.tsx graph-ui/src/components/RegionPanel.tsx graph-ui/src/components/RegionTooltip.tsx graph-ui/src/components/Sidebar.tsx graph-ui/src/components/StatsTab.tsx graph-ui/src/components/SymbolTab.tsx graph-ui/src/components/TriggerTree.tsx graph-ui/src/components/WikiHoverCard.test.tsx graph-ui/src/components/WikiHoverCard.tsx graph-ui/src/components/WikiPanel.test.tsx graph-ui/src/components/WikiPanel.tsx graph-ui/src/components/WikiPanel.zh.test.tsx graph-ui/src/components/wikiTier.ts graph-ui/src/hooks/useGraphData.ts graph-ui/src/hooks/useProjects.test.tsx graph-ui/src/hooks/useProjects.ts graph-ui/src/lib/atlas.test.ts graph-ui/src/lib/atlas.ts graph-ui/src/lib/complexity.test.ts graph-ui/src/lib/complexity.ts graph-ui/src/lib/composer.ts graph-ui/src/lib/density.test.ts graph-ui/src/lib/density.ts graph-ui/src/lib/findings.test.ts graph-ui/src/lib/findings.ts graph-ui/src/lib/firstread.test.ts graph-ui/src/lib/firstread.ts graph-ui/src/lib/flowgroup.test.ts graph-ui/src/lib/flowgroup.ts graph-ui/src/lib/i18n.ts graph-ui/src/lib/impact.test.ts graph-ui/src/lib/impact.ts graph-ui/src/lib/observed.test.ts graph-ui/src/lib/observed.ts graph-ui/src/lib/questions.test.ts graph-ui/src/lib/questions.ts graph-ui/src/lib/rationale.test.ts graph-ui/src/lib/rationale.ts graph-ui/src/lib/regions.test.ts graph-ui/src/lib/regions.ts graph-ui/src/lib/treemap.ts graph-ui/src/lib/types.ts graph-ui/src/lib/who.test.ts graph-ui/src/lib/who.ts graph-ui/src/lib/why.test.ts graph-ui/src/lib/why.ts graph-ui/src/lib/whyfetch.ts graph-ui/src/styles/globals.css graph-ui/src/test/setup.ts graph-ui/src/wiki/entries.test.ts graph-ui/src/wiki/entries.ts graph-ui/tsconfig.tsbuildinfo graph-ui/vite.config.ts scripts/run-test-wave.py scripts/security-ui.sh src/main.c src/mcp/mcp.c src/pipeline/pipeline.h src/pipeline/pipeline_internal.h src/store/store.c src/store/store.h src/ui/atlas.h src/ui/atlas_api.c src/ui/atlas_flows.c src/ui/atlas_handout.c src/ui/atlas_metrics.c src/ui/atlas_why.c src/ui/http_server.c src/ui/layout3d.c src/ui/layout3d.h src/ui/layout_internal.h src/ui/layout_regions.c src/watcher/watcher.c src/watcher/watcher.h test-infrastructure/docker-compose.yml test-infrastructure/ladder.sh test-infrastructure/run.sh tests/test_cypher.c tests/test_daemon_runtime.c tests/test_httpd.c tests/test_incremental.c tests/test_mcp.c tests/test_py_lsp_scale.c tests/test_ui.c tests/test_watcher.c
f100 README.md docs/CONFIGURATION.md scripts/test_mcp_in_process.py src/main.c
f101 tests/test_pipeline.c
f102 src/daemon/application.c src/daemon/application_internal.h tests/test_daemon.c tests/test_daemon_application.c
f103 src/discover/discover.c src/discover/discover.h src/discover/language.c tests/test_language.c
f104 src/watcher/watcher.c src/watcher/watcher.h tests/test_watcher.c
f105 scripts/test.sh src/main.c tests/test_worker_session_scope.sh
f106 src/pipeline/pass_complexity.c tests/fixtures/complexity_pass_cycle_order/p00_a.py tests/fixtures/complexity_pass_cycle_order/p00_b.py tests/fixtures/complexity_pass_cycle_order/p01_a.py tests/fixtures/complexity_pass_cycle_order/p01_b.py tests/fixtures/complexity_pass_cycle_order/p02_a.py tests/fixtures/complexity_pass_cycle_order/p02_b.py tests/fixtures/complexity_pass_cycle_order/p03_a.py tests/fixtures/complexity_pass_cycle_order/p03_b.py tests/fixtures/complexity_pass_cycle_order/p04_a.py tests/fixtures/complexity_pass_cycle_order/p04_b.py tests/fixtures/complexity_pass_cycle_order/p05_a.py tests/fixtures/complexity_pass_cycle_order/p05_b.py tests/fixtures/complexity_pass_cycle_order/p06_a.py tests/fixtures/complexity_pass_cycle_order/p06_b.py tests/fixtures/complexity_pass_cycle_order/p07_a.py tests/fixtures/complexity_pass_cycle_order/p07_b.py tests/fixtures/complexity_pass_cycle_order/p08_a.py tests/fixtures/complexity_pass_cycle_order/p08_b.py tests/fixtures/complexity_pass_cycle_order/p09_a.py tests/fixtures/complexity_pass_cycle_order/p09_b.py tests/fixtures/complexity_pass_cycle_order/p10_a.py tests/fixtures/complexity_pass_cycle_order/p10_b.py tests/fixtures/complexity_pass_cycle_order/p11_a.py tests/fixtures/complexity_pass_cycle_order/p11_b.py tests/fixtures/complexity_pass_cycle_order/p12_a.py tests/fixtures/complexity_pass_cycle_order/p12_b.py tests/fixtures/complexity_pass_cycle_order/p13_a.py tests/fixtures/complexity_pass_cycle_order/p13_b.py tests/fixtures/complexity_pass_cycle_order/p14_a.py tests/fixtures/complexity_pass_cycle_order/p14_b.py tests/fixtures/complexity_pass_cycle_order/p15_a.py tests/fixtures/complexity_pass_cycle_order/p15_b.py tests/fixtures/complexity_pass_cycle_order/p16_a.py tests/fixtures/complexity_pass_cycle_order/p16_b.py tests/fixtures/complexity_pass_cycle_order/p17_a.py tests/fixtures/complexity_pass_cycle_order/p17_b.py tests/fixtures/complexity_pass_cycle_order/p18_a.py tests/fixtures/complexity_pass_cycle_order/p18_b.py tests/fixtures/complexity_pass_cycle_order/p19_a.py tests/fixtures/complexity_pass_cycle_order/p19_b.py tests/fixtures/complexity_pass_cycle_order/p20_a.py tests/fixtures/complexity_pass_cycle_order/p20_b.py tests/fixtures/complexity_pass_cycle_order/p21_a.py tests/fixtures/complexity_pass_cycle_order/p21_b.py tests/fixtures/complexity_pass_cycle_order/p22_a.py tests/fixtures/complexity_pass_cycle_order/p22_b.py tests/fixtures/complexity_pass_cycle_order/p23_a.py tests/fixtures/complexity_pass_cycle_order/p23_b.py tests/fixtures/complexity_pass_cycle_order/t00_x.py tests/fixtures/complexity_pass_cycle_order/t00_y.py tests/fixtures/complexity_pass_cycle_order/t00_z.py tests/fixtures/complexity_pass_cycle_order/t01_x.py tests/fixtures/complexity_pass_cycle_order/t01_y.py tests/fixtures/complexity_pass_cycle_order/t01_z.py tests/fixtures/complexity_pass_cycle_order/t02_x.py tests/fixtures/complexity_pass_cycle_order/t02_y.py tests/fixtures/complexity_pass_cycle_order/t02_z.py tests/fixtures/complexity_pass_cycle_order/t03_x.py tests/fixtures/complexity_pass_cycle_order/t03_y.py tests/fixtures/complexity_pass_cycle_order/t03_z.py tests/fixtures/complexity_pass_cycle_order/t04_x.py tests/fixtures/complexity_pass_cycle_order/t04_y.py tests/fixtures/complexity_pass_cycle_order/t04_z.py tests/fixtures/complexity_pass_cycle_order/t05_x.py tests/fixtures/complexity_pass_cycle_order/t05_y.py tests/fixtures/complexity_pass_cycle_order/t05_z.py tests/test_pipeline.c
f107 docs/benchmarks/bounded-two-pass-results.json docs/benchmarks/bounded-two-pass.md scripts/benchmark-streaming.py src/pipeline/lsp_surface.c src/pipeline/lsp_surface.h src/pipeline/pass_parallel.c src/pipeline/pipeline.c src/pipeline/pipeline_internal.h tests/test_pipeline.c
f108 src/pipeline/pass_semantic_edges.c tests/test_pipeline.c
f109 .github/workflows/pr.yml internal/cbm/cbm.c scripts/ci/README.md scripts/ci/coverage-gate-allowlist.txt scripts/ci/parse-partial-baseline.txt scripts/ci/self-index-coverage-gate.sh scripts/test.sh tests/test_coverage_gate_contract.sh tests/test_parse_coverage.c tests/test_venue_parity_contract.sh
f110 Makefile.cbm README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/cli/cli.h src/daemon/application.c src/discover/discover.c src/discover/discover.h src/foundation/index_policy.c src/foundation/index_policy.h src/foundation/subprocess.c src/foundation/subprocess.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_daemon_application.c tests/test_discover.c tests/test_index_policy.c tests/test_index_supervisor.c tests/test_main.c tests/test_mcp.c tests/test_pipeline.c tests/test_subprocess.c tests/test_worker_error_response.sh tests/test_worker_watchdog.sh
f111 Makefile.cbm README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/cli/cli.h src/daemon/application.c src/daemon/bootstrap.c src/discover/discover.c src/discover/discover.h src/foundation/compat_fs.c src/foundation/compat_fs.h src/foundation/index_policy.c src/foundation/index_policy.h src/foundation/subprocess.c src/foundation/subprocess.h src/main.c src/mcp/index_supervisor.c src/mcp/index_supervisor.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_daemon_application.c tests/test_discover.c tests/test_index_policy.c tests/test_index_supervisor.c tests/test_main.c tests/test_mcp.c tests/test_pipeline.c tests/test_platform.c tests/test_subprocess.c tests/test_worker_error_response.sh tests/test_worker_watchdog.sh
f112 Makefile.cbm README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/cli/cli.h src/daemon/application.c src/daemon/bootstrap.c src/discover/discover.c src/discover/discover.h src/foundation/compat_fs.c src/foundation/compat_fs.h src/foundation/index_policy.c src/foundation/index_policy.h src/foundation/subprocess.c src/foundation/subprocess.h src/git/git_context.c src/git/git_context.h src/main.c src/mcp/index_supervisor.c src/mcp/index_supervisor.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_daemon_application.c tests/test_discover.c tests/test_git_context.c tests/test_index_policy.c tests/test_index_supervisor.c tests/test_main.c tests/test_mcp.c tests/test_pipeline.c tests/test_platform.c tests/test_subprocess.c tests/test_worker_error_response.sh tests/test_worker_watchdog.sh
f113 src/pipeline/pipeline_incremental.c tests/test_pipeline.c
f114 README.md THIRD_PARTY.md docs/index.html docs/llms.txt internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/grammar_vba.c internal/cbm/helpers.c internal/cbm/lang_specs.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/vba/LICENSE internal/cbm/vendored/grammars/vba/parser.c internal/cbm/vendored/grammars/vba/tree_sitter/alloc.h internal/cbm/vendored/grammars/vba/tree_sitter/array.h internal/cbm/vendored/grammars/vba/tree_sitter/parser.h pkg/chocolatey/codebase-memory-mcp.nuspec pkg/npm/README.md scripts/new-languages.json scripts/package-release.sh scripts/vendored-checksums.txt server.json src/discover/discover.h src/discover/language.c src/discover/userconfig.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_manifest.c tests/repro/repro_invariant_breadth.c tests/repro/repro_language_registry.c tests/test_extraction.c tests/test_grammar_labels.c tests/test_grammar_regression.c tests/test_lang_contract.c tests/test_language.c
f115 README.md THIRD_PARTY.md docs/index.html docs/llms.txt internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/extract_unified.h internal/cbm/grammar_vba.c internal/cbm/helpers.c internal/cbm/helpers.h internal/cbm/lang_specs.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/vba/LICENSE internal/cbm/vendored/grammars/vba/parser.c internal/cbm/vendored/grammars/vba/tree_sitter/alloc.h internal/cbm/vendored/grammars/vba/tree_sitter/array.h internal/cbm/vendored/grammars/vba/tree_sitter/parser.h pkg/chocolatey/codebase-memory-mcp.nuspec pkg/npm/README.md scripts/new-languages.json scripts/package-release.sh scripts/vendored-checksums.txt server.json src/discover/discover.h src/discover/language.c src/discover/userconfig.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_manifest.c tests/repro/repro_invariant_breadth.c tests/repro/repro_language_registry.c tests/test_extraction.c tests/test_grammar_labels.c tests/test_grammar_regression.c tests/test_lang_contract.c tests/test_language.c
f116 internal/cbm/lang_specs.c
f117 internal/cbm/extract_node_stack.h internal/cbm/lsp/ts_lsp.c tests/test_ts_lsp.c
f118 .cbmignore src/cli/cli.c src/cli/cli.h src/main.c src/mcp/mcp.c
f119 .cbmignore src/cli/cli.c src/cli/cli.h src/main.c src/mcp/mcp.c tests/test_cli.c
f120 src/git/git_context.c src/git/git_context.h src/mcp/mcp.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/store/store.c src/store/store.h tests/test_git_context.c tests/test_mcp.c tests/test_store_nodes.c
f121 src/watcher/watcher.c tests/test_watcher.c
f122 scripts/run-tests-parallel.sh src/git/git_context.c src/git/git_context.h src/mcp/mcp.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/store/store.c src/store/store.h tests/test_git_context.c tests/test_mcp.c tests/test_store_nodes.c
f123 scripts/test-windows.ps1 tests/windows/test_mcp_stdio.py
f124 internal/cbm/extract_defs.c tests/test_grammar_labels.c tests/test_grammar_regression.c
f125 src/cli/cli.c tests/test_cli.c
f126 src/cli/cli.c src/cli/config_json_like.c src/cli/hook_augment.c src/daemon/application.c src/foundation/compat_fs.c src/foundation/compat_fs.h src/main.c src/mcp/index_supervisor.c src/mcp/mcp.c src/pipeline/artifact.c src/pipeline/pipeline.c src/ui/config.c tests/test_artifact.c tests/test_cli.c tests/test_platform.c
f127 tests/test_daemon_frontend.c
f128 src/daemon/ipc.c src/daemon/ipc_internal.h tests/test_daemon_ipc.c
f129 .github/dependabot.yml .github/workflows/_build.yml .github/workflows/_lint.yml .github/workflows/_test.yml .github/workflows/release.yml .node-version CONTRIBUTING.md scripts/ci/lint-workflows.sh scripts/ci/test-frontend.sh tests/test_venue_parity_contract.sh
f130 src/foundation/compat_fs.c src/foundation/compat_fs.h src/pipeline/pipeline.c src/pipeline/pipeline_internal.h tests/test_pipeline.c
f131 src/daemon/application.c src/mcp/mcp.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/pipeline_internal.h tests/test_daemon_application.c tests/test_incremental.c tests/test_mcp.c tests/test_pipeline.c
f132 src/daemon/bootstrap.c src/daemon/bootstrap.h src/daemon/host.c src/daemon/ipc.c src/daemon/ipc.h src/daemon/ipc_internal.h src/foundation/platform.c src/foundation/platform.h tests/test_daemon_bootstrap.c tests/test_daemon_ipc.c
f133 Makefile.cbm src/cli/cli.c src/cli/config_edit_path.c src/cli/config_edit_path.h src/cli/config_json_like.c src/cli/config_text_edit.c src/cli/config_toml_edit.c src/cli/config_yaml_edit.c tests/test_cli.c tests/test_config_json_like.c tests/test_config_text_edit.c tests/test_config_toml_edit.c tests/test_config_yaml_edit.c
f134 src/cli/cli.c src/mcp/index_supervisor.c src/mcp/index_supervisor.h tests/test_cli.c
f135 src/cli/cli.c src/cli/config_json_like.c src/cli/config_json_like.h src/mcp/mcp.c tests/test_cli.c tests/test_config_json_like.c tests/test_mcp.c
f136 src/cli/cli.c tests/test_cli.c tests/test_smoke_fixture_contract.sh
f137 .github/workflows/_lint.yml .github/workflows/_test.yml
f138 src/daemon/ipc.c src/daemon/ipc.h tests/test_daemon_ipc.c
f139 src/cli/activation_transaction.c src/daemon/ipc.c src/daemon/ipc.h tests/test_daemon_ipc.c
f140 src/cli/cli.h src/main.c tests/test_cli.c
f141 internal/cbm/extract_defs.c tests/test_edge_types_probe.c tests/test_extraction.c
f142 internal/cbm/lsp/php_lsp.c
f143 internal/cbm/lsp/php_lsp.c src/pipeline/registry.c
f144 src/cli/cli.c src/cli/cli.h src/main.c src/mcp/mcp.c tests/test_cli.c
f145 README.md docs/UPSTREAM-SYNC.md docs/lsp-uplift/PERL-CROSS-FILE-INHERITANCE.md docs/lsp-uplift/PERL-DUCK-TYPING-AND-CROSS-LANG-AUDIT.md docs/lsp-uplift/PLAN.md docs/lsp-uplift/RESOLVER-PLAYBOOK.md docs/lsp-uplift/SOP.md internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/helpers.c internal/cbm/lang_specs.c internal/cbm/lsp/generated/perl_stdlib_data.c internal/cbm/lsp/generated/rust_crates_seed.c internal/cbm/lsp/go_lsp.c internal/cbm/lsp/go_lsp.h internal/cbm/lsp/go_stdlib_modern.c internal/cbm/lsp/java_lsp.c internal/cbm/lsp/perl_lsp.c internal/cbm/lsp/perl_lsp.h internal/cbm/lsp/py_builtins.c internal/cbm/lsp/py_lsp.c internal/cbm/lsp/py_stdlib_compat.c internal/cbm/lsp/rust_cargo.c internal/cbm/lsp/rust_cargo.h internal/cbm/lsp/rust_lsp.c internal/cbm/lsp/rust_lsp.h internal/cbm/lsp/type_registry.h internal/cbm/lsp_all.c internal/cbm/service_patterns.c internal/cbm/service_patterns.h src/discover/language.c src/pipeline/lsp_surface.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_lsp_cross.h src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pass_route_nodes.c src/pipeline/pass_semantic.c src/pipeline/pass_tests.c src/pipeline/pipeline.c src/pipeline/registry.c tests/test_extraction.c tests/test_go_lsp.c tests/test_java_lsp.c tests/test_language.c tests/test_perl_lsp.c tests/test_pipeline.c tests/test_py_lsp.c tests/test_registry.c tests/test_route_canon.c tests/test_rust_lsp.c
f146 src/foundation/subprocess.c src/foundation/subprocess.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h tests/test_index_supervisor.c tests/test_main.c tests/test_subprocess.c
f147 src/daemon/daemon.c src/daemon/daemon.h src/daemon/host.c src/daemon/runtime.c src/daemon/runtime.h tests/test_daemon_runtime.c
f148 internal/cbm/cbm.c src/foundation/compat.c src/foundation/compat.h tests/test_edge_types_probe.c
f149 scripts/test-windows.ps1 tests/windows/mcp_stdio.py tests/windows/test_mcp_stdio.py
f150 .github/workflows/codeql.yml
f151 .github/workflows/scorecard.yml
f152 src/cli/cli.c src/foundation/sha256.c src/foundation/sha256.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h tests/test_cli.c
f153 install.ps1 scripts/test.sh src/cli/activation_transaction.c src/cli/cli.c tests/test_activation_diagnostic_contract.sh tests/test_windows_bundle_contract.sh
f154 scripts/run-test-wave.py src/cli/cli.c src/foundation/sha256.c src/foundation/sha256.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h tests/test_cli.c
f155 .github/workflows/_security.yml graph-ui/package-lock.json graph-ui/package.json scripts/test.sh tests/test_security_gate_fail_closed.sh
f156 graph-ui/package-lock.json graph-ui/package.json
f157 .github/workflows/_security.yml .github/workflows/pr.yml .github/workflows/release.yml graph-ui/package-lock.json graph-ui/package.json scripts/test.sh tests/test_security_gate_fail_closed.sh
f158 tests/test_fqn.c
f159 .github/workflows/dry-run.yml tests/test_security_gate_fail_closed.sh
f160 tests/test_daemon_ipc.c tests/test_main.c
f161 src/daemon/ipc.c tests/test_daemon_ipc.c tests/test_main.c
f162 .github/workflows/_test.yml src/daemon/ipc.c src/daemon/ipc.h tests/test_daemon_ipc.c tests/test_main.c
f163 scripts/soak-test.sh scripts/test.sh tests/test_soak_runtime_isolation_contract.sh
f164 scripts/memlab.sh scripts/test.sh tests/test_memlab_runtime_isolation_contract.sh
f165 scripts/benchmark-index.sh scripts/benchmark-search-graph.sh scripts/test.sh tests/test_benchmark_runtime_isolation_contract.sh
f166 scripts/smoke-test.sh scripts/test.sh tests/test_smoke_runtime_isolation_contract.sh
f167 Makefile.cbm scripts/lint-memory-core.py scripts/memory-core-baseline.txt src/foundation/mem.c src/foundation/mem.h src/foundation/mem_core.c src/foundation/mem_core.h src/foundation/platform.h src/foundation/system_info.c src/pipeline/pipeline.c tests/test_mem.c
f168 src/cli/activation_transaction.c
f169 Makefile.cbm internal/cbm/arena.h internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_unified.c internal/cbm/lsp/c_lsp.c internal/cbm/lsp/c_lsp.h internal/cbm/lsp/cs_lsp.c internal/cbm/lsp/go_lsp.c internal/cbm/lsp/py_lsp.c internal/cbm/lsp/py_lsp.h internal/cbm/lsp/type_registry.c internal/cbm/lsp/type_registry.h internal/cbm/result_compact.c internal/cbm/result_spill.c internal/cbm/result_spill.h scripts/lint-memory-core.py scripts/memory-core-baseline.txt src/foundation/arena.c src/foundation/arena.h src/foundation/dyn_array.h src/foundation/hash_table.c src/foundation/hash_table.h src/foundation/mem.c src/foundation/mem.h src/foundation/mem_core.c src/foundation/mem_core.h src/foundation/platform.h src/foundation/slab_alloc.c src/foundation/system_info.c src/graph_buffer/graph_buffer.c src/graph_buffer/graph_buffer.h src/mcp/mcp.c src/pipeline/lsp_surface.c src/pipeline/lsp_surface.h src/pipeline/pass_complexity.c src/pipeline/pass_importance.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_lsp_cross.h src/pipeline/pass_parallel.c src/pipeline/pass_semantic.c src/pipeline/pass_semantic_edges.c src/pipeline/pass_similarity.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/worker_pool.c src/semantic/semantic.c src/semantic/semantic.h src/simhash/minhash.c tests/test_arena.c tests/test_c_lsp.c tests/test_extraction.c tests/test_mcp.c tests/test_mem.c tests/test_parallel.c tests/test_pipeline.c
f170 docs/CONFIGURATION.md src/main.c tests/test_daemon_open_readiness.py
f171 src/daemon/ipc.c tests/test_daemon_ipc.c
f172 src/cli/hook_augment.c src/daemon/runtime.c src/daemon/runtime.h src/main.c tests/test_cli.c tests/test_daemon_runtime.c
f173 Makefile.cbm internal/cbm/arena.h internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_unified.c internal/cbm/lsp/c_lsp.c internal/cbm/lsp/c_lsp.h internal/cbm/lsp/cs_lsp.c internal/cbm/lsp/go_lsp.c internal/cbm/lsp/py_lsp.c internal/cbm/lsp/py_lsp.h internal/cbm/lsp/type_registry.c internal/cbm/lsp/type_registry.h internal/cbm/result_compact.c internal/cbm/result_spill.c internal/cbm/result_spill.h scripts/lint-memory-core.py scripts/memory-core-baseline.txt src/foundation/arena.c src/foundation/arena.h src/foundation/diagnostics.c src/foundation/dyn_array.h src/foundation/hash_table.c src/foundation/hash_table.h src/foundation/mem.c src/foundation/mem.h src/foundation/mem_core.c src/foundation/mem_core.h src/foundation/platform.h src/foundation/slab_alloc.c src/foundation/system_info.c src/graph_buffer/graph_buffer.c src/graph_buffer/graph_buffer.h src/main.c src/mcp/mcp.c src/pipeline/lsp_surface.c src/pipeline/lsp_surface.h src/pipeline/pass_complexity.c src/pipeline/pass_importance.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_lsp_cross.h src/pipeline/pass_parallel.c src/pipeline/pass_semantic.c src/pipeline/pass_semantic_edges.c src/pipeline/pass_similarity.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/worker_pool.c src/semantic/semantic.c src/semantic/semantic.h src/simhash/minhash.c tests/test_arena.c tests/test_c_lsp.c tests/test_extraction.c tests/test_graph_buffer.c tests/test_mcp.c tests/test_mem.c tests/test_parallel.c tests/test_pipeline.c
f174 README.md scripts/smoke-test.sh src/cli/cli.c src/cli/cli.h tests/test_cli.c
f175 src/foundation/mem.c src/foundation/mem.h
f176 flake.nix pkg/chocolatey/codebase-memory-mcp.nuspec pkg/chocolatey/tools/chocolateyInstall.ps1 pkg/go/cmd/codebase-memory-mcp/main.go pkg/npm/package.json pkg/pypi/pyproject.toml pkg/pypi/src/codebase_memory_mcp/_cli.py server.json
f177 graph-ui/src/components/FilterPanel.tsx graph-ui/src/components/GraphTab.deadcode.test.tsx graph-ui/src/lib/colors.ts
f178 CONTRIBUTING.md README.md docs/index.html pkg/npm/README.md
f179 internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/service_patterns.c internal/cbm/service_patterns.h src/cli/cli.c src/cypher/cypher.c src/foundation/str_util.c src/foundation/str_util.h src/mcp/mcp.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_cli.c tests/test_cypher.c tests/test_extraction.c tests/test_infrascan.c tests/test_mcp.c tests/test_parse_coverage.c tests/test_pipeline.c tests/test_registry.c tests/test_str_util.c
f180 internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/service_patterns.c internal/cbm/service_patterns.h scripts/memory-core-baseline.txt src/cli/cli.c src/cypher/cypher.c src/foundation/str_util.c src/foundation/str_util.h src/mcp/mcp.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_cli.c tests/test_cypher.c tests/test_extraction.c tests/test_infrascan.c tests/test_mcp.c tests/test_parse_coverage.c tests/test_pipeline.c tests/test_registry.c tests/test_str_util.c
f181 src/discover/language.c tests/test_language.c
f182 .github/workflows/_memwaste.yml .github/workflows/dry-run.yml .github/workflows/nightly-soak.yml .github/workflows/pr.yml .github/workflows/release.yml Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_node_stack.h internal/cbm/lsp/go_lsp.c internal/cbm/result_compact.c internal/cbm/sqlite_writer.c scripts/fuzz.sh scripts/memlab-drive.py scripts/memlab-report.py scripts/memlab.sh scripts/memwaste-padding.py scripts/memwaste-ratchet.py scripts/memwaste-report.py scripts/memwaste-scaling.py scripts/memwaste.sh scripts/sanitizer-coverage-map.py scripts/sanitizer-map.json src/foundation/arena.c src/foundation/arena.h src/foundation/compat_thread.c src/foundation/dyn_array.h src/foundation/hash_table.c src/foundation/mem.c src/foundation/mem.h src/foundation/mem_core.c src/foundation/mem_events.c src/foundation/mem_events.h src/foundation/mem_override_darwin.c src/foundation/mem_override_libc.c src/foundation/mem_override_posix.c src/foundation/mem_override_win.c src/foundation/mem_profile.c src/foundation/mem_profile.h src/main.c src/pipeline/lsp_surface.c src/pipeline/pass_configlink.c src/pipeline/pass_k8s.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_lsp_cross.h src/pipeline/pass_parallel.c src/pipeline/pass_semantic.c src/pipeline/pass_semantic_edges.c src/pipeline/pass_similarity.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/registry.c src/pipeline/worker_pool.c src/semantic/semantic.c src/semantic/semantic.h src/simhash/minhash.c src/simhash/minhash.h tests/fuzz/cbm_fuzz.c tests/fuzz/corpus/config/json-empty.seed tests/fuzz/corpus/config/json.seed tests/fuzz/corpus/config/toml.seed tests/fuzz/corpus/config/yaml.seed tests/fuzz/corpus/cypher/agg.seed tests/fuzz/corpus/cypher/hops.seed tests/fuzz/corpus/cypher/match.seed tests/fuzz/corpus/cypher/optional.seed tests/fuzz/corpus/cypher/union.seed tests/fuzz/corpus/cypher/where.seed tests/fuzz/corpus/extract/c.seed tests/fuzz/corpus/extract/cpp.seed tests/fuzz/corpus/extract/csharp.seed tests/fuzz/corpus/extract/go.seed tests/fuzz/corpus/extract/java.seed tests/fuzz/corpus/extract/javascript.seed tests/fuzz/corpus/extract/makefile.seed tests/fuzz/corpus/extract/php.seed tests/fuzz/corpus/extract/python.seed tests/fuzz/corpus/extract/ruby.seed tests/fuzz/corpus/extract/rust.seed tests/fuzz/corpus/extract/sql.seed tests/fuzz/corpus/extract/typescript.seed tests/fuzz/corpus/extract/yaml.seed tests/test_arena.c tests/test_extraction.c tests/test_hash_table.c tests/test_main.c tests/test_mem.c tests/test_mem_events.c tests/test_registry.c tests/test_semantic.c tests/test_shell_line_endings.sh tests/test_simhash.c tests/test_venue_parity_contract.sh
f183 src/cli/cli.h src/main.c tests/test_cli.c tests/test_daemon_bootstrap.c
f184 src/cli/cli.h src/main.c tests/test_cli.c tests/test_daemon_bootstrap.c tests/test_mcp.c
f185 src/ui/layout3d.c tests/test_ui.c
f186 Makefile.cbm README.md src/daemon/application.c src/daemon/bootstrap.c src/foundation/mem_core.c src/foundation/mem_core.h src/main.c src/mcp/cache.c src/mcp/cache.h src/mcp/mcp.c tests/test_cache.c tests/test_cache_cli.py tests/test_daemon_application.c tests/test_daemon_bootstrap.c tests/test_main.c tests/test_mcp.c
f187 .github/workflows/_memwaste.yml .github/workflows/dry-run.yml .github/workflows/nightly-soak.yml .github/workflows/pr.yml .github/workflows/release.yml Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_node_stack.h internal/cbm/lsp/go_lsp.c internal/cbm/result_compact.c internal/cbm/result_spill.c internal/cbm/result_spill.h internal/cbm/sqlite_writer.c scripts/fuzz.sh scripts/memlab-drive.py scripts/memlab-report.py scripts/memlab.sh scripts/memory-core-baseline.txt scripts/memwaste-padding.py scripts/memwaste-ratchet.py scripts/memwaste-report.py scripts/memwaste-scaling.py scripts/memwaste.sh scripts/sanitizer-coverage-map.py scripts/sanitizer-map.json scripts/smoke-test.sh scripts/test.sh src/foundation/arena.c src/foundation/arena.h src/foundation/compat_thread.c src/foundation/dyn_array.h src/foundation/hash_table.c src/foundation/mem.c src/foundation/mem.h src/foundation/mem_core.c src/foundation/mem_events.c src/foundation/mem_events.h src/foundation/mem_override_darwin.c src/foundation/mem_override_libc.c src/foundation/mem_override_posix.c src/foundation/mem_override_win.c src/foundation/mem_profile.c src/foundation/mem_profile.h src/foundation/platform.h src/foundation/system_info.c src/graph_buffer/graph_buffer.c src/main.c src/pipeline/lsp_surface.c src/pipeline/pass_configlink.c src/pipeline/pass_k8s.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_lsp_cross.h src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pass_route_nodes.c src/pipeline/pass_semantic.c src/pipeline/pass_semantic_edges.c src/pipeline/pass_similarity.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/registry.c src/pipeline/worker_pool.c src/semantic/semantic.c src/semantic/semantic.h src/simhash/minhash.c src/simhash/minhash.h tests/fuzz/cbm_fuzz.c tests/fuzz/corpus/config/json-empty.seed tests/fuzz/corpus/config/json.seed tests/fuzz/corpus/config/toml.seed tests/fuzz/corpus/config/yaml.seed tests/fuzz/corpus/cypher/agg.seed tests/fuzz/corpus/cypher/hops.seed tests/fuzz/corpus/cypher/match.seed tests/fuzz/corpus/cypher/optional.seed tests/fuzz/corpus/cypher/union.seed tests/fuzz/corpus/cypher/where.seed tests/fuzz/corpus/extract/c.seed tests/fuzz/corpus/extract/cpp.seed tests/fuzz/corpus/extract/csharp.seed tests/fuzz/corpus/extract/go.seed tests/fuzz/corpus/extract/java.seed tests/fuzz/corpus/extract/javascript.seed tests/fuzz/corpus/extract/makefile.seed tests/fuzz/corpus/extract/php.seed tests/fuzz/corpus/extract/python.seed tests/fuzz/corpus/extract/ruby.seed tests/fuzz/corpus/extract/rust.seed tests/fuzz/corpus/extract/sql.seed tests/fuzz/corpus/extract/typescript.seed tests/fuzz/corpus/extract/yaml.seed tests/test_arena.c tests/test_extraction.c tests/test_hash_table.c tests/test_main.c tests/test_mem.c tests/test_mem_events.c tests/test_parallel.c tests/test_pipeline.c tests/test_registry.c tests/test_semantic.c tests/test_shell_line_endings.sh tests/test_simhash.c tests/test_thread_stack_tls_contract.sh tests/test_venue_parity_contract.sh
f188 .github/workflows/_memwaste.yml .github/workflows/dry-run.yml .github/workflows/nightly-soak.yml .github/workflows/pr.yml .github/workflows/release.yml Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_node_stack.h internal/cbm/extract_unified.c internal/cbm/extract_usages.c internal/cbm/lsp/go_lsp.c internal/cbm/result_compact.c internal/cbm/result_spill.c internal/cbm/result_spill.h internal/cbm/sqlite_writer.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/properties/scanner.c scripts/fuzz.sh scripts/memlab-drive.py scripts/memlab-report.py scripts/memlab.sh scripts/memory-core-baseline.txt scripts/memwaste-padding.py scripts/memwaste-ratchet.py scripts/memwaste-report.py scripts/memwaste-scaling.py scripts/memwaste.sh scripts/sanitizer-coverage-map.py scripts/sanitizer-map.json scripts/smoke-test.sh scripts/test.sh scripts/vendored-checksums.txt src/foundation/arena.c src/foundation/arena.h src/foundation/compat_thread.c src/foundation/dyn_array.h src/foundation/hash_table.c src/foundation/mem.c src/foundation/mem.h src/foundation/mem_core.c src/foundation/mem_events.c src/foundation/mem_events.h src/foundation/mem_override_darwin.c src/foundation/mem_override_libc.c src/foundation/mem_override_posix.c src/foundation/mem_override_win.c src/foundation/mem_profile.c src/foundation/mem_profile.h src/foundation/platform.h src/foundation/system_info.c src/graph_buffer/graph_buffer.c src/main.c src/pipeline/lsp_surface.c src/pipeline/pass_configlink.c src/pipeline/pass_k8s.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_lsp_cross.h src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pass_route_nodes.c src/pipeline/pass_semantic.c src/pipeline/pass_semantic_edges.c src/pipeline/pass_similarity.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/registry.c src/pipeline/worker_pool.c src/semantic/semantic.c src/semantic/semantic.h src/simhash/minhash.c src/simhash/minhash.h test-infrastructure/vm/vm-run-tests.sh tests/fuzz/cbm_fuzz.c tests/fuzz/corpus/config/json-empty.seed tests/fuzz/corpus/config/json.seed tests/fuzz/corpus/config/toml.seed tests/fuzz/corpus/config/yaml.seed tests/fuzz/corpus/cypher/agg.seed tests/fuzz/corpus/cypher/hops.seed tests/fuzz/corpus/cypher/match.seed tests/fuzz/corpus/cypher/optional.seed tests/fuzz/corpus/cypher/union.seed tests/fuzz/corpus/cypher/where.seed tests/fuzz/corpus/extract/c.seed tests/fuzz/corpus/extract/cpp.seed tests/fuzz/corpus/extract/csharp.seed tests/fuzz/corpus/extract/go.seed tests/fuzz/corpus/extract/java.seed tests/fuzz/corpus/extract/javascript.seed tests/fuzz/corpus/extract/makefile.seed tests/fuzz/corpus/extract/php.seed tests/fuzz/corpus/extract/python.seed tests/fuzz/corpus/extract/ruby.seed tests/fuzz/corpus/extract/rust.seed tests/fuzz/corpus/extract/sql.seed tests/fuzz/corpus/extract/typescript.seed tests/fuzz/corpus/extract/yaml.seed tests/test_arena.c tests/test_extraction.c tests/test_hash_table.c tests/test_main.c tests/test_mem.c tests/test_mem_events.c tests/test_parallel.c tests/test_pipeline.c tests/test_registry.c tests/test_semantic.c tests/test_shell_line_endings.sh tests/test_simhash.c tests/test_thread_stack_tls_contract.sh tests/test_venue_parity_contract.sh tests/test_vm_verdict_contract.sh
f189 internal/cbm/extract_calls.c internal/cbm/lsp/ts_lsp.c tests/test_extraction.c tests/test_ts_lsp.c
f190 .github/workflows/pr.yml Makefile.cbm README.md src/cli/cache.c src/cli/cache.h src/daemon/bootstrap.c src/daemon/version_cohort.c src/daemon/version_cohort.h src/foundation/mem_core.c src/foundation/mem_core.h src/main.c tests/test_cache.c tests/test_cache_cli.py tests/test_daemon_bootstrap.c tests/test_main.c tests/test_mcp.c tests/test_version_cohort.c
f191 .github/workflows/pr.yml Makefile.cbm README.md pkg/pypi/tests/test_cli.py src/cli/cache.c src/cli/cache.h src/daemon/bootstrap.c src/daemon/version_cohort.c src/daemon/version_cohort.h src/foundation/mem_core.c src/foundation/mem_core.h src/main.c tests/test_cache.c tests/test_cache_cli.py tests/test_daemon_bootstrap.c tests/test_main.c tests/test_mcp.c tests/test_version_cohort.c
f192 install.sh tests/test-install-android-guard.sh
f193 src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_pipeline.c tests/test_registry.c
f194 internal/cbm/vendored/grammars/sql/parser.c internal/cbm/vendored/grammars/tsx/parser.c scripts/vendored-checksums.txt tests/test_parse_coverage.c
f195 internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/sql/parser.c internal/cbm/vendored/grammars/tsx/parser.c scripts/vendored-checksums.txt tests/test_parse_coverage.c
f196 src/mcp/mcp.c src/pipeline/pipeline.c src/pipeline/pipeline.h tests/test_pipeline.c
f197 .github/workflows/_lint.yml .github/workflows/_test.yml .github/workflows/release.yml
f198 internal/cbm/lang_specs.c tests/test_extraction.c
f199 install.ps1
f200 internal/cbm/lsp/py_lsp.c internal/cbm/lsp/scope.c internal/cbm/lsp/scope.h tests/test_scope.c
f201 src/cypher/cypher.c src/foundation/recursion_whitelist.h tests/test_cypher.c
f202 internal/cbm/service_patterns.c tests/test_infrascan.c tests/test_pipeline.c
f203 scripts/memory-core-baseline.txt src/mcp/mcp.c
f204 internal/cbm/service_patterns.c tests/test_pipeline.c
f205 src/foundation/compat_thread.c
f206 CONTRIBUTING.md README.md
f207 README.md docs/WINDOWS_TROUBLESHOOTING.md
f208 scripts/memory-core-baseline.txt src/mcp/mcp.c tests/test_worker_session_scope.sh
f209 .github/workflows/smoke.yml scripts/smoke-invariants.sh
f210 internal/cbm/cbm.c internal/cbm/preprocessor.cpp internal/cbm/preprocessor.h src/mcp/mcp.c tests/test_extraction.c
f211 README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/daemon/application.c src/foundation/index_policy.c src/foundation/index_policy.h src/foundation/subprocess.c src/foundation/subprocess.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c tests/test_daemon_application.c tests/test_index_policy.c tests/test_index_supervisor.c tests/test_mcp.c tests/test_subprocess.c
f212 docs/EVALUATION_PLAN.md scripts/benchmark-index.sh scripts/benchmark-search-graph.sh scripts/test.sh tests/test_benchmark_runtime_isolation_contract.sh
f213 Makefile.cbm src/foundation/compat.c src/foundation/compat.h src/foundation/limits.h src/foundation/mem_override_win.c src/foundation/source_encoding.c src/foundation/source_encoding.h src/mcp/index_supervisor.c src/mcp/mcp.c src/pipeline/pass_calls.c src/pipeline/pass_compile_commands.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_semantic.c src/pipeline/pass_usages.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_pipeline.c tests/test_platform.c
f214 README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/daemon/application.c src/foundation/index_policy.c src/foundation/index_policy.h src/foundation/subprocess.c src/foundation/subprocess.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c tests/test_daemon_application.c tests/test_index_policy.c tests/test_index_supervisor.c tests/test_mcp.c tests/test_subprocess.c tests/test_worker_session_scope.sh
f215 .github/workflows/pr.yml
f216 CONTRIBUTING.md
f217 README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/daemon/application.c src/daemon/application.h src/foundation/index_policy.c src/foundation/index_policy.h src/foundation/subprocess.c src/foundation/subprocess.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c tests/test_daemon_application.c tests/test_index_policy.c tests/test_index_supervisor.c tests/test_mcp.c tests/test_subprocess.c tests/test_worker_session_scope.sh
f218 tests/test_daemon_bootstrap.c
f219 internal/cbm/service_patterns.c tests/test_infrascan.c
f220 Makefile.cbm src/cli/cli.c src/pipeline/pass_doclinks.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_doclinks.c tests/test_edge_structural.c tests/test_lang_contract.c tests/test_main.c tests/test_pipeline.c
f221 Makefile.cbm README.md src/cli/cli.c src/pipeline/pass_doclinks.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_doclinks.c tests/test_edge_structural.c tests/test_lang_contract.c tests/test_main.c tests/test_pipeline.c
f222 src/daemon/application.c src/mcp/mcp.c src/pipeline/lsp_surface.c src/pipeline/pipeline.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_daemon_application.c tests/test_mcp.c tests/test_pipeline.c
f223 README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/daemon/application.c src/daemon/application.h src/foundation/index_policy.c src/foundation/index_policy.h src/foundation/subprocess.c src/foundation/subprocess.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c tests/test_daemon_application.c tests/test_daemon_bootstrap.c tests/test_index_policy.c tests/test_index_supervisor.c tests/test_mcp.c tests/test_subprocess.c tests/test_worker_session_scope.sh
f224 internal/cbm/lsp/ts_lsp.c internal/cbm/lsp/ts_lsp.h tests/test_stack_overflow.c
f225 src/foundation/mem_override_win.c
f226 src/foundation/compat.c src/mcp/index_supervisor.c src/mcp/mcp.c tests/test_platform.c
f227 src/pipeline/lsp_surface.c tests/test_pipeline.c
f228 src/daemon/host.c src/daemon/ipc.c src/daemon/ipc.h src/daemon/runtime.c src/daemon/runtime.h src/daemon/version_cohort.c src/daemon/version_cohort.h src/foundation/private_file_lock.c src/foundation/private_file_lock_internal.h tests/test_daemon_ipc.c tests/test_private_file_lock.c tests/test_version_cohort.c
f229 src/foundation/compat.h tests/test_platform.c
f230 README.md docs/CONFIGURATION.md src/cli/cli.c src/cli/cli.h src/cli/hook_augment.c src/daemon/application.c src/git/git_context.c src/git/git_context.h src/mcp/mcp.c src/mcp/mcp_internal.h tests/test_cli.c tests/test_git_context.c tests/test_mcp.c
f231 internal/cbm/extract_defs.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_call_reference_contract.c tests/test_registry.c
f232 internal/cbm/extract_calls.c internal/cbm/extract_unified.c internal/cbm/extract_unified.h tests/repro/repro_call_scope_usages.c tests/test_convergence_probe.c
f233 pkg/scoop/codebase-memory-mcp.json
f234 src/mcp/mcp.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/store/store.c src/store/store.h tests/test_mcp.c tests/test_pipeline.c
f235 internal/cbm/extract_defs.c internal/cbm/helpers.c internal/cbm/helpers.h tests/test_extraction.c
f236 src/pipeline/registry.c tests/test_pipeline.c tests/test_registry.c
f237 internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_unified.c internal/cbm/extract_usages.c internal/cbm/helpers.c internal/cbm/helpers.h internal/cbm/result_compact.c src/foundation/constants.h src/foundation/recursion_whitelist.h src/mcp/mcp.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_usages.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/registry.c src/store/store.c src/store/store.h tests/repro/repro_call_argument_matrix_a.c tests/repro/repro_call_node_behaviors.c tests/test_extraction.c tests/test_mcp.c tests/test_pipeline.c tests/test_registry.c tests/test_store_nodes.c
f238 src/foundation/compat_fs.c src/foundation/compat_fs.h src/mcp/mcp.c src/pipeline/pipeline.c tests/test_pipeline.c
f239 src/mcp/mcp.c src/pipeline/pass_cross_repo.c src/pipeline/pass_cross_repo.h tests/test_cli.c tests/test_cross_repo.c
f240 src/pipeline/pass_cross_repo.c tests/test_cross_repo.c
f241 internal/cbm/extract_defs.c internal/cbm/lang_specs.c tests/test_extraction.c
f242 docs/CONFIGURATION.md src/daemon/ipc.c src/daemon/ipc.h tests/test_daemon_ipc.c
f243 docs/CONFIGURATION.md src/daemon/ipc.c src/daemon/ipc.h src/main.c tests/test_daemon_ipc.c
f244 src/cli/config_toml_edit.c src/cli/config_toml_edit.h tests/test_cli.c tests/test_config_toml_edit.c
f245 docs/cbmignore.md src/discover/discover.c tests/test_discover.c
f246 Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/sql_values.c internal/cbm/sql_values.h tests/test_extraction.c tests/test_parse_coverage.c tests/test_pipeline.c
f247 THIRD_PARTY.md internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/javascript/parser.c internal/cbm/vendored/grammars/tsx/parser.c internal/cbm/vendored/grammars/tsx/tree_sitter/array.h internal/cbm/vendored/grammars/tsx/tree_sitter/parser.h scripts/audit-license-provenance.py scripts/vendored-checksums.txt tests/test_parse_coverage.c tools/tree-sitter-javascript/grammar.js tools/tree-sitter-javascript/package.json tools/tree-sitter-javascript/patches/jsx-lone-ampersand.patch tools/tree-sitter-javascript/src/grammar.json tools/tree-sitter-javascript/src/node-types.json tools/tree-sitter-javascript/src/parser.c tools/tree-sitter-javascript/src/scanner.c tools/tree-sitter-javascript/src/tree_sitter/alloc.h tools/tree-sitter-javascript/src/tree_sitter/array.h tools/tree-sitter-javascript/src/tree_sitter/parser.h tools/tree-sitter-javascript/test/corpus/jsx_ampersand.txt tools/tree-sitter-javascript/tree-sitter.json tools/tree-sitter-tsx/define-grammar.js tools/tree-sitter-tsx/grammar.js tools/tree-sitter-tsx/javascript-grammar.js tools/tree-sitter-tsx/package.json tools/tree-sitter-tsx/src/_common_scanner.h tools/tree-sitter-tsx/src/grammar.json tools/tree-sitter-tsx/src/node-types.json tools/tree-sitter-tsx/src/parser.c tools/tree-sitter-tsx/src/scanner.c tools/tree-sitter-tsx/src/tree_sitter/alloc.h tools/tree-sitter-tsx/src/tree_sitter/array.h tools/tree-sitter-tsx/src/tree_sitter/parser.h tools/tree-sitter-tsx/test/corpus/jsx_ampersand.txt tools/tree-sitter-tsx/tree-sitter.json
f248 internal/cbm/lsp/c_lsp.c internal/cbm/lsp/ts_lsp.c internal/cbm/lsp/ts_lsp.h tests/test_c_lsp.c tests/test_ts_lsp.c
f249 internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/c_sharp/parser.c internal/cbm/vendored/grammars/c_sharp/scanner.c internal/cbm/vendored/grammars/c_sharp/tree_sitter/alloc.h internal/cbm/vendored/grammars/c_sharp/tree_sitter/array.h internal/cbm/vendored/grammars/c_sharp/tree_sitter/parser.h scripts/vendored-checksums.txt tests/test_parse_coverage.c
f250 internal/cbm/sqlite_writer.c tests/test_sqlite_writer.c
f251 src/mcp/mcp.c src/pipeline/fqn.c src/pipeline/pipeline.h tests/test_mcp.c
f252 internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/result_compact.c src/pipeline/lsp_surface.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline.c src/pipeline/pipeline_delta.c src/pipeline/pipeline_internal.h tests/test_extraction.c tests/test_pipeline.c
f253 README.md docs/CONFIGURATION.md src/cli/cli.c src/cli/cli.h src/daemon/host.c src/discover/discover.c src/discover/discover.h src/watcher/watcher.c src/watcher/watcher.h tests/test_cli.c tests/test_watcher.c
f254 src/daemon/runtime.c src/daemon/runtime.h tests/test_daemon_runtime.c
f255 internal/cbm/cbm.c internal/cbm/cbm.h tests/test_parse_coverage.c
f256 scripts/test.sh tests/repro/repro_main.c tests/test_git_env_isolation_contract.sh tests/test_helpers.h tests/test_main.c
f257 src/cli/config_toml_edit.c tests/test_config_toml_edit.c
f258 internal/cbm/lsp/generated/rust_stdlib_data.c src/pipeline/lsp_resolve.h src/pipeline/pass_calls.c src/pipeline/pass_parallel.c tests/test_parallel.c tests/test_pipeline.c
f259 internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/lsp/c_lsp.c internal/cbm/lsp/cs_lsp.c internal/cbm/lsp/go_lsp.c internal/cbm/lsp/java_lsp.c internal/cbm/lsp/kotlin_lsp.c internal/cbm/lsp/php_lsp.c internal/cbm/lsp/py_lsp.c internal/cbm/lsp/rust_lsp.c internal/cbm/lsp/ts_lsp.c tests/test_parse_coverage.c
f260 Makefile.cbm src/foundation/compat_fs.c src/foundation/compat_fs.h src/foundation/git_env.c src/foundation/git_env.h src/foundation/subprocess.c src/foundation/subprocess.h src/git/git_context.c src/mcp/mcp.c src/pipeline/artifact.c src/pipeline/pass_githistory.c src/watcher/watcher.c tests/test_git_context.c tests/test_subprocess.c
f261 Makefile.cbm internal/cbm/callable_sig.c internal/cbm/callable_sig.h internal/cbm/cbm.h internal/cbm/lsp/go_lsp.h internal/cbm/lsp/type_registry.h internal/cbm/result_compact.c scripts/memory-core-baseline.txt src/mcp/mcp.c src/pipeline/lsp_resolve.h src/pipeline/pass_ensemble_routing.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_semantic.c src/pipeline/registry.c src/store/store.c src/store/store.h tests/test_callable_sig.c tests/test_main.c tests/test_store_nodes.c
f262 src/pipeline/pass_cross_repo.c src/store/store.c src/store/store.h tests/test_cross_repo.c
f263 src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline.h src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_edge_imports.c tests/test_registry.c
f264 internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/lsp/cs_lsp.c internal/cbm/lsp/type_registry.h internal/cbm/result_compact.c src/pipeline/pass_lsp_cross.c src/pipeline/pipeline.c tests/test_parallel.c
f265 README.md docs/CONFIGURATION.md docs/index.html src/daemon/application.c src/mcp/mcp.c src/mcp/mcp.h src/watcher/watcher.c src/watcher/watcher.h tests/test_daemon_application.c tests/test_watcher.c
f266 src/daemon/host.c src/daemon/host_internal.h src/daemon/ipc.c src/daemon/ipc.h src/daemon/runtime.c src/daemon/runtime.h src/daemon/version_cohort.c src/daemon/version_cohort.h src/foundation/private_file_lock.c src/foundation/private_file_lock_internal.h tests/test_daemon_ipc.c tests/test_daemon_runtime.c tests/test_private_file_lock.c tests/test_version_cohort.c
f267 src/mcp/mcp.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/store/store.c tests/test_pipeline.c
f268 README.md docs/CONFIGURATION.md
f269 internal/cbm/extract_usages.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/rescript/scanner.c src/discover/discover.c src/discover/discover.h src/discover/language.c tests/test_complexity.c tests/test_extraction.c tests/test_language.c
f270 internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/result_compact.c scripts/memory-core-baseline.txt src/pipeline/lsp_surface.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline.c src/pipeline/pipeline_delta.c src/pipeline/pipeline_internal.h tests/test_extraction.c tests/test_pipeline.c
f271 THIRD_PARTY.md internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/pascal/parser.c internal/cbm/vendored/grammars/pascal/tree_sitter/parser.h scripts/audit-license-provenance.py scripts/vendored-checksums.txt tests/test_parse_coverage.c tools/tree-sitter-pascal/grammar.js tools/tree-sitter-pascal/package.json tools/tree-sitter-pascal/patches/unicode-identifiers.patch tools/tree-sitter-pascal/src/grammar.json tools/tree-sitter-pascal/src/node-types.json tools/tree-sitter-pascal/src/parser.c tools/tree-sitter-pascal/src/tree_sitter/alloc.h tools/tree-sitter-pascal/src/tree_sitter/array.h tools/tree-sitter-pascal/src/tree_sitter/parser.h tools/tree-sitter-pascal/test/corpus/unicode_identifiers.txt tools/tree-sitter-pascal/tree-sitter.json
f272 README.md src/daemon/application.c src/daemon/frontend.c src/daemon/frontend.h src/mcp/mcp.c src/mcp/mcp.h src/ui/http_server.c tests/test_daemon_application.c tests/test_daemon_frontend.c tests/test_mcp.c
f273 internal/cbm/cbm.h src/pipeline/pipeline_internal.h
f274 pkg/scoop/codebase-memory-mcp.json tests/test_version_metadata_contract.sh
f275 README.md scripts/memory-core-baseline.txt src/cli/cli.c src/mcp/mcp.c src/pipeline/artifact.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/store/store.c src/store/store.h tests/test_artifact.c tests/test_mcp.c tests/test_pipeline.c
f276 internal/cbm/extract_imports.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline_internal.h tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c
f277 internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/helpers.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
f278 src/foundation/compat.c
f279 internal/cbm/cbm.c src/cli/cli.c src/foundation/compat.h tests/test_extraction.c tests/test_platform.c
f280 src/mcp/mcp.c src/pipeline/pipeline.h src/pipeline/pipeline_internal.h tests/test_mcp.c
f281 internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/helpers.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline.h src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
f282 THIRD_PARTY.md internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_usages.c internal/cbm/helpers.c internal/cbm/helpers.h internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/objectscript_routine/objectscript_common.h internal/cbm/vendored/grammars/objectscript_routine/parser.c internal/cbm/vendored/grammars/objectscript_routine/scanner.c internal/cbm/vendored/grammars/objectscript_routine/tree_sitter/array.h internal/cbm/vendored/grammars/objectscript_udl/objectscript_common.h internal/cbm/vendored/grammars/objectscript_udl/parser.c internal/cbm/vendored/grammars/objectscript_udl/scanner.c internal/cbm/vendored/grammars/objectscript_udl/tree_sitter/array.h scripts/vendored-checksums.txt tests/test_extraction.c
f283 internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_unified.c internal/cbm/extract_usages.c internal/cbm/helpers.c internal/cbm/helpers.h tests/test_extraction.c
f284 src/daemon/application.c src/foundation/log.c src/foundation/log.h src/mcp/mcp.c src/ui/http_server.c tests/test_log.c tests/test_mcp.c
f285 internal/cbm/extract_defs.c scripts/memory-core-baseline.txt tests/test_extraction.c
f286 README.md docs/CONFIGURATION.md scripts/test.sh src/main.c tests/test_in_process_mcp.py tests/test_in_process_mcp.sh tests/test_runtime_isolation_contract.sh
f287 internal/cbm/extract_imports.c scripts/memory-core-baseline.txt src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline_internal.h tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c
f288 internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/helpers.c scripts/memory-core-baseline.txt src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
f289 internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/helpers.c scripts/memory-core-baseline.txt src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline.h src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
f290 src/cli/client_adapter.c src/cli/client_adapter.h tests/test_agent_clients.c
f291 internal/cbm/extract_usages.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/rescript/scanner.c scripts/vendored-checksums.txt src/discover/discover.c src/discover/discover.h src/discover/language.c tests/test_complexity.c tests/test_extraction.c tests/test_language.c
f292 Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/lsp/php_lsp.c internal/cbm/php_inline_html.c tests/test_extraction.c tests/test_pipeline.c
f293 Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/lsp/php_lsp.c internal/cbm/php_inline_html.c tests/test_extraction.c tests/test_php_lsp.c tests/test_pipeline.c
f294 src/daemon/bootstrap.h src/daemon/runtime.c src/daemon/runtime.h src/main.c tests/test_daemon_version.c
f295 scripts/memory-core-baseline.txt src/cli/agent_profiles.c src/cli/agent_profiles.h src/cli/cli.c tests/test_cli.c
f296 src/foundation/mem.c src/foundation/mem.h src/pipeline/pass_parallel.c tests/test_parallel.c
f297 internal/cbm/extract_defs.c internal/cbm/helpers.c internal/cbm/lang_specs.c tests/test_extraction.c
f298 src/mcp/mcp.c
f299 README.md THIRD_PARTY.md docs/index.html docs/llms.txt internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_usages.c internal/cbm/grammar_st.c internal/cbm/lang_specs.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/st/LICENSE internal/cbm/vendored/grammars/st/parser.c internal/cbm/vendored/grammars/st/scanner.c internal/cbm/vendored/grammars/st/tree_sitter/alloc.h internal/cbm/vendored/grammars/st/tree_sitter/array.h internal/cbm/vendored/grammars/st/tree_sitter/parser.h pkg/chocolatey/codebase-memory-mcp.nuspec pkg/npm/README.md scripts/new-languages.json scripts/package-release.sh scripts/vendored-checksums.txt server.json src/discover/language.c src/discover/userconfig.c src/mcp/mcp.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_manifest.c tests/repro/repro_language_registry.c tests/test_extraction.c tests/test_grammar_labels.c tests/test_grammar_regression.c tests/test_language.c
f300 README.md scripts/memory-core-baseline.txt src/cli/cli.c src/mcp/mcp.c src/pipeline/artifact.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/store/store.c src/store/store.h tests/test_artifact.c tests/test_mcp.c tests/test_pipeline.c tests/test_store_arch.c
f301 internal/cbm/cbm.h src/mcp/mcp.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/store/store.c src/store/store.h tests/test_mcp.c tests/test_pipeline.c
f302 THIRD_PARTY.md internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_usages.c internal/cbm/helpers.c internal/cbm/helpers.h internal/cbm/lang_specs.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/objectscript_routine/objectscript_common.h internal/cbm/vendored/grammars/objectscript_routine/parser.c internal/cbm/vendored/grammars/objectscript_routine/scanner.c internal/cbm/vendored/grammars/objectscript_routine/tree_sitter/array.h internal/cbm/vendored/grammars/objectscript_udl/objectscript_common.h internal/cbm/vendored/grammars/objectscript_udl/parser.c internal/cbm/vendored/grammars/objectscript_udl/scanner.c internal/cbm/vendored/grammars/objectscript_udl/tree_sitter/array.h scripts/vendored-checksums.txt tests/repro/repro_call_node_behaviors.c tests/repro/repro_call_node_manifest.c tests/test_extraction.c
f303 internal/cbm/lsp/ts_lsp.c tests/repro/repro_issue514.c tests/test_ts_lsp.c
f304 src/cli/cli.c src/cli/cli.h src/foundation/str_util.c src/foundation/str_util.h src/pipeline/pass_cross_repo.c tests/test_cli.c
f305 internal/cbm/result_spill.c internal/cbm/result_spill.h tests/test_extraction.c tests/test_main.c
f306 internal/cbm/lsp/ts_lsp.c tests/test_pipeline.c tests/test_ts_lsp.c
f307 tests/test_watcher.c
f308 tests/test_daemon_ipc.c
f309 src/daemon/bootstrap.c src/daemon/bootstrap.h tests/repro/repro_main.c tests/test_daemon_bootstrap.c tests/test_helpers.h tests/test_main.c
f310 src/cli/cli.c src/cli/cli.h tests/test_cli.c
f311 internal/cbm/extract_imports.c scripts/memory-core-baseline.txt src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c
f312 src/pipeline/pass_definitions.c tests/test_parallel.c
f313 internal/cbm/extract_usages.c tests/repro/repro_lexical_binding_precision.c tests/test_extraction.c
f314 internal/cbm/lsp/py_lsp.c tests/test_py_lsp.c tests/test_py_lsp_scale.c
f315 src/discover/discover.c src/discover/discover.h src/discover/language.c tests/test_discover.c tests/test_language.c
f316 internal/cbm/lsp/py_lsp.c internal/cbm/lsp/py_lsp.h tests/test_py_lsp.c
f317 internal/cbm/extract_defs.c src/store/store.h tests/test_extraction.c
f318 scripts/memory-core-baseline.txt src/cli/activation_transaction.c src/cli/activation_transaction.h tests/test_activation_transaction.c
f319 internal/cbm/lsp/py_lsp.c src/pipeline/lsp_resolve.h tests/repro/repro_lsp_go_py.c tests/test_edge_structural.c tests/test_lsp_resolution_probe.c tests/test_py_lsp.c
f320 internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/helpers.c scripts/memory-core-baseline.txt src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
f321 internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/helpers.c scripts/memory-core-baseline.txt src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
f322 src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_tests.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_pipeline.c tests/test_registry.c
f323 src/pipeline/pass_calls.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_parallel.c src/pipeline/pass_semantic.c src/pipeline/pass_usages.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_pipeline.c
1 # Distinct changed-file sets of the September 2026 PR pushes (git diff
2 # --name-only merge-base..head per push), referenced by 2026-09-pushes.tsv.
3 # Columns (TAB): fileset, paths (space-separated; no path contains a space).
4 f001 README.md
5 f002 internal/cbm/vendored/grammars/swift/scanner.c scripts/vendored-checksums.txt tests/test_extraction.c
6 f003 internal/cbm/lsp/go_lsp.c src/pipeline/pass_lsp_cross.c tests/test_go_lsp.c tests/test_parallel.c
7 f004 src/foundation/mem.c src/foundation/mem.h tests/test_mem.c
8 f005 Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h src/mcp/mcp.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline_internal.h src/store/store.c tests/test_index_resilience.c tests/test_mcp.c tests/test_parse_coverage.c tests/test_pipeline.c
9 f006 .github/workflows/pr.yml Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h scripts/ci/README.md scripts/ci/coverage-gate-allowlist.txt scripts/ci/parse-partial-baseline.txt scripts/ci/self-index-coverage-gate.sh src/mcp/mcp.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline_internal.h src/store/store.c tests/test_index_resilience.c tests/test_mcp.c tests/test_parse_coverage.c tests/test_pipeline.c tests/test_venue_parity_contract.sh
10 f007 README.md scripts/smoke-test.sh src/cli/cli.c tests/test_cli.c
11 f008 src/ui/http_server.c tests/test_httpd.c
12 f009 src/daemon/application.c src/daemon/application.h src/daemon/frontend.c src/daemon/frontend.h src/daemon/runtime.c src/daemon/runtime.h src/main.c tests/test_daemon_frontend.c tests/test_daemon_runtime.c
13 f010 internal/cbm/lsp/c_lsp.c internal/cbm/lsp/type_registry.c internal/cbm/lsp/type_registry.h tests/test_c_lsp.c
14 f011 src/pipeline/fqn.c src/pipeline/pass_pkgmap.c tests/test_edge_imports.c tests/test_fqn.c
15 f012 internal/cbm/extract_calls.c tests/test_extraction.c tests/test_pipeline.c
16 f013 src/mcp/mcp.c tests/test_mcp.c
17 f014 src/cypher/cypher.c tests/test_cypher.c
18 f015 src/mcp/mcp.c src/store/store.c src/store/store.h src/ui/layout3d.c src/ui/layout3d.h tests/test_store_arch.c tests/test_ui.c
19 f016 Makefile.cbm README.md docs/index.html docs/llms.txt scripts/smoke-invariants.sh src/cli/cli.c src/cli/cli.h src/foundation/platform.c src/foundation/platform.h src/mcp/mcp.c src/personal_memory/memory.c src/personal_memory/memory.h tests/test_cli.c tests/test_mcp.c
20 f017 internal/cbm/extract_defs.c tests/test_extraction.c
21 f018 internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/swift/scanner.c scripts/vendored-checksums.txt tests/test_extraction.c
22 f019 internal/cbm/extract_calls.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/swift/scanner.c scripts/vendored-checksums.txt tests/test_extraction.c tests/test_pipeline.c
23 f020 src/daemon/application.c tests/test_daemon_application.c tests/windows/test_non_ascii_path.py
24 f021 internal/cbm/lsp/ts_lsp.c tests/repro/repro_lsp_ts.c
25 f022 src/graph_buffer/graph_buffer.c src/mcp/mcp.c tests/test_graph_buffer.c tests/test_mcp.c
26 f023 internal/cbm/cbm.h internal/cbm/extract_calls.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
27 f024 .github/workflows/pr.yml Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h scripts/ci/README.md scripts/ci/coverage-gate-allowlist.txt scripts/ci/parse-partial-baseline.txt scripts/ci/self-index-coverage-gate.sh scripts/test.sh src/mcp/mcp.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline_internal.h src/store/store.c tests/test_coverage_gate_contract.sh tests/test_index_resilience.c tests/test_mcp.c tests/test_parse_coverage.c tests/test_pipeline.c tests/test_venue_parity_contract.sh
28 f025 Makefile.cbm README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/cli/cli.h src/daemon/application.c src/discover/discover.c src/discover/discover.h src/foundation/index_policy.c src/foundation/index_policy.h src/mcp/mcp.c src/mcp/mcp_internal.h src/pipeline/pipeline.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_daemon_application.c tests/test_discover.c tests/test_index_policy.c tests/test_main.c tests/test_pipeline.c tests/test_worker_error_response.sh tests/test_worker_watchdog.sh
29 f026 Makefile.cbm src/cli/cli.c src/pipeline/pass_doclinks.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_doclinks.c tests/test_edge_structural.c tests/test_lang_contract.c tests/test_main.c
30 f027 src/mcp/mcp.c src/mcp/mcp_internal.h tests/test_mcp.c
31 f028 internal/cbm/extract_defs.c src/discover/language.c src/pipeline/pass_route_nodes.c tests/test_extraction.c tests/test_language.c
32 f029 README.md docs/CONFIGURATION.md scripts/smoke-test.sh src/cli/cli.c src/cli/cli.h src/main.c tests/test_cli.c
33 f030 src/main.c tests/test_parent_watchdog.sh
34 f031 internal/cbm/extract_imports.c tests/test_extraction.c
35 f032 internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_unified.c internal/cbm/helpers.h src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
36 f033 internal/cbm/extract_calls.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
37 f034 internal/cbm/cbm.c internal/cbm/preprocessor.cpp internal/cbm/preprocessor.h tests/test_extraction.c
38 f035 internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_unified.c internal/cbm/helpers.c internal/cbm/lsp/type_registry.c src/pipeline/lsp_resolve.h src/pipeline/registry.c tests/test_extraction.c tests/test_pipeline.c
39 f036 internal/cbm/cbm.h internal/cbm/extract_semantic.c internal/cbm/extract_usages.c src/pipeline/pass_parallel.c src/pipeline/pass_usages.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_pipeline.c tests/test_registry.c
40 f037 internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_defs.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
41 f038 internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_unified.c internal/cbm/extract_unified.h src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
42 f039 src/cli/cli.c src/ui/http_server.c tests/test_cli.c
43 f040 internal/cbm/extract_channels.c tests/test_extraction.c tests/test_pipeline.c
44 f041 internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_unified.c internal/cbm/helpers.h internal/cbm/lsp/go_lsp.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_extraction.c tests/test_parallel.c tests/test_pipeline.c tests/test_registry.c
45 f042 internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_unified.c internal/cbm/helpers.c internal/cbm/helpers.h internal/cbm/lsp/go_lsp.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_extraction.c tests/test_parallel.c tests/test_pipeline.c tests/test_registry.c
46 f043 internal/cbm/extract_defs.c tests/test_edge_types_probe.c
47 f044 internal/cbm/extract_calls.c tests/test_extraction.c
48 f045 internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_channels.c internal/cbm/extract_defs.c internal/cbm/extract_env_accesses.c internal/cbm/extract_imports.c internal/cbm/extract_node_stack.h internal/cbm/extract_semantic.c internal/cbm/extract_type_assigns.c internal/cbm/extract_type_refs.c tests/test_extraction.c tests/test_mem.c
49 f046 src/cli/client_adapter.c tests/test_agent_clients.c
50 f047 graph-ui/src/hooks/useProjects.test.tsx graph-ui/src/hooks/useProjects.ts
51 f048 internal/cbm/cbm.c tests/test_parse_coverage.c
52 f049 graph-ui/src/App.test.tsx graph-ui/src/App.tsx src/ui/http_server.c tests/test_httpd.c
53 f050 src/daemon/application.c src/foundation/workspace.c src/foundation/workspace.h src/mcp/mcp.c src/ui/http_server.c tests/test_workspace.c
54 f051 README.md docs/CONFIGURATION.md src/cli/cli.c src/cli/cli.h src/daemon/ipc.c src/foundation/compat_fs.c src/foundation/compat_fs.h src/main.c tests/test_daemon_ipc.c
55 f052 src/traces/traces.c src/traces/traces.h tests/test_traces.c
56 f053 src/pipeline/registry.c tests/test_pipeline.c
57 f054 src/cli/cli.h src/cli/hook_augment.c src/foundation/platform.c src/foundation/platform.h src/mcp/index_supervisor.c src/mcp/mcp.c src/mcp/mcp.h tests/test_cli.c tests/test_platform.c
58 f055 tests/test_cli.c
59 f056 src/daemon/application.c src/mcp/mcp.c src/pipeline/pipeline.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c tests/test_daemon_application.c tests/test_mcp.c tests/test_pipeline.c
60 f057 internal/cbm/extract_defs.c src/discover/language.c tests/test_extraction.c tests/test_language.c
61 f058 src/cli/hook_augment.c src/daemon/runtime.c src/daemon/runtime.h src/foundation/sha256.c src/foundation/sha256.h src/main.c tests/test_cli.c tests/test_daemon_runtime.c
62 f059 tests/windows/test_cli_non_ascii_arg.py tests/windows/test_daemon_stability.py tests/windows/test_hook_augment.py
63 f060 src/cli/cli.c
64 f061 .github/workflows/_test.yml
65 f062 scripts/smoke-test.sh
66 f063 src/foundation/subprocess.c src/foundation/subprocess.h src/mcp/index_supervisor.c tests/test_main.c tests/test_subprocess.c
67 f064 graph-ui/src/components/EdgeLines.test.ts graph-ui/src/components/EdgeLines.tsx graph-ui/src/components/GraphTab.edgeSampling.test.tsx graph-ui/src/components/GraphTab.test.ts graph-ui/src/components/GraphTab.tsx graph-ui/src/components/NodeCloud.test.ts graph-ui/src/components/NodeCloud.tsx graph-ui/src/components/NodeDetailPanel.test.tsx graph-ui/src/components/NodeDetailPanel.tsx graph-ui/src/components/NodeLabels.tsx graph-ui/src/hooks/useGraphData.test.ts graph-ui/src/hooks/useGraphData.ts graph-ui/src/lib/edgeBudget.test.ts graph-ui/src/lib/edgeBudget.ts graph-ui/src/lib/graphIndex.test.ts graph-ui/src/lib/graphIndex.ts graph-ui/src/lib/topK.test.ts graph-ui/src/lib/topK.ts src/store/store.c src/store/store.h src/ui/http_server.c src/ui/layout3d.c src/ui/layout3d.h src/ui/layout3d_internal.h tests/test_httpd.c tests/test_store_edges.c tests/test_store_search.c tests/test_ui.c
68 f065 graph-ui/package-lock.json
69 f066 src/foundation/lock_registry.c src/foundation/lock_registry_internal.h tests/test_lock_registry.c
70 f067 src/daemon/version_cohort.c src/daemon/version_cohort.h tests/test_version_cohort.c
71 f068 internal/cbm/extract_defs.c tests/test_extraction_inheritance.c
72 f069 internal/cbm/extract_channels.c tests/test_extraction.c
73 f070 test-infrastructure/Dockerfile.msan
74 f071 src/git/git_context.c src/ui/http_server.c tests/test_daemon_application.c tests/test_git_context.c tests/test_httpd.c
75 f072 scripts/hooks/pre-commit
76 f073 tests/test_daemon_runtime.c
77 f074 src/cli/agent_profiles.c src/cli/client_adapter.c tests/test_agent_clients.c tests/test_agent_profiles.c
78 f075 internal/cbm/sqlite_writer.c src/graph_buffer/graph_buffer.c tests/test_graph_buffer.c
79 f076 scripts/run-test-wave.py tests/test_parallel_harness_contract.sh
80 f077 internal/cbm/extract_imports.c internal/cbm/lang_specs.c tests/test_edge_structural.c tests/test_extraction.c tests/test_grammar_labels.c
81 f078 internal/cbm/sqlite_writer.c src/graph_buffer/graph_buffer.c tests/test_graph_buffer.c tests/test_sqlite_writer.c
82 f079 src/foundation/dump_verify.c src/foundation/dump_verify.h tests/test_dump_verify.c
83 f080 Makefile.cbm scripts/ci/check-binary-composition.sh tests/test_mcpb_bundle_contract.sh tests/test_release_candidate_derivation_contract.sh
84 f081 scripts/smoke-test.sh src/cli/cli.c tests/test_cli.c tests/windows/test_hook_augment.py
85 f082 src/main.c tests/windows/test_daemon_lifecycle.py
86 f083 README.md docs/CONFIGURATION.md graph-ui/src/components/NodeDetailPanel.test.tsx graph-ui/src/components/NodeDetailPanel.tsx graph-ui/src/components/StatsTab.test.tsx graph-ui/src/hooks/useProjects.test.tsx graph-ui/src/hooks/useProjects.ts scripts/smoke-test.sh src/cli/cli.c src/cli/cli.h src/cli/client_adapter.c src/cli/hook_augment.c src/cli/progress_sink.c src/cli/progress_sink.h src/cypher/cypher.c src/cypher/cypher.h src/foundation/compat_fs.c src/foundation/compat_fs.h src/foundation/log.c src/foundation/log.h src/main.c src/mcp/compact_out.c src/mcp/compact_out.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c src/store/store.c src/store/store.h src/ui/http_server.c tests/scale_contract.sh tests/test_cli.c tests/test_cypher.c tests/test_daemon_runtime.c tests/test_diagnostics.c tests/test_httpd.c tests/test_incremental.c tests/test_index_resilience.c tests/test_index_supervisor.c tests/test_log.c tests/test_main.c tests/test_mcp.c tests/test_pipeline.c tests/test_store_pragmas.c tests/windows/test_daemon_lifecycle.py tests/windows/test_daemon_stability.py
87 f084 README.md docs/CONFIGURATION.md graph-ui/src/components/NodeDetailPanel.test.tsx graph-ui/src/components/NodeDetailPanel.tsx graph-ui/src/components/StatsTab.test.tsx graph-ui/src/hooks/useProjects.test.tsx graph-ui/src/hooks/useProjects.ts scripts/smoke-test.sh src/cli/cli.c src/cli/cli.h src/cli/client_adapter.c src/cli/hook_augment.c src/cli/progress_sink.c src/cli/progress_sink.h src/cypher/cypher.c src/cypher/cypher.h src/foundation/compat_fs.c src/foundation/compat_fs.h src/foundation/log.c src/foundation/log.h src/main.c src/mcp/compact_out.c src/mcp/compact_out.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c src/store/store.c src/store/store.h src/ui/http_server.c tests/scale_contract.sh tests/test_cli.c tests/test_cypher.c tests/test_daemon_runtime.c tests/test_diagnostics.c tests/test_httpd.c tests/test_incremental.c tests/test_index_resilience.c tests/test_index_supervisor.c tests/test_log.c tests/test_main.c tests/test_mcp.c tests/test_pipeline.c tests/test_store_pragmas.c tests/windows/test_daemon_lifecycle.py tests/windows/test_daemon_stability.py tests/windows/test_hook_augment.py
88 f085 src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_pipeline.c tests/test_registry.c
89 f086 src/cli/config_yaml_edit.c tests/test_config_yaml_edit.c
90 f087 src/foundation/compat_fs.c tests/test_cli.c tests/test_platform.c
91 f088 .github/workflows/release.yml
92 f089 .github/workflows/pages.yml
93 f090 src/store/store.c tests/test_store_nodes.c
94 f091 src/discover/discover.c tests/test_discover.c
95 f092 Makefile.cbm internal/cbm/extract_defs.c internal/cbm/helpers.c internal/cbm/helpers.h tests/repro/repro_issue1692.c tests/repro/repro_main.c
96 f093 src/mcp/mcp.c src/store/store.c tests/test_mcp.c tests/test_store_nodes.c
97 f094 tests/test_mcp.c
98 f095 src/mcp/mcp.c src/store/store.c src/store/store.h tests/test_mcp.c
99 f096 scripts/msan.sh scripts/test.sh tests/test_main.c
100 f097 src/daemon/ipc.c src/daemon/ipc.h tests/test_cli.c tests/test_daemon_ipc.c
101 f098 src/daemon/application.c src/mcp/mcp.c tests/test_daemon.c
102 f099 Makefile.cbm README.md graph-ui/index.html graph-ui/src/App.tsx graph-ui/src/components/ChangesTab.tsx graph-ui/src/components/CommandK.tsx graph-ui/src/components/ControlTab.tsx graph-ui/src/components/DashboardTab.tsx graph-ui/src/components/DisplaySettingsMenu.tsx graph-ui/src/components/ErrorBoundary.tsx graph-ui/src/components/FilterPanel.tsx graph-ui/src/components/FlowsTab.observed.test.tsx graph-ui/src/components/FlowsTab.tsx graph-ui/src/components/FlowsTab.zh.test.tsx graph-ui/src/components/GraphLoader.tsx graph-ui/src/components/GraphScene.tsx graph-ui/src/components/GraphTab.deadcode.test.tsx graph-ui/src/components/GraphTab.filters.test.tsx graph-ui/src/components/GraphTab.regions.test.tsx graph-ui/src/components/GraphTab.selection.test.tsx graph-ui/src/components/GraphTab.tsx graph-ui/src/components/HaloLayer.tsx graph-ui/src/components/MetricChip.tsx graph-ui/src/components/Minimap.tsx graph-ui/src/components/MissedCallout.tsx graph-ui/src/components/ModulesTab.tsx graph-ui/src/components/NodeDetailPanel.showmore.test.tsx graph-ui/src/components/NodeDetailPanel.tsx graph-ui/src/components/NodeTooltip.tsx graph-ui/src/components/OverviewTab.tsx graph-ui/src/components/ProjectCard.tsx graph-ui/src/components/ProjectSwitcher.tsx graph-ui/src/components/PromptBasket.tsx graph-ui/src/components/RegionPanel.tsx graph-ui/src/components/RegionTooltip.tsx graph-ui/src/components/Sidebar.tsx graph-ui/src/components/StatsTab.tsx graph-ui/src/components/SymbolTab.tsx graph-ui/src/components/TriggerTree.tsx graph-ui/src/components/WikiHoverCard.test.tsx graph-ui/src/components/WikiHoverCard.tsx graph-ui/src/components/WikiPanel.test.tsx graph-ui/src/components/WikiPanel.tsx graph-ui/src/components/WikiPanel.zh.test.tsx graph-ui/src/components/wikiTier.ts graph-ui/src/hooks/useGraphData.ts graph-ui/src/hooks/useProjects.test.tsx graph-ui/src/hooks/useProjects.ts graph-ui/src/lib/atlas.test.ts graph-ui/src/lib/atlas.ts graph-ui/src/lib/complexity.test.ts graph-ui/src/lib/complexity.ts graph-ui/src/lib/composer.ts graph-ui/src/lib/density.test.ts graph-ui/src/lib/density.ts graph-ui/src/lib/findings.test.ts graph-ui/src/lib/findings.ts graph-ui/src/lib/firstread.test.ts graph-ui/src/lib/firstread.ts graph-ui/src/lib/flowgroup.test.ts graph-ui/src/lib/flowgroup.ts graph-ui/src/lib/i18n.ts graph-ui/src/lib/impact.test.ts graph-ui/src/lib/impact.ts graph-ui/src/lib/observed.test.ts graph-ui/src/lib/observed.ts graph-ui/src/lib/questions.test.ts graph-ui/src/lib/questions.ts graph-ui/src/lib/rationale.test.ts graph-ui/src/lib/rationale.ts graph-ui/src/lib/regions.test.ts graph-ui/src/lib/regions.ts graph-ui/src/lib/treemap.ts graph-ui/src/lib/types.ts graph-ui/src/lib/who.test.ts graph-ui/src/lib/who.ts graph-ui/src/lib/why.test.ts graph-ui/src/lib/why.ts graph-ui/src/lib/whyfetch.ts graph-ui/src/styles/globals.css graph-ui/src/test/setup.ts graph-ui/src/wiki/entries.test.ts graph-ui/src/wiki/entries.ts graph-ui/tsconfig.tsbuildinfo graph-ui/vite.config.ts scripts/run-test-wave.py scripts/security-ui.sh src/main.c src/mcp/mcp.c src/pipeline/pipeline.h src/pipeline/pipeline_internal.h src/store/store.c src/store/store.h src/ui/atlas.h src/ui/atlas_api.c src/ui/atlas_flows.c src/ui/atlas_handout.c src/ui/atlas_metrics.c src/ui/atlas_why.c src/ui/http_server.c src/ui/layout3d.c src/ui/layout3d.h src/ui/layout_internal.h src/ui/layout_regions.c src/watcher/watcher.c src/watcher/watcher.h test-infrastructure/docker-compose.yml test-infrastructure/ladder.sh test-infrastructure/run.sh tests/test_cypher.c tests/test_daemon_runtime.c tests/test_httpd.c tests/test_incremental.c tests/test_mcp.c tests/test_py_lsp_scale.c tests/test_ui.c tests/test_watcher.c
103 f100 README.md docs/CONFIGURATION.md scripts/test_mcp_in_process.py src/main.c
104 f101 tests/test_pipeline.c
105 f102 src/daemon/application.c src/daemon/application_internal.h tests/test_daemon.c tests/test_daemon_application.c
106 f103 src/discover/discover.c src/discover/discover.h src/discover/language.c tests/test_language.c
107 f104 src/watcher/watcher.c src/watcher/watcher.h tests/test_watcher.c
108 f105 scripts/test.sh src/main.c tests/test_worker_session_scope.sh
109 f106 src/pipeline/pass_complexity.c tests/fixtures/complexity_pass_cycle_order/p00_a.py tests/fixtures/complexity_pass_cycle_order/p00_b.py tests/fixtures/complexity_pass_cycle_order/p01_a.py tests/fixtures/complexity_pass_cycle_order/p01_b.py tests/fixtures/complexity_pass_cycle_order/p02_a.py tests/fixtures/complexity_pass_cycle_order/p02_b.py tests/fixtures/complexity_pass_cycle_order/p03_a.py tests/fixtures/complexity_pass_cycle_order/p03_b.py tests/fixtures/complexity_pass_cycle_order/p04_a.py tests/fixtures/complexity_pass_cycle_order/p04_b.py tests/fixtures/complexity_pass_cycle_order/p05_a.py tests/fixtures/complexity_pass_cycle_order/p05_b.py tests/fixtures/complexity_pass_cycle_order/p06_a.py tests/fixtures/complexity_pass_cycle_order/p06_b.py tests/fixtures/complexity_pass_cycle_order/p07_a.py tests/fixtures/complexity_pass_cycle_order/p07_b.py tests/fixtures/complexity_pass_cycle_order/p08_a.py tests/fixtures/complexity_pass_cycle_order/p08_b.py tests/fixtures/complexity_pass_cycle_order/p09_a.py tests/fixtures/complexity_pass_cycle_order/p09_b.py tests/fixtures/complexity_pass_cycle_order/p10_a.py tests/fixtures/complexity_pass_cycle_order/p10_b.py tests/fixtures/complexity_pass_cycle_order/p11_a.py tests/fixtures/complexity_pass_cycle_order/p11_b.py tests/fixtures/complexity_pass_cycle_order/p12_a.py tests/fixtures/complexity_pass_cycle_order/p12_b.py tests/fixtures/complexity_pass_cycle_order/p13_a.py tests/fixtures/complexity_pass_cycle_order/p13_b.py tests/fixtures/complexity_pass_cycle_order/p14_a.py tests/fixtures/complexity_pass_cycle_order/p14_b.py tests/fixtures/complexity_pass_cycle_order/p15_a.py tests/fixtures/complexity_pass_cycle_order/p15_b.py tests/fixtures/complexity_pass_cycle_order/p16_a.py tests/fixtures/complexity_pass_cycle_order/p16_b.py tests/fixtures/complexity_pass_cycle_order/p17_a.py tests/fixtures/complexity_pass_cycle_order/p17_b.py tests/fixtures/complexity_pass_cycle_order/p18_a.py tests/fixtures/complexity_pass_cycle_order/p18_b.py tests/fixtures/complexity_pass_cycle_order/p19_a.py tests/fixtures/complexity_pass_cycle_order/p19_b.py tests/fixtures/complexity_pass_cycle_order/p20_a.py tests/fixtures/complexity_pass_cycle_order/p20_b.py tests/fixtures/complexity_pass_cycle_order/p21_a.py tests/fixtures/complexity_pass_cycle_order/p21_b.py tests/fixtures/complexity_pass_cycle_order/p22_a.py tests/fixtures/complexity_pass_cycle_order/p22_b.py tests/fixtures/complexity_pass_cycle_order/p23_a.py tests/fixtures/complexity_pass_cycle_order/p23_b.py tests/fixtures/complexity_pass_cycle_order/t00_x.py tests/fixtures/complexity_pass_cycle_order/t00_y.py tests/fixtures/complexity_pass_cycle_order/t00_z.py tests/fixtures/complexity_pass_cycle_order/t01_x.py tests/fixtures/complexity_pass_cycle_order/t01_y.py tests/fixtures/complexity_pass_cycle_order/t01_z.py tests/fixtures/complexity_pass_cycle_order/t02_x.py tests/fixtures/complexity_pass_cycle_order/t02_y.py tests/fixtures/complexity_pass_cycle_order/t02_z.py tests/fixtures/complexity_pass_cycle_order/t03_x.py tests/fixtures/complexity_pass_cycle_order/t03_y.py tests/fixtures/complexity_pass_cycle_order/t03_z.py tests/fixtures/complexity_pass_cycle_order/t04_x.py tests/fixtures/complexity_pass_cycle_order/t04_y.py tests/fixtures/complexity_pass_cycle_order/t04_z.py tests/fixtures/complexity_pass_cycle_order/t05_x.py tests/fixtures/complexity_pass_cycle_order/t05_y.py tests/fixtures/complexity_pass_cycle_order/t05_z.py tests/test_pipeline.c
110 f107 docs/benchmarks/bounded-two-pass-results.json docs/benchmarks/bounded-two-pass.md scripts/benchmark-streaming.py src/pipeline/lsp_surface.c src/pipeline/lsp_surface.h src/pipeline/pass_parallel.c src/pipeline/pipeline.c src/pipeline/pipeline_internal.h tests/test_pipeline.c
111 f108 src/pipeline/pass_semantic_edges.c tests/test_pipeline.c
112 f109 .github/workflows/pr.yml internal/cbm/cbm.c scripts/ci/README.md scripts/ci/coverage-gate-allowlist.txt scripts/ci/parse-partial-baseline.txt scripts/ci/self-index-coverage-gate.sh scripts/test.sh tests/test_coverage_gate_contract.sh tests/test_parse_coverage.c tests/test_venue_parity_contract.sh
113 f110 Makefile.cbm README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/cli/cli.h src/daemon/application.c src/discover/discover.c src/discover/discover.h src/foundation/index_policy.c src/foundation/index_policy.h src/foundation/subprocess.c src/foundation/subprocess.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_daemon_application.c tests/test_discover.c tests/test_index_policy.c tests/test_index_supervisor.c tests/test_main.c tests/test_mcp.c tests/test_pipeline.c tests/test_subprocess.c tests/test_worker_error_response.sh tests/test_worker_watchdog.sh
114 f111 Makefile.cbm README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/cli/cli.h src/daemon/application.c src/daemon/bootstrap.c src/discover/discover.c src/discover/discover.h src/foundation/compat_fs.c src/foundation/compat_fs.h src/foundation/index_policy.c src/foundation/index_policy.h src/foundation/subprocess.c src/foundation/subprocess.h src/main.c src/mcp/index_supervisor.c src/mcp/index_supervisor.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_daemon_application.c tests/test_discover.c tests/test_index_policy.c tests/test_index_supervisor.c tests/test_main.c tests/test_mcp.c tests/test_pipeline.c tests/test_platform.c tests/test_subprocess.c tests/test_worker_error_response.sh tests/test_worker_watchdog.sh
115 f112 Makefile.cbm README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/cli/cli.h src/daemon/application.c src/daemon/bootstrap.c src/discover/discover.c src/discover/discover.h src/foundation/compat_fs.c src/foundation/compat_fs.h src/foundation/index_policy.c src/foundation/index_policy.h src/foundation/subprocess.c src/foundation/subprocess.h src/git/git_context.c src/git/git_context.h src/main.c src/mcp/index_supervisor.c src/mcp/index_supervisor.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_daemon_application.c tests/test_discover.c tests/test_git_context.c tests/test_index_policy.c tests/test_index_supervisor.c tests/test_main.c tests/test_mcp.c tests/test_pipeline.c tests/test_platform.c tests/test_subprocess.c tests/test_worker_error_response.sh tests/test_worker_watchdog.sh
116 f113 src/pipeline/pipeline_incremental.c tests/test_pipeline.c
117 f114 README.md THIRD_PARTY.md docs/index.html docs/llms.txt internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/grammar_vba.c internal/cbm/helpers.c internal/cbm/lang_specs.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/vba/LICENSE internal/cbm/vendored/grammars/vba/parser.c internal/cbm/vendored/grammars/vba/tree_sitter/alloc.h internal/cbm/vendored/grammars/vba/tree_sitter/array.h internal/cbm/vendored/grammars/vba/tree_sitter/parser.h pkg/chocolatey/codebase-memory-mcp.nuspec pkg/npm/README.md scripts/new-languages.json scripts/package-release.sh scripts/vendored-checksums.txt server.json src/discover/discover.h src/discover/language.c src/discover/userconfig.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_manifest.c tests/repro/repro_invariant_breadth.c tests/repro/repro_language_registry.c tests/test_extraction.c tests/test_grammar_labels.c tests/test_grammar_regression.c tests/test_lang_contract.c tests/test_language.c
118 f115 README.md THIRD_PARTY.md docs/index.html docs/llms.txt internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/extract_unified.h internal/cbm/grammar_vba.c internal/cbm/helpers.c internal/cbm/helpers.h internal/cbm/lang_specs.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/vba/LICENSE internal/cbm/vendored/grammars/vba/parser.c internal/cbm/vendored/grammars/vba/tree_sitter/alloc.h internal/cbm/vendored/grammars/vba/tree_sitter/array.h internal/cbm/vendored/grammars/vba/tree_sitter/parser.h pkg/chocolatey/codebase-memory-mcp.nuspec pkg/npm/README.md scripts/new-languages.json scripts/package-release.sh scripts/vendored-checksums.txt server.json src/discover/discover.h src/discover/language.c src/discover/userconfig.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_manifest.c tests/repro/repro_invariant_breadth.c tests/repro/repro_language_registry.c tests/test_extraction.c tests/test_grammar_labels.c tests/test_grammar_regression.c tests/test_lang_contract.c tests/test_language.c
119 f116 internal/cbm/lang_specs.c
120 f117 internal/cbm/extract_node_stack.h internal/cbm/lsp/ts_lsp.c tests/test_ts_lsp.c
121 f118 .cbmignore src/cli/cli.c src/cli/cli.h src/main.c src/mcp/mcp.c
122 f119 .cbmignore src/cli/cli.c src/cli/cli.h src/main.c src/mcp/mcp.c tests/test_cli.c
123 f120 src/git/git_context.c src/git/git_context.h src/mcp/mcp.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/store/store.c src/store/store.h tests/test_git_context.c tests/test_mcp.c tests/test_store_nodes.c
124 f121 src/watcher/watcher.c tests/test_watcher.c
125 f122 scripts/run-tests-parallel.sh src/git/git_context.c src/git/git_context.h src/mcp/mcp.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/store/store.c src/store/store.h tests/test_git_context.c tests/test_mcp.c tests/test_store_nodes.c
126 f123 scripts/test-windows.ps1 tests/windows/test_mcp_stdio.py
127 f124 internal/cbm/extract_defs.c tests/test_grammar_labels.c tests/test_grammar_regression.c
128 f125 src/cli/cli.c tests/test_cli.c
129 f126 src/cli/cli.c src/cli/config_json_like.c src/cli/hook_augment.c src/daemon/application.c src/foundation/compat_fs.c src/foundation/compat_fs.h src/main.c src/mcp/index_supervisor.c src/mcp/mcp.c src/pipeline/artifact.c src/pipeline/pipeline.c src/ui/config.c tests/test_artifact.c tests/test_cli.c tests/test_platform.c
130 f127 tests/test_daemon_frontend.c
131 f128 src/daemon/ipc.c src/daemon/ipc_internal.h tests/test_daemon_ipc.c
132 f129 .github/dependabot.yml .github/workflows/_build.yml .github/workflows/_lint.yml .github/workflows/_test.yml .github/workflows/release.yml .node-version CONTRIBUTING.md scripts/ci/lint-workflows.sh scripts/ci/test-frontend.sh tests/test_venue_parity_contract.sh
133 f130 src/foundation/compat_fs.c src/foundation/compat_fs.h src/pipeline/pipeline.c src/pipeline/pipeline_internal.h tests/test_pipeline.c
134 f131 src/daemon/application.c src/mcp/mcp.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/pipeline_internal.h tests/test_daemon_application.c tests/test_incremental.c tests/test_mcp.c tests/test_pipeline.c
135 f132 src/daemon/bootstrap.c src/daemon/bootstrap.h src/daemon/host.c src/daemon/ipc.c src/daemon/ipc.h src/daemon/ipc_internal.h src/foundation/platform.c src/foundation/platform.h tests/test_daemon_bootstrap.c tests/test_daemon_ipc.c
136 f133 Makefile.cbm src/cli/cli.c src/cli/config_edit_path.c src/cli/config_edit_path.h src/cli/config_json_like.c src/cli/config_text_edit.c src/cli/config_toml_edit.c src/cli/config_yaml_edit.c tests/test_cli.c tests/test_config_json_like.c tests/test_config_text_edit.c tests/test_config_toml_edit.c tests/test_config_yaml_edit.c
137 f134 src/cli/cli.c src/mcp/index_supervisor.c src/mcp/index_supervisor.h tests/test_cli.c
138 f135 src/cli/cli.c src/cli/config_json_like.c src/cli/config_json_like.h src/mcp/mcp.c tests/test_cli.c tests/test_config_json_like.c tests/test_mcp.c
139 f136 src/cli/cli.c tests/test_cli.c tests/test_smoke_fixture_contract.sh
140 f137 .github/workflows/_lint.yml .github/workflows/_test.yml
141 f138 src/daemon/ipc.c src/daemon/ipc.h tests/test_daemon_ipc.c
142 f139 src/cli/activation_transaction.c src/daemon/ipc.c src/daemon/ipc.h tests/test_daemon_ipc.c
143 f140 src/cli/cli.h src/main.c tests/test_cli.c
144 f141 internal/cbm/extract_defs.c tests/test_edge_types_probe.c tests/test_extraction.c
145 f142 internal/cbm/lsp/php_lsp.c
146 f143 internal/cbm/lsp/php_lsp.c src/pipeline/registry.c
147 f144 src/cli/cli.c src/cli/cli.h src/main.c src/mcp/mcp.c tests/test_cli.c
148 f145 README.md docs/UPSTREAM-SYNC.md docs/lsp-uplift/PERL-CROSS-FILE-INHERITANCE.md docs/lsp-uplift/PERL-DUCK-TYPING-AND-CROSS-LANG-AUDIT.md docs/lsp-uplift/PLAN.md docs/lsp-uplift/RESOLVER-PLAYBOOK.md docs/lsp-uplift/SOP.md internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/helpers.c internal/cbm/lang_specs.c internal/cbm/lsp/generated/perl_stdlib_data.c internal/cbm/lsp/generated/rust_crates_seed.c internal/cbm/lsp/go_lsp.c internal/cbm/lsp/go_lsp.h internal/cbm/lsp/go_stdlib_modern.c internal/cbm/lsp/java_lsp.c internal/cbm/lsp/perl_lsp.c internal/cbm/lsp/perl_lsp.h internal/cbm/lsp/py_builtins.c internal/cbm/lsp/py_lsp.c internal/cbm/lsp/py_stdlib_compat.c internal/cbm/lsp/rust_cargo.c internal/cbm/lsp/rust_cargo.h internal/cbm/lsp/rust_lsp.c internal/cbm/lsp/rust_lsp.h internal/cbm/lsp/type_registry.h internal/cbm/lsp_all.c internal/cbm/service_patterns.c internal/cbm/service_patterns.h src/discover/language.c src/pipeline/lsp_surface.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_lsp_cross.h src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pass_route_nodes.c src/pipeline/pass_semantic.c src/pipeline/pass_tests.c src/pipeline/pipeline.c src/pipeline/registry.c tests/test_extraction.c tests/test_go_lsp.c tests/test_java_lsp.c tests/test_language.c tests/test_perl_lsp.c tests/test_pipeline.c tests/test_py_lsp.c tests/test_registry.c tests/test_route_canon.c tests/test_rust_lsp.c
149 f146 src/foundation/subprocess.c src/foundation/subprocess.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h tests/test_index_supervisor.c tests/test_main.c tests/test_subprocess.c
150 f147 src/daemon/daemon.c src/daemon/daemon.h src/daemon/host.c src/daemon/runtime.c src/daemon/runtime.h tests/test_daemon_runtime.c
151 f148 internal/cbm/cbm.c src/foundation/compat.c src/foundation/compat.h tests/test_edge_types_probe.c
152 f149 scripts/test-windows.ps1 tests/windows/mcp_stdio.py tests/windows/test_mcp_stdio.py
153 f150 .github/workflows/codeql.yml
154 f151 .github/workflows/scorecard.yml
155 f152 src/cli/cli.c src/foundation/sha256.c src/foundation/sha256.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h tests/test_cli.c
156 f153 install.ps1 scripts/test.sh src/cli/activation_transaction.c src/cli/cli.c tests/test_activation_diagnostic_contract.sh tests/test_windows_bundle_contract.sh
157 f154 scripts/run-test-wave.py src/cli/cli.c src/foundation/sha256.c src/foundation/sha256.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h tests/test_cli.c
158 f155 .github/workflows/_security.yml graph-ui/package-lock.json graph-ui/package.json scripts/test.sh tests/test_security_gate_fail_closed.sh
159 f156 graph-ui/package-lock.json graph-ui/package.json
160 f157 .github/workflows/_security.yml .github/workflows/pr.yml .github/workflows/release.yml graph-ui/package-lock.json graph-ui/package.json scripts/test.sh tests/test_security_gate_fail_closed.sh
161 f158 tests/test_fqn.c
162 f159 .github/workflows/dry-run.yml tests/test_security_gate_fail_closed.sh
163 f160 tests/test_daemon_ipc.c tests/test_main.c
164 f161 src/daemon/ipc.c tests/test_daemon_ipc.c tests/test_main.c
165 f162 .github/workflows/_test.yml src/daemon/ipc.c src/daemon/ipc.h tests/test_daemon_ipc.c tests/test_main.c
166 f163 scripts/soak-test.sh scripts/test.sh tests/test_soak_runtime_isolation_contract.sh
167 f164 scripts/memlab.sh scripts/test.sh tests/test_memlab_runtime_isolation_contract.sh
168 f165 scripts/benchmark-index.sh scripts/benchmark-search-graph.sh scripts/test.sh tests/test_benchmark_runtime_isolation_contract.sh
169 f166 scripts/smoke-test.sh scripts/test.sh tests/test_smoke_runtime_isolation_contract.sh
170 f167 Makefile.cbm scripts/lint-memory-core.py scripts/memory-core-baseline.txt src/foundation/mem.c src/foundation/mem.h src/foundation/mem_core.c src/foundation/mem_core.h src/foundation/platform.h src/foundation/system_info.c src/pipeline/pipeline.c tests/test_mem.c
171 f168 src/cli/activation_transaction.c
172 f169 Makefile.cbm internal/cbm/arena.h internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_unified.c internal/cbm/lsp/c_lsp.c internal/cbm/lsp/c_lsp.h internal/cbm/lsp/cs_lsp.c internal/cbm/lsp/go_lsp.c internal/cbm/lsp/py_lsp.c internal/cbm/lsp/py_lsp.h internal/cbm/lsp/type_registry.c internal/cbm/lsp/type_registry.h internal/cbm/result_compact.c internal/cbm/result_spill.c internal/cbm/result_spill.h scripts/lint-memory-core.py scripts/memory-core-baseline.txt src/foundation/arena.c src/foundation/arena.h src/foundation/dyn_array.h src/foundation/hash_table.c src/foundation/hash_table.h src/foundation/mem.c src/foundation/mem.h src/foundation/mem_core.c src/foundation/mem_core.h src/foundation/platform.h src/foundation/slab_alloc.c src/foundation/system_info.c src/graph_buffer/graph_buffer.c src/graph_buffer/graph_buffer.h src/mcp/mcp.c src/pipeline/lsp_surface.c src/pipeline/lsp_surface.h src/pipeline/pass_complexity.c src/pipeline/pass_importance.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_lsp_cross.h src/pipeline/pass_parallel.c src/pipeline/pass_semantic.c src/pipeline/pass_semantic_edges.c src/pipeline/pass_similarity.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/worker_pool.c src/semantic/semantic.c src/semantic/semantic.h src/simhash/minhash.c tests/test_arena.c tests/test_c_lsp.c tests/test_extraction.c tests/test_mcp.c tests/test_mem.c tests/test_parallel.c tests/test_pipeline.c
173 f170 docs/CONFIGURATION.md src/main.c tests/test_daemon_open_readiness.py
174 f171 src/daemon/ipc.c tests/test_daemon_ipc.c
175 f172 src/cli/hook_augment.c src/daemon/runtime.c src/daemon/runtime.h src/main.c tests/test_cli.c tests/test_daemon_runtime.c
176 f173 Makefile.cbm internal/cbm/arena.h internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_unified.c internal/cbm/lsp/c_lsp.c internal/cbm/lsp/c_lsp.h internal/cbm/lsp/cs_lsp.c internal/cbm/lsp/go_lsp.c internal/cbm/lsp/py_lsp.c internal/cbm/lsp/py_lsp.h internal/cbm/lsp/type_registry.c internal/cbm/lsp/type_registry.h internal/cbm/result_compact.c internal/cbm/result_spill.c internal/cbm/result_spill.h scripts/lint-memory-core.py scripts/memory-core-baseline.txt src/foundation/arena.c src/foundation/arena.h src/foundation/diagnostics.c src/foundation/dyn_array.h src/foundation/hash_table.c src/foundation/hash_table.h src/foundation/mem.c src/foundation/mem.h src/foundation/mem_core.c src/foundation/mem_core.h src/foundation/platform.h src/foundation/slab_alloc.c src/foundation/system_info.c src/graph_buffer/graph_buffer.c src/graph_buffer/graph_buffer.h src/main.c src/mcp/mcp.c src/pipeline/lsp_surface.c src/pipeline/lsp_surface.h src/pipeline/pass_complexity.c src/pipeline/pass_importance.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_lsp_cross.h src/pipeline/pass_parallel.c src/pipeline/pass_semantic.c src/pipeline/pass_semantic_edges.c src/pipeline/pass_similarity.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/worker_pool.c src/semantic/semantic.c src/semantic/semantic.h src/simhash/minhash.c tests/test_arena.c tests/test_c_lsp.c tests/test_extraction.c tests/test_graph_buffer.c tests/test_mcp.c tests/test_mem.c tests/test_parallel.c tests/test_pipeline.c
177 f174 README.md scripts/smoke-test.sh src/cli/cli.c src/cli/cli.h tests/test_cli.c
178 f175 src/foundation/mem.c src/foundation/mem.h
179 f176 flake.nix pkg/chocolatey/codebase-memory-mcp.nuspec pkg/chocolatey/tools/chocolateyInstall.ps1 pkg/go/cmd/codebase-memory-mcp/main.go pkg/npm/package.json pkg/pypi/pyproject.toml pkg/pypi/src/codebase_memory_mcp/_cli.py server.json
180 f177 graph-ui/src/components/FilterPanel.tsx graph-ui/src/components/GraphTab.deadcode.test.tsx graph-ui/src/lib/colors.ts
181 f178 CONTRIBUTING.md README.md docs/index.html pkg/npm/README.md
182 f179 internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/service_patterns.c internal/cbm/service_patterns.h src/cli/cli.c src/cypher/cypher.c src/foundation/str_util.c src/foundation/str_util.h src/mcp/mcp.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_cli.c tests/test_cypher.c tests/test_extraction.c tests/test_infrascan.c tests/test_mcp.c tests/test_parse_coverage.c tests/test_pipeline.c tests/test_registry.c tests/test_str_util.c
183 f180 internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/service_patterns.c internal/cbm/service_patterns.h scripts/memory-core-baseline.txt src/cli/cli.c src/cypher/cypher.c src/foundation/str_util.c src/foundation/str_util.h src/mcp/mcp.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_cli.c tests/test_cypher.c tests/test_extraction.c tests/test_infrascan.c tests/test_mcp.c tests/test_parse_coverage.c tests/test_pipeline.c tests/test_registry.c tests/test_str_util.c
184 f181 src/discover/language.c tests/test_language.c
185 f182 .github/workflows/_memwaste.yml .github/workflows/dry-run.yml .github/workflows/nightly-soak.yml .github/workflows/pr.yml .github/workflows/release.yml Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_node_stack.h internal/cbm/lsp/go_lsp.c internal/cbm/result_compact.c internal/cbm/sqlite_writer.c scripts/fuzz.sh scripts/memlab-drive.py scripts/memlab-report.py scripts/memlab.sh scripts/memwaste-padding.py scripts/memwaste-ratchet.py scripts/memwaste-report.py scripts/memwaste-scaling.py scripts/memwaste.sh scripts/sanitizer-coverage-map.py scripts/sanitizer-map.json src/foundation/arena.c src/foundation/arena.h src/foundation/compat_thread.c src/foundation/dyn_array.h src/foundation/hash_table.c src/foundation/mem.c src/foundation/mem.h src/foundation/mem_core.c src/foundation/mem_events.c src/foundation/mem_events.h src/foundation/mem_override_darwin.c src/foundation/mem_override_libc.c src/foundation/mem_override_posix.c src/foundation/mem_override_win.c src/foundation/mem_profile.c src/foundation/mem_profile.h src/main.c src/pipeline/lsp_surface.c src/pipeline/pass_configlink.c src/pipeline/pass_k8s.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_lsp_cross.h src/pipeline/pass_parallel.c src/pipeline/pass_semantic.c src/pipeline/pass_semantic_edges.c src/pipeline/pass_similarity.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/registry.c src/pipeline/worker_pool.c src/semantic/semantic.c src/semantic/semantic.h src/simhash/minhash.c src/simhash/minhash.h tests/fuzz/cbm_fuzz.c tests/fuzz/corpus/config/json-empty.seed tests/fuzz/corpus/config/json.seed tests/fuzz/corpus/config/toml.seed tests/fuzz/corpus/config/yaml.seed tests/fuzz/corpus/cypher/agg.seed tests/fuzz/corpus/cypher/hops.seed tests/fuzz/corpus/cypher/match.seed tests/fuzz/corpus/cypher/optional.seed tests/fuzz/corpus/cypher/union.seed tests/fuzz/corpus/cypher/where.seed tests/fuzz/corpus/extract/c.seed tests/fuzz/corpus/extract/cpp.seed tests/fuzz/corpus/extract/csharp.seed tests/fuzz/corpus/extract/go.seed tests/fuzz/corpus/extract/java.seed tests/fuzz/corpus/extract/javascript.seed tests/fuzz/corpus/extract/makefile.seed tests/fuzz/corpus/extract/php.seed tests/fuzz/corpus/extract/python.seed tests/fuzz/corpus/extract/ruby.seed tests/fuzz/corpus/extract/rust.seed tests/fuzz/corpus/extract/sql.seed tests/fuzz/corpus/extract/typescript.seed tests/fuzz/corpus/extract/yaml.seed tests/test_arena.c tests/test_extraction.c tests/test_hash_table.c tests/test_main.c tests/test_mem.c tests/test_mem_events.c tests/test_registry.c tests/test_semantic.c tests/test_shell_line_endings.sh tests/test_simhash.c tests/test_venue_parity_contract.sh
186 f183 src/cli/cli.h src/main.c tests/test_cli.c tests/test_daemon_bootstrap.c
187 f184 src/cli/cli.h src/main.c tests/test_cli.c tests/test_daemon_bootstrap.c tests/test_mcp.c
188 f185 src/ui/layout3d.c tests/test_ui.c
189 f186 Makefile.cbm README.md src/daemon/application.c src/daemon/bootstrap.c src/foundation/mem_core.c src/foundation/mem_core.h src/main.c src/mcp/cache.c src/mcp/cache.h src/mcp/mcp.c tests/test_cache.c tests/test_cache_cli.py tests/test_daemon_application.c tests/test_daemon_bootstrap.c tests/test_main.c tests/test_mcp.c
190 f187 .github/workflows/_memwaste.yml .github/workflows/dry-run.yml .github/workflows/nightly-soak.yml .github/workflows/pr.yml .github/workflows/release.yml Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_node_stack.h internal/cbm/lsp/go_lsp.c internal/cbm/result_compact.c internal/cbm/result_spill.c internal/cbm/result_spill.h internal/cbm/sqlite_writer.c scripts/fuzz.sh scripts/memlab-drive.py scripts/memlab-report.py scripts/memlab.sh scripts/memory-core-baseline.txt scripts/memwaste-padding.py scripts/memwaste-ratchet.py scripts/memwaste-report.py scripts/memwaste-scaling.py scripts/memwaste.sh scripts/sanitizer-coverage-map.py scripts/sanitizer-map.json scripts/smoke-test.sh scripts/test.sh src/foundation/arena.c src/foundation/arena.h src/foundation/compat_thread.c src/foundation/dyn_array.h src/foundation/hash_table.c src/foundation/mem.c src/foundation/mem.h src/foundation/mem_core.c src/foundation/mem_events.c src/foundation/mem_events.h src/foundation/mem_override_darwin.c src/foundation/mem_override_libc.c src/foundation/mem_override_posix.c src/foundation/mem_override_win.c src/foundation/mem_profile.c src/foundation/mem_profile.h src/foundation/platform.h src/foundation/system_info.c src/graph_buffer/graph_buffer.c src/main.c src/pipeline/lsp_surface.c src/pipeline/pass_configlink.c src/pipeline/pass_k8s.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_lsp_cross.h src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pass_route_nodes.c src/pipeline/pass_semantic.c src/pipeline/pass_semantic_edges.c src/pipeline/pass_similarity.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/registry.c src/pipeline/worker_pool.c src/semantic/semantic.c src/semantic/semantic.h src/simhash/minhash.c src/simhash/minhash.h tests/fuzz/cbm_fuzz.c tests/fuzz/corpus/config/json-empty.seed tests/fuzz/corpus/config/json.seed tests/fuzz/corpus/config/toml.seed tests/fuzz/corpus/config/yaml.seed tests/fuzz/corpus/cypher/agg.seed tests/fuzz/corpus/cypher/hops.seed tests/fuzz/corpus/cypher/match.seed tests/fuzz/corpus/cypher/optional.seed tests/fuzz/corpus/cypher/union.seed tests/fuzz/corpus/cypher/where.seed tests/fuzz/corpus/extract/c.seed tests/fuzz/corpus/extract/cpp.seed tests/fuzz/corpus/extract/csharp.seed tests/fuzz/corpus/extract/go.seed tests/fuzz/corpus/extract/java.seed tests/fuzz/corpus/extract/javascript.seed tests/fuzz/corpus/extract/makefile.seed tests/fuzz/corpus/extract/php.seed tests/fuzz/corpus/extract/python.seed tests/fuzz/corpus/extract/ruby.seed tests/fuzz/corpus/extract/rust.seed tests/fuzz/corpus/extract/sql.seed tests/fuzz/corpus/extract/typescript.seed tests/fuzz/corpus/extract/yaml.seed tests/test_arena.c tests/test_extraction.c tests/test_hash_table.c tests/test_main.c tests/test_mem.c tests/test_mem_events.c tests/test_parallel.c tests/test_pipeline.c tests/test_registry.c tests/test_semantic.c tests/test_shell_line_endings.sh tests/test_simhash.c tests/test_thread_stack_tls_contract.sh tests/test_venue_parity_contract.sh
191 f188 .github/workflows/_memwaste.yml .github/workflows/dry-run.yml .github/workflows/nightly-soak.yml .github/workflows/pr.yml .github/workflows/release.yml Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_node_stack.h internal/cbm/extract_unified.c internal/cbm/extract_usages.c internal/cbm/lsp/go_lsp.c internal/cbm/result_compact.c internal/cbm/result_spill.c internal/cbm/result_spill.h internal/cbm/sqlite_writer.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/properties/scanner.c scripts/fuzz.sh scripts/memlab-drive.py scripts/memlab-report.py scripts/memlab.sh scripts/memory-core-baseline.txt scripts/memwaste-padding.py scripts/memwaste-ratchet.py scripts/memwaste-report.py scripts/memwaste-scaling.py scripts/memwaste.sh scripts/sanitizer-coverage-map.py scripts/sanitizer-map.json scripts/smoke-test.sh scripts/test.sh scripts/vendored-checksums.txt src/foundation/arena.c src/foundation/arena.h src/foundation/compat_thread.c src/foundation/dyn_array.h src/foundation/hash_table.c src/foundation/mem.c src/foundation/mem.h src/foundation/mem_core.c src/foundation/mem_events.c src/foundation/mem_events.h src/foundation/mem_override_darwin.c src/foundation/mem_override_libc.c src/foundation/mem_override_posix.c src/foundation/mem_override_win.c src/foundation/mem_profile.c src/foundation/mem_profile.h src/foundation/platform.h src/foundation/system_info.c src/graph_buffer/graph_buffer.c src/main.c src/pipeline/lsp_surface.c src/pipeline/pass_configlink.c src/pipeline/pass_k8s.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_lsp_cross.h src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pass_route_nodes.c src/pipeline/pass_semantic.c src/pipeline/pass_semantic_edges.c src/pipeline/pass_similarity.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/registry.c src/pipeline/worker_pool.c src/semantic/semantic.c src/semantic/semantic.h src/simhash/minhash.c src/simhash/minhash.h test-infrastructure/vm/vm-run-tests.sh tests/fuzz/cbm_fuzz.c tests/fuzz/corpus/config/json-empty.seed tests/fuzz/corpus/config/json.seed tests/fuzz/corpus/config/toml.seed tests/fuzz/corpus/config/yaml.seed tests/fuzz/corpus/cypher/agg.seed tests/fuzz/corpus/cypher/hops.seed tests/fuzz/corpus/cypher/match.seed tests/fuzz/corpus/cypher/optional.seed tests/fuzz/corpus/cypher/union.seed tests/fuzz/corpus/cypher/where.seed tests/fuzz/corpus/extract/c.seed tests/fuzz/corpus/extract/cpp.seed tests/fuzz/corpus/extract/csharp.seed tests/fuzz/corpus/extract/go.seed tests/fuzz/corpus/extract/java.seed tests/fuzz/corpus/extract/javascript.seed tests/fuzz/corpus/extract/makefile.seed tests/fuzz/corpus/extract/php.seed tests/fuzz/corpus/extract/python.seed tests/fuzz/corpus/extract/ruby.seed tests/fuzz/corpus/extract/rust.seed tests/fuzz/corpus/extract/sql.seed tests/fuzz/corpus/extract/typescript.seed tests/fuzz/corpus/extract/yaml.seed tests/test_arena.c tests/test_extraction.c tests/test_hash_table.c tests/test_main.c tests/test_mem.c tests/test_mem_events.c tests/test_parallel.c tests/test_pipeline.c tests/test_registry.c tests/test_semantic.c tests/test_shell_line_endings.sh tests/test_simhash.c tests/test_thread_stack_tls_contract.sh tests/test_venue_parity_contract.sh tests/test_vm_verdict_contract.sh
192 f189 internal/cbm/extract_calls.c internal/cbm/lsp/ts_lsp.c tests/test_extraction.c tests/test_ts_lsp.c
193 f190 .github/workflows/pr.yml Makefile.cbm README.md src/cli/cache.c src/cli/cache.h src/daemon/bootstrap.c src/daemon/version_cohort.c src/daemon/version_cohort.h src/foundation/mem_core.c src/foundation/mem_core.h src/main.c tests/test_cache.c tests/test_cache_cli.py tests/test_daemon_bootstrap.c tests/test_main.c tests/test_mcp.c tests/test_version_cohort.c
194 f191 .github/workflows/pr.yml Makefile.cbm README.md pkg/pypi/tests/test_cli.py src/cli/cache.c src/cli/cache.h src/daemon/bootstrap.c src/daemon/version_cohort.c src/daemon/version_cohort.h src/foundation/mem_core.c src/foundation/mem_core.h src/main.c tests/test_cache.c tests/test_cache_cli.py tests/test_daemon_bootstrap.c tests/test_main.c tests/test_mcp.c tests/test_version_cohort.c
195 f192 install.sh tests/test-install-android-guard.sh
196 f193 src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_pipeline.c tests/test_registry.c
197 f194 internal/cbm/vendored/grammars/sql/parser.c internal/cbm/vendored/grammars/tsx/parser.c scripts/vendored-checksums.txt tests/test_parse_coverage.c
198 f195 internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/sql/parser.c internal/cbm/vendored/grammars/tsx/parser.c scripts/vendored-checksums.txt tests/test_parse_coverage.c
199 f196 src/mcp/mcp.c src/pipeline/pipeline.c src/pipeline/pipeline.h tests/test_pipeline.c
200 f197 .github/workflows/_lint.yml .github/workflows/_test.yml .github/workflows/release.yml
201 f198 internal/cbm/lang_specs.c tests/test_extraction.c
202 f199 install.ps1
203 f200 internal/cbm/lsp/py_lsp.c internal/cbm/lsp/scope.c internal/cbm/lsp/scope.h tests/test_scope.c
204 f201 src/cypher/cypher.c src/foundation/recursion_whitelist.h tests/test_cypher.c
205 f202 internal/cbm/service_patterns.c tests/test_infrascan.c tests/test_pipeline.c
206 f203 scripts/memory-core-baseline.txt src/mcp/mcp.c
207 f204 internal/cbm/service_patterns.c tests/test_pipeline.c
208 f205 src/foundation/compat_thread.c
209 f206 CONTRIBUTING.md README.md
210 f207 README.md docs/WINDOWS_TROUBLESHOOTING.md
211 f208 scripts/memory-core-baseline.txt src/mcp/mcp.c tests/test_worker_session_scope.sh
212 f209 .github/workflows/smoke.yml scripts/smoke-invariants.sh
213 f210 internal/cbm/cbm.c internal/cbm/preprocessor.cpp internal/cbm/preprocessor.h src/mcp/mcp.c tests/test_extraction.c
214 f211 README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/daemon/application.c src/foundation/index_policy.c src/foundation/index_policy.h src/foundation/subprocess.c src/foundation/subprocess.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c tests/test_daemon_application.c tests/test_index_policy.c tests/test_index_supervisor.c tests/test_mcp.c tests/test_subprocess.c
215 f212 docs/EVALUATION_PLAN.md scripts/benchmark-index.sh scripts/benchmark-search-graph.sh scripts/test.sh tests/test_benchmark_runtime_isolation_contract.sh
216 f213 Makefile.cbm src/foundation/compat.c src/foundation/compat.h src/foundation/limits.h src/foundation/mem_override_win.c src/foundation/source_encoding.c src/foundation/source_encoding.h src/mcp/index_supervisor.c src/mcp/mcp.c src/pipeline/pass_calls.c src/pipeline/pass_compile_commands.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_semantic.c src/pipeline/pass_usages.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_pipeline.c tests/test_platform.c
217 f214 README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/daemon/application.c src/foundation/index_policy.c src/foundation/index_policy.h src/foundation/subprocess.c src/foundation/subprocess.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c tests/test_daemon_application.c tests/test_index_policy.c tests/test_index_supervisor.c tests/test_mcp.c tests/test_subprocess.c tests/test_worker_session_scope.sh
218 f215 .github/workflows/pr.yml
219 f216 CONTRIBUTING.md
220 f217 README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/daemon/application.c src/daemon/application.h src/foundation/index_policy.c src/foundation/index_policy.h src/foundation/subprocess.c src/foundation/subprocess.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c tests/test_daemon_application.c tests/test_index_policy.c tests/test_index_supervisor.c tests/test_mcp.c tests/test_subprocess.c tests/test_worker_session_scope.sh
221 f218 tests/test_daemon_bootstrap.c
222 f219 internal/cbm/service_patterns.c tests/test_infrascan.c
223 f220 Makefile.cbm src/cli/cli.c src/pipeline/pass_doclinks.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_doclinks.c tests/test_edge_structural.c tests/test_lang_contract.c tests/test_main.c tests/test_pipeline.c
224 f221 Makefile.cbm README.md src/cli/cli.c src/pipeline/pass_doclinks.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_doclinks.c tests/test_edge_structural.c tests/test_lang_contract.c tests/test_main.c tests/test_pipeline.c
225 f222 src/daemon/application.c src/mcp/mcp.c src/pipeline/lsp_surface.c src/pipeline/pipeline.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_daemon_application.c tests/test_mcp.c tests/test_pipeline.c
226 f223 README.md docs/CONFIGURATION.md docs/INDEX_RESOURCE_LIMITS.md scripts/test-runtime.sh src/cli/cli.c src/daemon/application.c src/daemon/application.h src/foundation/index_policy.c src/foundation/index_policy.h src/foundation/subprocess.c src/foundation/subprocess.h src/mcp/index_supervisor.c src/mcp/index_supervisor.h src/mcp/mcp.c src/mcp/mcp.h src/mcp/mcp_internal.h src/pipeline/pipeline.c tests/test_daemon_application.c tests/test_daemon_bootstrap.c tests/test_index_policy.c tests/test_index_supervisor.c tests/test_mcp.c tests/test_subprocess.c tests/test_worker_session_scope.sh
227 f224 internal/cbm/lsp/ts_lsp.c internal/cbm/lsp/ts_lsp.h tests/test_stack_overflow.c
228 f225 src/foundation/mem_override_win.c
229 f226 src/foundation/compat.c src/mcp/index_supervisor.c src/mcp/mcp.c tests/test_platform.c
230 f227 src/pipeline/lsp_surface.c tests/test_pipeline.c
231 f228 src/daemon/host.c src/daemon/ipc.c src/daemon/ipc.h src/daemon/runtime.c src/daemon/runtime.h src/daemon/version_cohort.c src/daemon/version_cohort.h src/foundation/private_file_lock.c src/foundation/private_file_lock_internal.h tests/test_daemon_ipc.c tests/test_private_file_lock.c tests/test_version_cohort.c
232 f229 src/foundation/compat.h tests/test_platform.c
233 f230 README.md docs/CONFIGURATION.md src/cli/cli.c src/cli/cli.h src/cli/hook_augment.c src/daemon/application.c src/git/git_context.c src/git/git_context.h src/mcp/mcp.c src/mcp/mcp_internal.h tests/test_cli.c tests/test_git_context.c tests/test_mcp.c
234 f231 internal/cbm/extract_defs.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_call_reference_contract.c tests/test_registry.c
235 f232 internal/cbm/extract_calls.c internal/cbm/extract_unified.c internal/cbm/extract_unified.h tests/repro/repro_call_scope_usages.c tests/test_convergence_probe.c
236 f233 pkg/scoop/codebase-memory-mcp.json
237 f234 src/mcp/mcp.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/store/store.c src/store/store.h tests/test_mcp.c tests/test_pipeline.c
238 f235 internal/cbm/extract_defs.c internal/cbm/helpers.c internal/cbm/helpers.h tests/test_extraction.c
239 f236 src/pipeline/registry.c tests/test_pipeline.c tests/test_registry.c
240 f237 internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_unified.c internal/cbm/extract_usages.c internal/cbm/helpers.c internal/cbm/helpers.h internal/cbm/result_compact.c src/foundation/constants.h src/foundation/recursion_whitelist.h src/mcp/mcp.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_usages.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/registry.c src/store/store.c src/store/store.h tests/repro/repro_call_argument_matrix_a.c tests/repro/repro_call_node_behaviors.c tests/test_extraction.c tests/test_mcp.c tests/test_pipeline.c tests/test_registry.c tests/test_store_nodes.c
241 f238 src/foundation/compat_fs.c src/foundation/compat_fs.h src/mcp/mcp.c src/pipeline/pipeline.c tests/test_pipeline.c
242 f239 src/mcp/mcp.c src/pipeline/pass_cross_repo.c src/pipeline/pass_cross_repo.h tests/test_cli.c tests/test_cross_repo.c
243 f240 src/pipeline/pass_cross_repo.c tests/test_cross_repo.c
244 f241 internal/cbm/extract_defs.c internal/cbm/lang_specs.c tests/test_extraction.c
245 f242 docs/CONFIGURATION.md src/daemon/ipc.c src/daemon/ipc.h tests/test_daemon_ipc.c
246 f243 docs/CONFIGURATION.md src/daemon/ipc.c src/daemon/ipc.h src/main.c tests/test_daemon_ipc.c
247 f244 src/cli/config_toml_edit.c src/cli/config_toml_edit.h tests/test_cli.c tests/test_config_toml_edit.c
248 f245 docs/cbmignore.md src/discover/discover.c tests/test_discover.c
249 f246 Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/sql_values.c internal/cbm/sql_values.h tests/test_extraction.c tests/test_parse_coverage.c tests/test_pipeline.c
250 f247 THIRD_PARTY.md internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/javascript/parser.c internal/cbm/vendored/grammars/tsx/parser.c internal/cbm/vendored/grammars/tsx/tree_sitter/array.h internal/cbm/vendored/grammars/tsx/tree_sitter/parser.h scripts/audit-license-provenance.py scripts/vendored-checksums.txt tests/test_parse_coverage.c tools/tree-sitter-javascript/grammar.js tools/tree-sitter-javascript/package.json tools/tree-sitter-javascript/patches/jsx-lone-ampersand.patch tools/tree-sitter-javascript/src/grammar.json tools/tree-sitter-javascript/src/node-types.json tools/tree-sitter-javascript/src/parser.c tools/tree-sitter-javascript/src/scanner.c tools/tree-sitter-javascript/src/tree_sitter/alloc.h tools/tree-sitter-javascript/src/tree_sitter/array.h tools/tree-sitter-javascript/src/tree_sitter/parser.h tools/tree-sitter-javascript/test/corpus/jsx_ampersand.txt tools/tree-sitter-javascript/tree-sitter.json tools/tree-sitter-tsx/define-grammar.js tools/tree-sitter-tsx/grammar.js tools/tree-sitter-tsx/javascript-grammar.js tools/tree-sitter-tsx/package.json tools/tree-sitter-tsx/src/_common_scanner.h tools/tree-sitter-tsx/src/grammar.json tools/tree-sitter-tsx/src/node-types.json tools/tree-sitter-tsx/src/parser.c tools/tree-sitter-tsx/src/scanner.c tools/tree-sitter-tsx/src/tree_sitter/alloc.h tools/tree-sitter-tsx/src/tree_sitter/array.h tools/tree-sitter-tsx/src/tree_sitter/parser.h tools/tree-sitter-tsx/test/corpus/jsx_ampersand.txt tools/tree-sitter-tsx/tree-sitter.json
251 f248 internal/cbm/lsp/c_lsp.c internal/cbm/lsp/ts_lsp.c internal/cbm/lsp/ts_lsp.h tests/test_c_lsp.c tests/test_ts_lsp.c
252 f249 internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/c_sharp/parser.c internal/cbm/vendored/grammars/c_sharp/scanner.c internal/cbm/vendored/grammars/c_sharp/tree_sitter/alloc.h internal/cbm/vendored/grammars/c_sharp/tree_sitter/array.h internal/cbm/vendored/grammars/c_sharp/tree_sitter/parser.h scripts/vendored-checksums.txt tests/test_parse_coverage.c
253 f250 internal/cbm/sqlite_writer.c tests/test_sqlite_writer.c
254 f251 src/mcp/mcp.c src/pipeline/fqn.c src/pipeline/pipeline.h tests/test_mcp.c
255 f252 internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/result_compact.c src/pipeline/lsp_surface.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline.c src/pipeline/pipeline_delta.c src/pipeline/pipeline_internal.h tests/test_extraction.c tests/test_pipeline.c
256 f253 README.md docs/CONFIGURATION.md src/cli/cli.c src/cli/cli.h src/daemon/host.c src/discover/discover.c src/discover/discover.h src/watcher/watcher.c src/watcher/watcher.h tests/test_cli.c tests/test_watcher.c
257 f254 src/daemon/runtime.c src/daemon/runtime.h tests/test_daemon_runtime.c
258 f255 internal/cbm/cbm.c internal/cbm/cbm.h tests/test_parse_coverage.c
259 f256 scripts/test.sh tests/repro/repro_main.c tests/test_git_env_isolation_contract.sh tests/test_helpers.h tests/test_main.c
260 f257 src/cli/config_toml_edit.c tests/test_config_toml_edit.c
261 f258 internal/cbm/lsp/generated/rust_stdlib_data.c src/pipeline/lsp_resolve.h src/pipeline/pass_calls.c src/pipeline/pass_parallel.c tests/test_parallel.c tests/test_pipeline.c
262 f259 internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/lsp/c_lsp.c internal/cbm/lsp/cs_lsp.c internal/cbm/lsp/go_lsp.c internal/cbm/lsp/java_lsp.c internal/cbm/lsp/kotlin_lsp.c internal/cbm/lsp/php_lsp.c internal/cbm/lsp/py_lsp.c internal/cbm/lsp/rust_lsp.c internal/cbm/lsp/ts_lsp.c tests/test_parse_coverage.c
263 f260 Makefile.cbm src/foundation/compat_fs.c src/foundation/compat_fs.h src/foundation/git_env.c src/foundation/git_env.h src/foundation/subprocess.c src/foundation/subprocess.h src/git/git_context.c src/mcp/mcp.c src/pipeline/artifact.c src/pipeline/pass_githistory.c src/watcher/watcher.c tests/test_git_context.c tests/test_subprocess.c
264 f261 Makefile.cbm internal/cbm/callable_sig.c internal/cbm/callable_sig.h internal/cbm/cbm.h internal/cbm/lsp/go_lsp.h internal/cbm/lsp/type_registry.h internal/cbm/result_compact.c scripts/memory-core-baseline.txt src/mcp/mcp.c src/pipeline/lsp_resolve.h src/pipeline/pass_ensemble_routing.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_semantic.c src/pipeline/registry.c src/store/store.c src/store/store.h tests/test_callable_sig.c tests/test_main.c tests/test_store_nodes.c
265 f262 src/pipeline/pass_cross_repo.c src/store/store.c src/store/store.h tests/test_cross_repo.c
266 f263 src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline.h src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_edge_imports.c tests/test_registry.c
267 f264 internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/lsp/cs_lsp.c internal/cbm/lsp/type_registry.h internal/cbm/result_compact.c src/pipeline/pass_lsp_cross.c src/pipeline/pipeline.c tests/test_parallel.c
268 f265 README.md docs/CONFIGURATION.md docs/index.html src/daemon/application.c src/mcp/mcp.c src/mcp/mcp.h src/watcher/watcher.c src/watcher/watcher.h tests/test_daemon_application.c tests/test_watcher.c
269 f266 src/daemon/host.c src/daemon/host_internal.h src/daemon/ipc.c src/daemon/ipc.h src/daemon/runtime.c src/daemon/runtime.h src/daemon/version_cohort.c src/daemon/version_cohort.h src/foundation/private_file_lock.c src/foundation/private_file_lock_internal.h tests/test_daemon_ipc.c tests/test_daemon_runtime.c tests/test_private_file_lock.c tests/test_version_cohort.c
270 f267 src/mcp/mcp.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/store/store.c tests/test_pipeline.c
271 f268 README.md docs/CONFIGURATION.md
272 f269 internal/cbm/extract_usages.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/rescript/scanner.c src/discover/discover.c src/discover/discover.h src/discover/language.c tests/test_complexity.c tests/test_extraction.c tests/test_language.c
273 f270 internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/result_compact.c scripts/memory-core-baseline.txt src/pipeline/lsp_surface.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pipeline.c src/pipeline/pipeline_delta.c src/pipeline/pipeline_internal.h tests/test_extraction.c tests/test_pipeline.c
274 f271 THIRD_PARTY.md internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/pascal/parser.c internal/cbm/vendored/grammars/pascal/tree_sitter/parser.h scripts/audit-license-provenance.py scripts/vendored-checksums.txt tests/test_parse_coverage.c tools/tree-sitter-pascal/grammar.js tools/tree-sitter-pascal/package.json tools/tree-sitter-pascal/patches/unicode-identifiers.patch tools/tree-sitter-pascal/src/grammar.json tools/tree-sitter-pascal/src/node-types.json tools/tree-sitter-pascal/src/parser.c tools/tree-sitter-pascal/src/tree_sitter/alloc.h tools/tree-sitter-pascal/src/tree_sitter/array.h tools/tree-sitter-pascal/src/tree_sitter/parser.h tools/tree-sitter-pascal/test/corpus/unicode_identifiers.txt tools/tree-sitter-pascal/tree-sitter.json
275 f272 README.md src/daemon/application.c src/daemon/frontend.c src/daemon/frontend.h src/mcp/mcp.c src/mcp/mcp.h src/ui/http_server.c tests/test_daemon_application.c tests/test_daemon_frontend.c tests/test_mcp.c
276 f273 internal/cbm/cbm.h src/pipeline/pipeline_internal.h
277 f274 pkg/scoop/codebase-memory-mcp.json tests/test_version_metadata_contract.sh
278 f275 README.md scripts/memory-core-baseline.txt src/cli/cli.c src/mcp/mcp.c src/pipeline/artifact.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/store/store.c src/store/store.h tests/test_artifact.c tests/test_mcp.c tests/test_pipeline.c
279 f276 internal/cbm/extract_imports.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline_internal.h tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c
280 f277 internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/helpers.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
281 f278 src/foundation/compat.c
282 f279 internal/cbm/cbm.c src/cli/cli.c src/foundation/compat.h tests/test_extraction.c tests/test_platform.c
283 f280 src/mcp/mcp.c src/pipeline/pipeline.h src/pipeline/pipeline_internal.h tests/test_mcp.c
284 f281 internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/helpers.c src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline.h src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
285 f282 THIRD_PARTY.md internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_usages.c internal/cbm/helpers.c internal/cbm/helpers.h internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/objectscript_routine/objectscript_common.h internal/cbm/vendored/grammars/objectscript_routine/parser.c internal/cbm/vendored/grammars/objectscript_routine/scanner.c internal/cbm/vendored/grammars/objectscript_routine/tree_sitter/array.h internal/cbm/vendored/grammars/objectscript_udl/objectscript_common.h internal/cbm/vendored/grammars/objectscript_udl/parser.c internal/cbm/vendored/grammars/objectscript_udl/scanner.c internal/cbm/vendored/grammars/objectscript_udl/tree_sitter/array.h scripts/vendored-checksums.txt tests/test_extraction.c
286 f283 internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_unified.c internal/cbm/extract_usages.c internal/cbm/helpers.c internal/cbm/helpers.h tests/test_extraction.c
287 f284 src/daemon/application.c src/foundation/log.c src/foundation/log.h src/mcp/mcp.c src/ui/http_server.c tests/test_log.c tests/test_mcp.c
288 f285 internal/cbm/extract_defs.c scripts/memory-core-baseline.txt tests/test_extraction.c
289 f286 README.md docs/CONFIGURATION.md scripts/test.sh src/main.c tests/test_in_process_mcp.py tests/test_in_process_mcp.sh tests/test_runtime_isolation_contract.sh
290 f287 internal/cbm/extract_imports.c scripts/memory-core-baseline.txt src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline_internal.h tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c
291 f288 internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/helpers.c scripts/memory-core-baseline.txt src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
292 f289 internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/helpers.c scripts/memory-core-baseline.txt src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline.h src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
293 f290 src/cli/client_adapter.c src/cli/client_adapter.h tests/test_agent_clients.c
294 f291 internal/cbm/extract_usages.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/rescript/scanner.c scripts/vendored-checksums.txt src/discover/discover.c src/discover/discover.h src/discover/language.c tests/test_complexity.c tests/test_extraction.c tests/test_language.c
295 f292 Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/lsp/php_lsp.c internal/cbm/php_inline_html.c tests/test_extraction.c tests/test_pipeline.c
296 f293 Makefile.cbm internal/cbm/cbm.c internal/cbm/cbm.h internal/cbm/lsp/php_lsp.c internal/cbm/php_inline_html.c tests/test_extraction.c tests/test_php_lsp.c tests/test_pipeline.c
297 f294 src/daemon/bootstrap.h src/daemon/runtime.c src/daemon/runtime.h src/main.c tests/test_daemon_version.c
298 f295 scripts/memory-core-baseline.txt src/cli/agent_profiles.c src/cli/agent_profiles.h src/cli/cli.c tests/test_cli.c
299 f296 src/foundation/mem.c src/foundation/mem.h src/pipeline/pass_parallel.c tests/test_parallel.c
300 f297 internal/cbm/extract_defs.c internal/cbm/helpers.c internal/cbm/lang_specs.c tests/test_extraction.c
301 f298 src/mcp/mcp.c
302 f299 README.md THIRD_PARTY.md docs/index.html docs/llms.txt internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_usages.c internal/cbm/grammar_st.c internal/cbm/lang_specs.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/st/LICENSE internal/cbm/vendored/grammars/st/parser.c internal/cbm/vendored/grammars/st/scanner.c internal/cbm/vendored/grammars/st/tree_sitter/alloc.h internal/cbm/vendored/grammars/st/tree_sitter/array.h internal/cbm/vendored/grammars/st/tree_sitter/parser.h pkg/chocolatey/codebase-memory-mcp.nuspec pkg/npm/README.md scripts/new-languages.json scripts/package-release.sh scripts/vendored-checksums.txt server.json src/discover/language.c src/discover/userconfig.c src/mcp/mcp.c tests/repro/repro_call_argument_matrix_b.c tests/repro/repro_call_node_manifest.c tests/repro/repro_language_registry.c tests/test_extraction.c tests/test_grammar_labels.c tests/test_grammar_regression.c tests/test_language.c
303 f300 README.md scripts/memory-core-baseline.txt src/cli/cli.c src/mcp/mcp.c src/pipeline/artifact.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/store/store.c src/store/store.h tests/test_artifact.c tests/test_mcp.c tests/test_pipeline.c tests/test_store_arch.c
304 f301 internal/cbm/cbm.h src/mcp/mcp.c src/pipeline/pass_calls.c src/pipeline/pass_parallel.c src/pipeline/pipeline.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/store/store.c src/store/store.h tests/test_mcp.c tests/test_pipeline.c
305 f302 THIRD_PARTY.md internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_usages.c internal/cbm/helpers.c internal/cbm/helpers.h internal/cbm/lang_specs.c internal/cbm/vendored/grammars/MANIFEST.md internal/cbm/vendored/grammars/objectscript_routine/objectscript_common.h internal/cbm/vendored/grammars/objectscript_routine/parser.c internal/cbm/vendored/grammars/objectscript_routine/scanner.c internal/cbm/vendored/grammars/objectscript_routine/tree_sitter/array.h internal/cbm/vendored/grammars/objectscript_udl/objectscript_common.h internal/cbm/vendored/grammars/objectscript_udl/parser.c internal/cbm/vendored/grammars/objectscript_udl/scanner.c internal/cbm/vendored/grammars/objectscript_udl/tree_sitter/array.h scripts/vendored-checksums.txt tests/repro/repro_call_node_behaviors.c tests/repro/repro_call_node_manifest.c tests/test_extraction.c
306 f303 internal/cbm/lsp/ts_lsp.c tests/repro/repro_issue514.c tests/test_ts_lsp.c
307 f304 src/cli/cli.c src/cli/cli.h src/foundation/str_util.c src/foundation/str_util.h src/pipeline/pass_cross_repo.c tests/test_cli.c
308 f305 internal/cbm/result_spill.c internal/cbm/result_spill.h tests/test_extraction.c tests/test_main.c
309 f306 internal/cbm/lsp/ts_lsp.c tests/test_pipeline.c tests/test_ts_lsp.c
310 f307 tests/test_watcher.c
311 f308 tests/test_daemon_ipc.c
312 f309 src/daemon/bootstrap.c src/daemon/bootstrap.h tests/repro/repro_main.c tests/test_daemon_bootstrap.c tests/test_helpers.h tests/test_main.c
313 f310 src/cli/cli.c src/cli/cli.h tests/test_cli.c
314 f311 internal/cbm/extract_imports.c scripts/memory-core-baseline.txt src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c
315 f312 src/pipeline/pass_definitions.c tests/test_parallel.c
316 f313 internal/cbm/extract_usages.c tests/repro/repro_lexical_binding_precision.c tests/test_extraction.c
317 f314 internal/cbm/lsp/py_lsp.c tests/test_py_lsp.c tests/test_py_lsp_scale.c
318 f315 src/discover/discover.c src/discover/discover.h src/discover/language.c tests/test_discover.c tests/test_language.c
319 f316 internal/cbm/lsp/py_lsp.c internal/cbm/lsp/py_lsp.h tests/test_py_lsp.c
320 f317 internal/cbm/extract_defs.c src/store/store.h tests/test_extraction.c
321 f318 scripts/memory-core-baseline.txt src/cli/activation_transaction.c src/cli/activation_transaction.h tests/test_activation_transaction.c
322 f319 internal/cbm/lsp/py_lsp.c src/pipeline/lsp_resolve.h tests/repro/repro_lsp_go_py.c tests/test_edge_structural.c tests/test_lsp_resolution_probe.c tests/test_py_lsp.c
323 f320 internal/cbm/cbm.h internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/helpers.c scripts/memory-core-baseline.txt src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
324 f321 internal/cbm/cbm.h internal/cbm/extract_calls.c internal/cbm/extract_defs.c internal/cbm/extract_imports.c internal/cbm/extract_unified.c internal/cbm/helpers.c scripts/memory-core-baseline.txt src/pipeline/pass_calls.c src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_pkgmap.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_edge_imports.c tests/test_extraction.c tests/test_pipeline.c tests/test_registry.c
325 f322 src/pipeline/pass_definitions.c src/pipeline/pass_parallel.c src/pipeline/pass_tests.c src/pipeline/pipeline.h src/pipeline/pipeline_incremental.c src/pipeline/pipeline_internal.h src/pipeline/registry.c tests/test_pipeline.c tests/test_registry.c
326 f323 src/pipeline/pass_calls.c src/pipeline/pass_lsp_cross.c src/pipeline/pass_parallel.c src/pipeline/pass_semantic.c src/pipeline/pass_usages.c src/pipeline/pipeline.h src/pipeline/registry.c tests/test_pipeline.c
@@ -0,0 +1,776 @@
# Every PR push of September 2026 (one pr.yml run each) with the tier the
# lane analysis assigned it (sel_classify.py) and the tier the selector must
# produce. They differ only where a deviation is documented in `note`:
# makefile-by-name the analysis classified Makefile.cbm by its DIFF
# (source registration / test flags -> no tier of its
# own); a file list cannot show that, so the selector
# treats every Makefile.cbm change as a build-flag
# change: T5-core, full. Deliberately conservative.
# Columns (TAB): run_id, pr, head_sha, fileset, analysis_tier, expected_tier, note.
33464275799 1697 22f90fe883f641c2c460a210a1de3a8df74c4285 f001 T0a-docs T0a-docs
33464725171 1986 9d91891cb5dc6b9d67a0d4919d44d6346799af23 f002 T5-core T5-core
33484752078 1939 e11d5900cd8735fd2ff5ca5308bc028fbe5ed2a5 f003 T3-mid T3-mid
33492041913 1657 7f158e950f874bea6d9530eba16f44b0032f5cac f004 T5-core T5-core
33512870122 1941 35efca813cb2e280fd98168e037cc2a098e365c3 f005 T3-mid T5-core makefile-by-name
33512876012 1971 a5af586d9275249ff9ab8fd7504c9a1d12f7ef85 f005 T3-mid T5-core makefile-by-name
33512878163 1968 9e954104ff5c23bbbb05eccce024b74e6e0a1a6a f006 T5-core T5-core
33525282952 1739 2a2e817bbc11c64755e9d9e3c2e3bb951cf29083 f007 T4-platform T4-platform
33525334366 1817 e7bf16ac616d3d04733caa075a59902fa204d664 f008 T4-platform T4-platform
33529411666 1784 fad564e1e1ad683657ae604bec2c5f0852f19151 f009 T4-platform T4-platform
33530652927 1992 abd82a108b459d70a923d7b4a43651fbeb835912 f001 T0a-docs T0a-docs
33530742647 1697 22f90fe883f641c2c460a210a1de3a8df74c4285 f001 T0a-docs T0a-docs
33533600441 1993 da61c81af243e3277e3089fb474ee5c08fe5c148 f010 T2-extract T2-extract
33533605337 1697 4b844b7d9e782ff26284248c072578215cda3d2f f001 T0a-docs T0a-docs
33541770974 1697 957b27fca50d2c3b1ac86d771733f39612bae841 f001 T0a-docs T0a-docs
33542424474 1811 fb2e38f5bbd45a2504d9daee09fc4c57a8d7db7a f011 T3-mid T3-mid
33542805417 1697 4f3412b78a5fa64ad01cb9ffedea603d101262ec f001 T0a-docs T0a-docs
33542807266 1896 50eaeebeaa2edb2ce14cabbf0d128e47f47b9c5b f012 T2-extract T2-extract
33542815580 1703 359f7360ddd39dc155637076e76a4e855c27611f f013 T3-mid T3-mid
33543233252 1811 b149247a0fd78d912ec906f44c2cf30bae7e5209 f011 T3-mid T3-mid
33549536985 1918 738174f74a2fbc2ad53ee9f4b2aed1eea2214d8e f014 T3-mid T3-mid
33550775043 1996 5e328814fc99ffe588daae4f0f1e8cf5066e084b f015 T4-platform T4-platform
33556880174 808 c19373cd4eba3dc836ccd9b12569cceb9aeab970 f016 T4-platform T5-core makefile-by-name
33557912833 808 909d01dd80de2c2d2b3518b60e33901dd4f284d0 f016 T4-platform T5-core makefile-by-name
33558106767 808 4b33971df0796fe841df867ca202f28a59a5824f f016 T4-platform T5-core makefile-by-name
33558842421 1697 40e5e373ab27bba3129405f346aa5937f9c93850 f001 T0a-docs T0a-docs
33558848235 1895 230f42454d76e103209e3baa54f6b7bf563cd2bd f017 T2-extract T2-extract
33560684540 1998 45b97287c75a0d2f49a28ac900c2dc00d518e594 f014 T3-mid T3-mid
33564199875 1986 b2ac3c58a99c534d4882674b5651deb61296be42 f018 T5-core T5-core
33564348882 1697 40e5e373ab27bba3129405f346aa5937f9c93850 f001 T0a-docs T0a-docs
33564426767 1976 82e1de8cef4121db80c53f4e144530dcf5dcb747 f019 T5-core T5-core
33565344188 1876 9d4ac2b98b9c19633ce2cbf8d9c6bf9edc57bba6 f020 T4-platform T4-platform
33569142503 808 89f1edcb50ecdec1e0db64002f1d8eed07164d46 f016 T4-platform T5-core makefile-by-name
33569301401 1999 2641ec858dff11307d42e60e8e3dccc627d02f0e f021 T2-extract T2-extract
33571644044 1875 25a7aacc87250bff752ff61cd33e9236be5d1310 f014 T3-mid T3-mid
33571903536 1877 62b44519c68b403ea44d9a85ba8ee74cddee66e6 f022 T3-mid T3-mid
33572811960 1912 63f79365e3217d2ef7a3615567bb96627f3fd6b3 f023 T3-mid T3-mid
33574864607 1968 a3e8fa3d08691f050f16d75879fd8a84b73ad76a f024 T5-core T5-core
33576560737 1986 e8204092333ee1c38cd681a81abb36f125288894 f018 T5-core T5-core
33577534823 1999 a739a2d81b43eb6938d014e005b306ab27e02396 f021 T2-extract T2-extract
33578326689 2002 976f312ea24acb8b1840ee5f766f3e2588af333e f014 T3-mid T3-mid
33578871318 1723 b769ad6324f866a580d731f5579a9d076cc45cf0 f025 T5-core T5-core
33580573384 1832 844a36b78aa10f16338d24a1a819ca9a31525129 f026 T4-platform T5-core makefile-by-name
33586571817 1836 2f37f61e8848f31f308bce8623f66dc2fdb50619 f027 T3-mid T3-mid
33588509339 1824 fd73c347fbcfd7f66c210577b4d713fc6ce7780b f028 T3-mid T3-mid
33590570368 1862 0e8fc16abce48346d07af78acf3293a805085e23 f029 T4-platform T4-platform
33593858493 1874 c0173a3362878e3cb9e7bf9b65c7ab79b46e73ef f030 T4-platform T4-platform
33593864739 1871 4772f4715172173559ba3f9130ddd90df4416ed7 f013 T3-mid T3-mid
33596260489 1779 98e386e58b753bcf63f67aeeacbfcb94961d035c f013 T3-mid T3-mid
33599715238 1739 4c1b23477b8e0de27feee74eac11c21a1fd64151 f007 T4-platform T4-platform
33600443796 808 53a97f0f74e34a55076ea3d1f1dc0be562b1c7b9 f016 T4-platform T5-core makefile-by-name
33600512620 808 003f8f4c2efbc22fe72e2dcab7fec5cfd1b42cd0 f016 T4-platform T5-core makefile-by-name
33605426923 1931 5cb90f64adaf9c5a38b591971aa7c09e3d9e958c f031 T2-extract T2-extract
33605453120 1936 27d4d34f7973823530922ec5485ce19a64589e22 f032 T3-mid T3-mid
33605453184 1907 651ae3c2af1a22ec61a498b39350785aee480973 f033 T3-mid T3-mid
33605453306 1913 a6b1a69a7c9a9ba67ac16c88e9c11de3524f87cc f032 T3-mid T3-mid
33605454090 1915 fcbd12ee1ba8580c1c83ce7ba797613ff5af00e7 f032 T3-mid T3-mid
33608282620 2005 256b3d3722247f451a2213023dc36a1ebd5827af f034 T2-extract T2-extract
33610039868 1811 cd8a5ed0d7c5674072f5d422b12302aa6424fda3 f011 T3-mid T3-mid
33610440955 1946 4bfb074b2892ec181e6b16dee3ee984af6dda201 f035 T3-mid T3-mid
33612193345 2006 349aecba9528427fa635975e246f5b8959b14f30 f036 T3-mid T3-mid
33612686565 2002 3d9a6cf5cb7fd39c6b5fb4dfa43011aa70f736ea f014 T3-mid T3-mid
33614840408 1947 ec0ca565a6571cad6e1c90914400b8dd4c2098e9 f037 T3-mid T3-mid
33615574338 1912 97517a4652f376cb6bf096255e235edf456f86b1 f038 T3-mid T3-mid
33615892072 1777 7fedaba331d8fbd241582f3ceeccd70af0ade149 f039 T4-platform T4-platform
33616176807 1949 91a3c6a5173de50fb47c4ebc5a8ba448598f7d5a f040 T2-extract T2-extract
33617264651 1913 dbba7573016e17f20fe5afaafe5cc2cddd98744e f041 T3-mid T3-mid
33617265496 1936 ac46c17fc37f78ae518145e73d51b46ca3013fac f042 T3-mid T3-mid
33617265877 1915 adff62e7f20dd439129d17c5dc028c7069af92f2 f042 T3-mid T3-mid
33620018950 2008 c36b4fbc446f9085704a6073b81ec743fd4180fc f043 T2-extract T2-extract
33620040078 2009 592894a4387a50e1d128ff6a2695ff48f8cc32a5 f044 T2-extract T2-extract
33641828637 2013 40f2722d6166c1f97fa1667c1e63e8c97e6f05f0 f045 T2-extract T2-extract
33653455785 1809 88269ac2ee70ab88f3ee8d84bd509399e3a8f3d6 f046 T4-platform T4-platform
33654490848 1809 b026a6907807f7a411e7da4de45d0cedc2e93dc9 f046 T4-platform T4-platform
33656319321 1819 64ea496a7b3623f5f8b28343aba99535b282d86a f047 T0b-nonproduct T0b-nonproduct
33658603433 1773 572725e61a00b9deda7407971dc9290477170515 f048 T2-extract T2-extract
33658798739 1811 3f08fac5139a4acd3f393e9ca78538c1ba8b94d7 f011 T3-mid T3-mid
33663175876 2016 0e3c9a3d52fbdd234db180894e94f434efce3505 f049 T4-platform T4-platform
33663200103 2017 1e92269d7562d89a816271c4d99904ee53a94606 f050 T4-platform T4-platform
33669707175 2016 e0785b353af1af3812d5d14cad55c839efc5749d f049 T4-platform T4-platform
33683309620 1649 ff8714662728f55a5ff368462fd8daedc80b89a6 f051 T4-platform T4-platform
33687771558 1832 69a9c9005a6d81889cc7fe5e0355e610d2a08e6a f026 T4-platform T5-core makefile-by-name
33688645901 1723 83ca40ced11f51e479d4d868ed167b47cfe993d9 f025 T5-core T5-core
33700059940 1896 c4e902849f8062e37a0f0ddaed1c6cc042780cb3 f012 T2-extract T2-extract
33700061299 1976 7b528b3247257fdc8d503a4af617007bbe3f8454 f012 T2-extract T2-extract
33702992083 1874 4529db7eb56c5793227bdce34723827030796a8b f030 T4-platform T4-platform
33702997094 1871 2283bcb9eb85b17863786608a0c41b0937521b29 f013 T3-mid T3-mid
33703360466 1998 41e649281bb9b27ead5d6ec223a0f0c78924c8f8 f014 T3-mid T3-mid
33705255783 1918 426e415f5427f2fb2e9c6f918cc7cc9abd9c109b f014 T3-mid T3-mid
33705617151 1881 57ef0a6d8662e9854dc5711922f801c3da381071 f052 T3-mid T3-mid
33706085211 1897 2c76563a137876b110cec15b9b6fa08d35069f73 f053 T3-mid T3-mid
33706763915 1880 6b801eec68d6465aad2020de438e629cead99f5c f054 T4-platform T4-platform
33708048488 2019 ad04339030bbfea4e77139bcfa9a87356c1e0e5b f055 T1-tests T1-tests
33727721488 2022 c846ed7b37331696543e71a70a8e5c8dd301467d f056 T4-platform T4-platform
33731062466 1874 c38d19139ad9657ab563951ccbf83dcbd1bb2435 f030 T4-platform T4-platform
33731071275 1871 adb49b3a28826e6eef871b1777b63668199c0463 f013 T3-mid T3-mid
33732861010 1811 da09b73bc0c03f3909a0153d75b8479220c31e2c f011 T3-mid T3-mid
33739760521 1811 e79a7d73b45644d86ab3ca3c7af84aa04aebe75f f011 T3-mid T3-mid
33740406928 2024 1770d68a2c934501b889e625dc94ef933e3a1815 f057 T3-mid T3-mid
33745916176 1811 bbecc9b9e92af203a03433e8cb6968e1e8d21cb2 f011 T3-mid T3-mid
33751987784 1996 7c4b5fc8a7c9ed2f1cf3b675bb821dbbccf2c1bb f015 T4-platform T4-platform
33752032210 1811 b64e2170009159a5111b1339312512f4e66b1b57 f011 T3-mid T3-mid
33754008677 1874 956489dcf88d0fd571efc493e6a93bb1d96760c7 f030 T4-platform T4-platform
33757191193 1767 06576511ce84b54b2177a75ae82c3af7dc897160 f058 T4-platform T4-platform
33758229230 2026 73f4a432e4ac1d575635a490d96e0f5ea588344d f059 T4-platform T4-platform
33763283492 2027 0ac290e2e243148b9e84bb682cf1101fe38bc6e3 f060 T4-platform T4-platform
33765101593 2028 1d38be3331576f50ce555d99717974060a6a16a8 f061 T5-core T5-core
33765957247 2029 be66a2d2c6f1538a4a831eb2c91622997be5b5ec f062 T4-platform T4-platform
33771464451 1809 e65378e6a5faf49abd7a0987994d7c98a9f834ad f046 T4-platform T4-platform
33775413774 1809 d402623c20e1dbff62e560a865fd4955798dfbfe f046 T4-platform T4-platform
33779258079 2030 3507d9555d73aa746cde23139588081c6c02d2e2 f063 T4-platform T4-platform
33799475629 1819 b25b1f65b402ba5395da846f05a306746055594f f047 T0b-nonproduct T0b-nonproduct
33799483900 1918 74700067c48b967edc4ef723b658a0fbb04f291c f014 T3-mid T3-mid
33799487158 1880 e63292a8d4036fc2618ecbee973bd6cccaaaffc4 f054 T4-platform T4-platform
33799492841 1881 4d4af28672729a5c1326974684728c9ee150926a f052 T3-mid T3-mid
33799495512 1897 429141261daefc04fc94f35300f7dbd899ed28f2 f053 T3-mid T3-mid
33799501343 1767 8a74d814e5149be17f2fc70f9a59d0c166604273 f058 T4-platform T4-platform
33799505794 2024 380b1c5af1d5f3e94248752266031b3035080793 f057 T3-mid T3-mid
33799508762 1811 f47cd47afe9e7e144f3c6d49df37c5b53d3d1cdb f011 T3-mid T3-mid
33809990745 2040 12f5e2c3636b694cbcc76b197815a983349fff5a f064 T4-platform T4-platform
33816056229 2041 588cd5543a617c73646be61de6926b9ac7004064 f055 T1-tests T1-tests
33816348076 2042 9933548586760fd81f2cf76673ec42c338ff62eb f065 T0b-nonproduct T0b-nonproduct
33818108315 2043 7109e5e7b013f43230f735f93043ae608be5d98f f066 T4-platform T4-platform
33831172851 2005 ec28e66434bc03ec33dcd05563b3bfd7f58efe86 f034 T2-extract T2-extract
33849949280 2047 9104feb67f78959e64ddff78d236e0dea475a434 f067 T4-platform T4-platform
33850387435 1976 aa3627d3bfb38627d700043a599622bb0e80ef2f f012 T2-extract T2-extract
33850532415 1709 2efb7a6323f992d5c407cc85e37b6d167c184ca1 f068 T2-extract T2-extract
33850538524 1721 dc703dad4a706a0f2c8130a869922677dba09ae3 f044 T2-extract T2-extract
33850542851 1730 90ba8990764674b2eeaca6fa372c654a63a04f51 f069 T2-extract T2-extract
33864581134 2048 f404dfbc7ae47f9912b18a0554a5e5d91f65be1a f070 T0b-nonproduct T0b-nonproduct
33868849245 1816 2ea73059f894df564e21be409908c2b5d3d53b37 f071 T4-platform T4-platform
33879400189 1996 8a1f2503c33bdd9a4d87782493af5640a505594f f015 T4-platform T4-platform
33879404796 2041 e74f041b3387e89fc3d3bc727cae772516099e54 f055 T1-tests T1-tests
33879411345 1809 b435bd87b6d0e6a8da43c2964829927fff331f5c f046 T4-platform T4-platform
33879415252 2002 c6ff57df0c216dc0fc86442f111607d8b3bd7ef8 f014 T3-mid T3-mid
33879421262 1741 fbda6bacf78b47e4c498dfb44ae653f9d10a4add f072 T0b-nonproduct T0b-nonproduct
33879427336 1742 e63bc6f06a03e93fad9e962c9b9aaf4b0309306e f073 T1-tests T1-tests
33879430001 1976 6f7a4af4a3a1716a5aeb5cae60a112e2231195a5 f012 T2-extract T2-extract
33879554682 2043 7e47043acce9bdfd87e7becc7fe7a348b02753ec f066 T4-platform T4-platform
33883643915 2042 6b0c44afcfeae65eed82e2dbbd58f00b043f21f6 f065 T0b-nonproduct T0b-nonproduct
33883715988 1649 f195c6aa81f425882626cca1186116ec64a0143e f051 T4-platform T4-platform
33884431041 1649 89186a26bcc37ae51f943b785270aa66716c832c f051 T4-platform T4-platform
33885636468 1933 8d46d2586255fc85e35f00ff8b814424b140e623 f074 T4-platform T4-platform
33887517545 2051 e2195dcb8f6fba33b72da60c1f3fffc83cdcd714 f075 T3-mid T3-mid
33891234168 1874 8069c7cd4c73ff7ea4513d38d7dfd08c350472a4 f030 T4-platform T4-platform
33892259683 2055 36bee9039848829c7d2271c44a46515cdaa8d870 f076 T5-core T5-core
33892321505 2056 24f54c0eb64c516941abee2ad0d7de758b34ac86 f077 T2-extract T2-extract
33892540012 2055 132e8fc32309e843ccd97138c8f032e317333673 f076 T5-core T5-core
33910645341 2051 1067e519c04f6273dd6e2b95b16bd0a9db4c3056 f078 T3-mid T3-mid
33913569180 1851 20c7dd3dbbba55770b82a99f6fcec6120f362873 f079 T4-platform T4-platform
33914255147 1914 7b92d2a6218936767b112081b9aa4e79d5b81dd9 f080 T5-core T5-core
33917205956 1849 39c74bdd5c8b5dc3bcd43f962195add0ba794150 f081 T4-platform T4-platform
33918309198 1796 ad479b61e9f23c01606f42c7d44ca174f104a2c8 f082 T4-platform T4-platform
33921848127 1597 335279ee403922c0b84c3972c3fc4782ffe878bc f083 T4-platform T4-platform
33923008470 1597 0094133cca7d92e587c4d876ccdb37140515fa7c f084 T4-platform T4-platform
33923354326 1766 797a11a15eff6154428aeb268c0b55edb5647a7a f085 T3-mid T3-mid
33924300711 2059 e0f37d7cb305cd2ff1d709c3ba1198cf950e83df f086 T4-platform T4-platform
33924381537 1597 3cd0043d0a3dc282c6a36ae0a262ebe02a4cec0f f084 T4-platform T4-platform
33932138466 2060 3a8b8e82120872a56ca4d0157260569d90abe85c f087 T4-platform T4-platform
33934626037 1784 4cf2acf2b7483ffa0fe3918f1a38e6d72d08b743 f009 T4-platform T4-platform
33935475331 1597 b900338a71c7395ade83c4e371ca277f2cb5498b f084 T4-platform T4-platform
33940288019 2062 9b4a3bc91e2f4d50e00deccf236c6b0db177f4eb f065 T0b-nonproduct T0b-nonproduct
33941972045 1597 10d05b38a63ff1efa62bfe2d419b01c72bcb2bc9 f084 T4-platform T4-platform
33944160492 1597 a15d99e39fd9ea04722f064ed02dc726b13d2943 f084 T4-platform T4-platform
33947308282 1874 9ae4ba7648101603eada6e7cd54c6cdc284617f6 f030 T4-platform T4-platform
33947974349 2063 f8132b42ac9ee1fde464866b5f3b08939528f267 f088 T0b-nonproduct T0b-nonproduct
33948072527 2064 3979f12a749232e1f934949f58f68c83a8b66014 f089 T0b-nonproduct T0b-nonproduct
33949446878 1597 9427f435158662074d6576053a6675a403696543 f084 T4-platform T4-platform
33951152502 2065 7dfd2c064911a7f2b246b7edd610fab4f993240c f090 T3-mid T3-mid
33951945765 2066 4b73a518d720bbc8773970402ca96f5661726b78 f013 T3-mid T3-mid
33963617997 1874 41f52a0f3b948468c468ddcca36489eb5f42fff3 f030 T4-platform T4-platform
33970672289 2063 3b173288feafebac3f0845f53e481f69adcd70b1 f088 T0b-nonproduct T0b-nonproduct
33971609329 1784 b9da6dca03126ef0c8cacccb3321dd5d69ccf34d f009 T4-platform T4-platform
33979110285 1822 7fbaf02904363d38c88f9b749b4523a614feecd5 f091 T3-mid T3-mid
33979848957 1759 683d4bc312625a3d26819635673529ab02a0d0e1 f092 T2-extract T5-core makefile-by-name
33980369725 2056 4f82a1e56f0eef435fed0eac5c8dd06b5b30c2fc f077 T2-extract T2-extract
33980647419 2065 3e1a22897ccea3404b95126112009e8b1a719a39 f093 T3-mid T3-mid
33980897876 2065 73c509f4246b0f97ca772b6d9a3159181a7fa6b6 f093 T3-mid T3-mid
33980899537 2066 c4bec4a6b38ed66ccd97df06490e051d3d9d81cd f094 T1-tests T1-tests
33982832791 2067 b7e18c8940d2bb58d85339d27da0754e5aba92e5 f095 T3-mid T3-mid
33984123524 1716 439659b333895a49174dca1fe4343f356616210c f096 T5-core T5-core
33985769258 1894 b49c7a736c0789069e5c2d07a301143e382af53c f097 T4-platform T4-platform
33986188532 1739 b51411753e9423f1c67f810ecf54f9fce903cefc f007 T4-platform T4-platform
33986691207 2069 3b9052c955a9aafbb3baff874a93bba19afeaace f076 T5-core T5-core
33990039141 1941 b04f54506ab42eddcbd89232a0d47d8355edd596 f005 T3-mid T5-core makefile-by-name
33990592318 1971 f89f01e9456c8241a28fb909d930ec8d88b23239 f005 T3-mid T5-core makefile-by-name
33993950950 2059 3007dbf8426764a92ace4da111836b86bec6b508 f086 T4-platform T4-platform
34001348423 2056 e384f88f90d1f13ac95220f15de72e888a654c98 f077 T2-extract T2-extract
34001350759 1822 eb3bd818b597b9c1f317906b350cbf323f2a4995 f091 T3-mid T3-mid
34001352917 1716 26682d58c7595c4166d7b7d274885115f2dac324 f096 T5-core T5-core
34001355464 1759 b35f73123302d8d101323c3621e6fe755d4970c6 f092 T2-extract T5-core makefile-by-name
34001357884 2069 f8951524252b71be8d8d3ea1f86d03eb97d6e9d5 f076 T5-core T5-core
34001359042 2066 7da8bf15838c42f8e3f366fc2188ca63f2b61942 f094 T1-tests T1-tests
34001473433 1739 9a5e3f36a9580f76211300faa8b7ed5942169b47 f007 T4-platform T4-platform
34002279390 2070 c1b9c451c7f124936e635835ca4dd0cf8bf7d5b3 f098 T4-platform T4-platform
34003689551 1860 a44732b6f8ee6409cd12b135f14867f1b5ecab33 f099 T5-core T5-core
34010341126 1998 04ba2fa368db12a80091315bfa649626a36145e1 f014 T3-mid T3-mid
34011344933 1968 17b6dfe68b65ae7fae797487c69efdf110b0cbae f024 T5-core T5-core
34012479012 2065 c72fe3222a5f3caac2e87636252931f3cfce2619 f093 T3-mid T3-mid
34012788868 2041 874a972fb7c4eb06e739dce0a390fffb85d31f83 f055 T1-tests T1-tests
34012788874 1976 eb80a4ecff04d8bfacef20e1aa2ee29445a782ff f012 T2-extract T2-extract
34014013202 1874 a3500040e69af8284e3cac1398e00ae3d00a0524 f030 T4-platform T4-platform
34014527172 2065 60449f895dbaedb4e74c4f155a8e214d78486e12 f093 T3-mid T3-mid
34018899805 2072 b0f9398d59c0c8a5bb03b80eba84d30e630160ec f100 T4-platform T4-platform
34019712929 2072 06644428b6d0a6295414d23d2c0613f29a73e1d7 f100 T4-platform T4-platform
34019861168 2072 bae6977e91daf703b812136c70556cefe4e3c283 f100 T4-platform T4-platform
34035459918 1968 f3e1253859c299d194c608d91d5c89065ca0c9ff f024 T5-core T5-core
34037162475 2073 5abd77f5d9580d75f8600914b46495be21e9a4a5 f101 T1-tests T1-tests
34037415003 1971 9540d64c52aecf89ce8b0524b8fea69c2aafb0e9 f005 T3-mid T5-core makefile-by-name
34037489952 2056 9cb2e9dd1c9fd4e6b3cb9f939ec46264b05c75ab f077 T2-extract T2-extract
34037491375 1716 8ab6bb6edd69a4ec64df07c3910a5c035b42c937 f096 T5-core T5-core
34037493874 1739 53f55ebe72870695c15ace98101df5af6eeef38e f007 T4-platform T4-platform
34037496632 1759 bb21edbbbba442364b88598af97478a97e62243a f092 T2-extract T5-core makefile-by-name
34037543448 2041 99fa463023fe63520d5a23c29f777b9d91d553b7 f055 T1-tests T1-tests
34038315061 1941 ddb6533cd9c16e8e2be12aa791521420fb427881 f005 T3-mid T5-core makefile-by-name
34038319912 2059 306c02d44e9a464faf68640f183fa6e82fea0907 f086 T4-platform T4-platform
34038901925 1971 ede0e3b111054c165b0237124084894b45e0e052 f005 T3-mid T5-core makefile-by-name
34039040074 2070 9846c3f1711d557c78426913efaff340e37feb4e f102 T4-platform T4-platform
34044235267 2074 6db22aadb6e5a92b9b193608e2ac3f420e461ec3 f103 T3-mid T3-mid
34044659567 1998 c8ea1cad0dd34e6f6f8c542014a1a22885f67ee3 f014 T3-mid T3-mid
34044858364 2075 086422020f5fd9396b46384b10413cdca6994c96 f104 T4-platform T4-platform
34044911261 2065 d550dcbe73558bb773211a0ff9463daff91721fd f093 T3-mid T3-mid
34045088264 2076 a08b9ecbbda54793275cbd4e93fecf0ad4ff1c0a f105 T5-core T5-core
34063281067 1739 1d35def402f586b4c18bba41de33c881eda8cfa2 f007 T4-platform T4-platform
34064340654 2079 e410c86f270a013d79639577d82009698b207e8b f106 T3-mid T3-mid
34065487385 1998 98899ba5d62c9a66d038b24f3857b137d03d44b0 f014 T3-mid T3-mid
34066407580 1716 e3c0010267e783b4270e0ac7c8ee6332b61aeabb f096 T5-core T5-core
34066408930 2076 1b89228d90b32ddd46f494df8755f496f6aba030 f105 T5-core T5-core
34066410797 1759 fbe9eae11c237459da50a06ebd83f709d655d491 f092 T2-extract T5-core makefile-by-name
34066413612 2065 8eb63ecfe79ea1f33fd911b8913a8b704ecde9d8 f093 T3-mid T3-mid
34066415650 1739 76d0382f8f958238119c8e36bbdd204838d15db5 f007 T4-platform T4-platform
34066415871 2079 dab58c0701d8806ddac2783949f1a478225f80bb f106 T3-mid T3-mid
34066418484 1998 a143b0882d0457f2f56b2d734128f914325d555f f014 T3-mid T3-mid
34068996256 2082 16a739d32314faeee5539386a92afd2363dc1879 f107 T3-mid T3-mid
34069613782 2083 77a0c7d94156a43f19047330c7356be4ee12cba5 f108 T3-mid T3-mid
34071276309 1874 4ef5672c53777f9dd316510dacfa3b88e2166fe2 f030 T4-platform T4-platform
34074515665 1894 088fedb2d25b869621fd93db05b2f3aa69e30e65 f097 T4-platform T4-platform
34074587051 1894 4d3d06ac2aba832ba774cad396a3f445b290f0b4 f097 T4-platform T4-platform
34081576339 1971 e5c17de6217ec5938d18d8c51e2c0d7ead116a19 f048 T2-extract T2-extract
34081831353 1968 2b1dafd83d6df8b0ad8a064e1b23bc27ecf4de7d f109 T5-core T5-core
34085524473 2084 4e0b7e1d168744438f0602735c4fee3970f3caf1 f008 T4-platform T4-platform
34086907163 2082 d8dbf01c3f90257d98c7e23a21c5c6f7f64d3a4d f107 T3-mid T3-mid
34090735558 1723 c8e7f7e3cc26f1faf8eaff5889d6ff0abd75ae9e f025 T5-core T5-core
34090831729 1724 70b411d5cf392e1ad6528e9f78d391304368ee30 f110 T5-core T5-core
34090999743 1725 8152277aebdd974badfbe0ffce13dfd231a23732 f111 T5-core T5-core
34091031555 1725 980934b2a2cf4c1dd32163894b500e0c96587bc6 f111 T5-core T5-core
34091168476 1726 fa11ee4193bb7537e18865fb649d1e73e8be1db9 f111 T5-core T5-core
34091245271 1727 f9a7e1fece6264d1677ed5dbbc2894553f7ecebf f112 T5-core T5-core
34091261353 1728 4602cb100fb0dd404d49531158204fa3f00d9ac2 f112 T5-core T5-core
34091378540 2072 c72cb8bb950019b5451325e52940da956250f0d0 f100 T4-platform T4-platform
34091504731 2072 bc945a4d6c6716ee22d314b6b53a7d50ed007081 f100 T4-platform T4-platform
34091593211 1725 ca6a5d5bc408d59af6ae9ca1edab0d169664092c f111 T5-core T5-core
34091642820 1726 f870ce4cd80c0882568ba19438a3cb6ebb2e43a4 f111 T5-core T5-core
34091649819 1727 8d4c416579db0148869f3f5c7ffed3e0e90c4417 f112 T5-core T5-core
34091656935 1728 37e213860e8f8390f54880bfff717345d2da5689 f112 T5-core T5-core
34091819685 2022 84fffefa48cbb60ca1533d2433921787c8ea1031 f056 T4-platform T4-platform
34092213233 2072 838fb7090f3a08920363e9ee793824b4d6202bef f100 T4-platform T4-platform
34098390879 2085 9f57edfa576072398728eda2c6fc2585d2ecb063 f113 T3-mid T3-mid
34109049475 2086 b9d550b6f3e1c08ce6cf59140601205d609dcd52 f114 T5-core T5-core
34109055503 2087 45b28f0b11d3e74e28e492c6c234a0f7ed0b30e4 f115 T5-core T5-core
34113927999 2086 2082b2d2890ceb007e04b9ba77129d14a8f4df52 f114 T5-core T5-core
34113933101 2087 9a7445e93de55eaca53855057ce6ce1a6f234564 f115 T5-core T5-core
34115081932 2073 8e29e1568f442ed9422e0fcd24d2594ae0ba7893 f101 T1-tests T1-tests
34118689934 2089 dc34e63a0a0aa29cb70456b692733124aba65350 f046 T4-platform T4-platform
34126118251 2090 6b56d78e1bbb5715c9d0c715453244187bd299bc f116 T2-extract T2-extract
34132427862 2073 aec9bfe3751d8c1f2eb2cb48d8fe584b9d3fd973 f101 T1-tests T1-tests
34134175243 1725 8d57fa45ae548e23b11083072447a62ff053da86 f111 T5-core T5-core
34134183283 1726 c263f48d0307540b0837726ba5a3c2cd9d2ea012 f111 T5-core T5-core
34134191503 1727 94119b3803e276a4cc45f4b92ddaf3d1377e3889 f112 T5-core T5-core
34134201351 1728 f588bc374ab91697ccb4467f5df9686ada5cb62c f112 T5-core T5-core
34138662959 1784 85482e3c599d454d76ccef5e862a5dc6c38a898e f009 T4-platform T4-platform
34139187715 2091 b974adf6ed9b82d8669d1a65b1757816ce0d12df f117 T2-extract T2-extract
34139262637 1784 a21562666317f752f9921109a0c70427f3febe04 f009 T4-platform T4-platform
34141564461 2092 d1abca20c091b9623187c5cfc61f251e7bd7796e f118 T4-platform T4-platform
34143404498 2091 ec56ea8f60f0058bd1cd0fffb38cc0153b99f895 f117 T2-extract T2-extract
34143899077 2092 1f08f82adcd979ba2f0efb38a1ccfed13901f76c f118 T4-platform T4-platform
34146676636 2092 4f8290aae42ad3f2ca4767089057e74756311743 f119 T4-platform T4-platform
34159409001 1561 b939a2d6ee76e482288a0dbbf712cd94d0d57164 f120 T3-mid T3-mid
34168455959 2094 7219a24cc692500cdeb5cc192adbd47ac8bb6ee1 f086 T4-platform T4-platform
34168458726 2095 d3eb7e9823b43a8e2e5917d2d234367c1f88436d f091 T3-mid T3-mid
34168461878 2096 9e1faa7327f8192101f16d8d6d81020937475166 f121 T4-platform T4-platform
34180076668 1561 6875483ffe40017f79884872a325e2ff6b37befb f122 T5-core T5-core
34180195791 1561 a7941a4d2094aa6bbb0683e3a17371a0463de209 f122 T5-core T5-core
34186497464 2097 d653052508998144e39357a37f72a12448c769f1 f123 T4-platform T4-platform
34187802611 2098 5f9ac15818e3bbda9e031db54867dfab2b6c531e f124 T2-extract T2-extract
34187888044 2099 95f252447de4ae08707b199f02f296f28f8dcb21 f125 T4-platform T4-platform
34188664789 2100 2a47a6556cc14dd5ac036b3c2f3225c9925cdeec f013 T3-mid T3-mid
34190172584 2060 8622d6a0ed4935aedaa336d216d8b6b269732de5 f126 T4-platform T4-platform
34192737413 2098 f6c310c08e6dad38b92e953c85c7c5876f6df839 f124 T2-extract T2-extract
34194384003 2097 d9dec5dd3b334a514113670b42732ca9ea4a15de f123 T4-platform T4-platform
34194639858 2100 669f6a3741ef7b875aa5eaeda317acf1900bcd33 f013 T3-mid T3-mid
34195390569 2101 0c1d6f68dbf3f2c8215ae9c52003d785ac874a22 f127 T1-tests T1-tests
34195424010 2060 3c854ae217c4c5d2f48d8aaeb136541bb0d8b52a f126 T4-platform T4-platform
34196235240 2103 9f876f70ed13636a8f9a25ba043e8f682c6688f6 f128 T4-platform T4-platform
34236364592 2105 22e0335d94552dc1a9d9ef0ee8221e7665608209 f013 T3-mid T3-mid
34240237866 1784 18b2f8670c64fd83221617c5e7970fb95af17454 f009 T4-platform T4-platform
34244310689 2105 dcb10fe902905926eaf9325bb03254dde9cea6ab f013 T3-mid T3-mid
34249699686 1823 b09ef3df3289e74bae129df4df29886fefc75484 f076 T5-core T5-core
34249700997 1823 b09ef3df3289e74bae129df4df29886fefc75484 f076 T5-core T5-core
34253442941 2109 e65fffc5b1ac269c16d4371fc8c1ccbd102e267a f129 T5-core T5-core
34255410324 2109 840de91e384d2ea36dc2360ee12ace26d25b680f f129 T5-core T5-core
34257609138 2111 7b4eb4a00ac600060be4836ee4e1bbfa7bbedf24 f130 T4-platform T4-platform
34257629348 2112 21591d51168d26f931f64d62edc36013cd1f6249 f131 T4-platform T4-platform
34257632907 2113 00060cec854dbd38d7864aeb95a2d8ba1baa8ab2 f132 T4-platform T4-platform
34260896139 2110 d40ed1a6bbc968bbddf90abd3b38e74a131a1b5a f133 T4-platform T5-core makefile-by-name
34261770203 2113 0567dc70a097f36a953368978d6728b0a412b229 f132 T4-platform T4-platform
34261855843 1768 e8dcb28ba719e4272f605f3b961422bb1f3c3fad f055 T1-tests T1-tests
34263433901 2110 7bf366f63b3fd5211cec19e483ee2ed27145ca14 f133 T4-platform T5-core makefile-by-name
34263688699 2115 013e1f7ec7d4b981a1cd28bf35d5767c195f8447 f134 T4-platform T4-platform
34264299704 2116 3a4160b4610f83ea26daf69f1ffe7cb898bf48ad f135 T4-platform T4-platform
34273160491 2119 8135be28a764fad4ee55abe466762bbda7ebab74 f136 T4-platform T4-platform
34274955966 2116 7e73b48ef155a0e14a6e058be56287232e3976ea f135 T4-platform T4-platform
34275249072 2111 91e31211df9dd61459617d857d46b71699e34df8 f130 T4-platform T4-platform
34278140735 2110 a3c24a71b4c28e805b912f9f928b6b0b151f9495 f133 T4-platform T5-core makefile-by-name
34282381014 2113 ecfb9642e153ffc656d0bc2344b17e9d454ff916 f132 T4-platform T4-platform
34288397854 2115 92cb3b9a4bebbc79bd283e447104669ca068ea64 f134 T4-platform T4-platform
34315414268 2116 e45d705105a63f2fd9b35ab9e37ffbc640be8231 f135 T4-platform T4-platform
34315782814 2124 8134712dc43b159fd863244ce53ba23641f20f8c f070 T0b-nonproduct T0b-nonproduct
34327374288 1723 21ae02154a198265d7dbed13af4280aa2b1e5690 f025 T5-core T5-core
34327382260 1724 87da4422c34feb4df8ff369dab8d770c33d142eb f110 T5-core T5-core
34327387577 1725 d82f760040412d51d424f3c287d277e89d4fb199 f111 T5-core T5-core
34327494146 1726 9380b22a24b3ec85482fd209475f5b1641b5f699 f111 T5-core T5-core
34327502315 1727 579336cbdaa6493f47ed4de10f64387b6f7f16b8 f112 T5-core T5-core
34327526954 1728 9d5cfa53098003cb68bf1812a94ca51f5e046056 f112 T5-core T5-core
34379806132 1823 5e5e433d94880c283400215ba79094225a8b33df f076 T5-core T5-core
34392601650 2116 9ef7da8cbfba8de5526ccd05e7196f5fa73af8f1 f135 T4-platform T4-platform
34392735468 2110 91c63be5d4e41d44e0746ef533af4a0b1967d6b3 f133 T4-platform T5-core makefile-by-name
34392998828 2136 557cb0102863e66cbd5cc622509a29a4964d1831 f137 T5-core T5-core
34393093741 2137 4fa11d1ab0b5807a7eaed645a6e3675f0663def8 f125 T4-platform T4-platform
34408696449 2138 8116672a7e3a02aecb5cb0e6fd9e685cd5066fe1 f138 T4-platform T4-platform
34413434571 2139 df579830f3941e5ebaef73dc87d183aee358fbcb f139 T4-platform T4-platform
34414749652 2139 618849512d01a3d14fcb704532911f9d02c23436 f139 T4-platform T4-platform
34415791973 2139 62bafb76b380d3af1cd1d6228b4afea0082c2a7b f139 T4-platform T4-platform
34433093443 2140 a1d11e79087bb1284d6126b02b22c3ce7e72ab07 f140 T4-platform T4-platform
34446903088 2089 8f3c15044b8200a15ee1895c5127f4bf117da493 f046 T4-platform T4-platform
34456518090 2160 f7b56b900d4726221de2882d5a5d076b29f6758f f125 T4-platform T4-platform
34456521294 2161 c8e244d8ff1d51e57c9d757a8e6bad80dc67f107 f091 T3-mid T3-mid
34460157213 2159 8d353a573dcc4be7ae69a74685eeb819a5626b5f f013 T3-mid T3-mid
34460157936 2158 b5f119c3f27bc3601bc048b16bbfaf57af3908f2 f141 T2-extract T2-extract
34464851279 2161 6790e1ac0fc134360cda6544befc1af2e29b9605 f091 T3-mid T3-mid
34464853698 2160 b3af44832790fbc6dd6a7e09d18362f55149a4da f125 T4-platform T4-platform
34465154636 2164 70926a2956ce986bfacbf6e7ebfd398fd91ff266 f142 T2-extract T2-extract
34465653349 2164 9115f5154383bd091e125c956aa4f241ebac1c7b f143 T3-mid T3-mid
34470429949 2159 7e111f877b85b79efdd371475d7f71e61174f95e f013 T3-mid T3-mid
34484066063 2092 b2bb18cfc79e129f50b5da940413e5735bce09b1 f144 T4-platform T4-platform
34491583983 2092 b317506b5a62a3383e763011e5b148a5afd92a89 f144 T4-platform T4-platform
34491670375 2092 b037b488be4620a5e92424e7e390ee625cd7e5af f144 T4-platform T4-platform
34524166740 2170 5fc107a0a61419faf0a2495ab923aba25a37ddb5 f053 T3-mid T3-mid
34528400072 1823 ac75e587425c60154dbcaa7392c0522764c93647 f076 T5-core T5-core
34551561701 2171 4842e11b2d9f5e6c2ced39d12dc487bcc7d915be f145 T3-mid T3-mid
34595459266 2030 e65bc6b71e32d20aeafb42c00b6097a0e2ded2c0 f146 T4-platform T4-platform
34595482014 2030 eeff3f19ff97d60af4f34298aa38a652fe0705b2 f146 T4-platform T4-platform
34636198205 2179 6091db7104bcaa0b61566272290e04ce448851a7 f147 T4-platform T4-platform
34636557089 2116 f6a29270b72a5373e4d949ed3f4078a40830c57d f135 T4-platform T4-platform
34640188970 2180 527016258e0da33ef883966f618609f4cf58abf8 f148 T4-platform T4-platform
34649361704 2139 53700ee1c72f1a5c2551a83c74d20278e4ac664b f139 T4-platform T4-platform
34649395431 2137 ca36316c6b34a6081de24d69699665a20b16de0e f125 T4-platform T4-platform
34649399412 2113 ca86fdb057b3d7f6d31e179ccbe64629fb1b0921 f132 T4-platform T4-platform
34649404928 2110 33534653f10c052f2a5c7f1953756aeb10499be9 f133 T4-platform T5-core makefile-by-name
34649406520 2115 78a6d19f1eaaa73054639ab0f14c372c53cd1bb3 f134 T4-platform T4-platform
34649409769 2111 951b9ab022df391410528fe98769d091c203fc60 f130 T4-platform T4-platform
34651276769 2181 c8184ed94887a6b5e130726885b44a07c63a47b7 f091 T3-mid T3-mid
34656706072 2182 14bacf8919b64bea19b537b74c894f0f82783bdf f149 T4-platform T4-platform
34676336284 2185 eb5e34cfc141454e849daf413f648e73f74c455a f150 T5-core T5-core
34676377540 2186 f6281874bba9a78ac7543564ff8f76b5262daeef f151 T0b-nonproduct T0b-nonproduct
34676429093 2187 cea35fcc0520a0ba95139bd0210052f09b9cfa20 f150 T5-core T5-core
34696991657 2115 9a460b389ce3002ac164605c3627bb86aa3b9be3 f152 T4-platform T4-platform
34700514102 2188 734906379f235c07cce960ce6a57746ea3be48bb f153 T5-core T5-core
34703996478 2115 fa99ff64eb6579fb15f3aae1aff7da349cb2999a f154 T5-core T5-core
34708249832 2189 22cdf7d03db87bba19a76a072dd517dd88a54d40 f155 T5-core T5-core
34708361984 2190 8c2692c1ee32284c813c05746e833dea5dfedfe0 f156 T0b-nonproduct T0b-nonproduct
34710253103 2189 1ad52f5fb1b3d96255756de01ce30912b1899e09 f157 T5-core T5-core
34712092850 2191 27cacee1c2d3022fa0fe915b03ca4310042b7514 f124 T2-extract T2-extract
34713347879 2190 044fef7957d48a097b48f4ac3c505aa67de8164f f156 T0b-nonproduct T0b-nonproduct
34713405483 2192 9073fe4469bd222ca01bd48249b52bbf7ed5710d f158 T1-tests T1-tests
34720983404 2076 2b80b31306b3dbb9ac8bc0ad50372f724f1dd243 f105 T5-core T5-core
34725488475 2194 09c0e88a6484c5fb696d7fc7b5a5e3f32b8cba68 f159 T1-tests T1-tests
34729075944 1561 e0b9642010cbf003bef9b8b29ba4ebb02b38171c f120 T3-mid T3-mid
34729170802 1561 789c60c86bc75b342c52ddedcaf174d12ccdfa73 f120 T3-mid T3-mid
34745069386 2195 469c3dd996f23cdb629f1bbd6a751bc0f29a99f2 f160 T1-tests T1-tests
34746469011 2195 f9038b84eb3bf82c2dff4cc4ea7f1ec1d8d13f01 f161 T4-platform T4-platform
34747474867 2195 d21e6cc762a1816d8c274174c9bba01601b888ee f162 T5-core T5-core
34747919526 2195 085017ab2172c2f3d4cd3e27251033ecbc5b814b f162 T5-core T5-core
34748079069 2195 ac8f5b8a8137f15e95478be71ee6742e928ba5bc f162 T5-core T5-core
34751940360 2073 232764a12295a59f05619a2bd032c8f92af6f17d f101 T1-tests T1-tests
34752354822 1716 8d4fed61db63559a0961f11f821db21c26e7d24f f096 T5-core T5-core
34752961647 2196 81a8a03c820cb8bccafd0ebac355eede21b5e8bf f163 T5-core T5-core
34752965817 2197 823e720982e1bce047a9243bd9b24d9688dadb72 f164 T5-core T5-core
34752971903 2198 b5b69af7f8d0d1083974e993f0e9b92a435fa253 f165 T5-core T5-core
34753122921 2199 d14bc7051c8a32b2678bf10d5ee711637d606edf f166 T5-core T5-core
34758237901 2201 1167972031d427be6773b86ec6dc9903317360d7 f013 T3-mid T3-mid
34764451388 2202 7fedfe85c63446d3695b75d463a9b8767655730f f167 T5-core T5-core
34769627346 2203 848ce778766d73ab4eb09ba8c98001da8f587fe0 f168 T4-platform T4-platform
34769883104 2203 00411ce7bc41995783bfb16aada673515585446d f168 T4-platform T4-platform
34769958569 2203 d64c9f2afe6b2add648ee1e3eead48acda025579 f168 T4-platform T4-platform
34797944694 2203 581f0c74006beb3edd28babbebeda7e48b3fa1d6 f168 T4-platform T4-platform
34805101784 2202 7b77fd4834afe355cc4c7326ac3a55f0502f7264 f169 T5-core T5-core
34822774278 2205 e0a6133293556d87de6c2ca43de674e3a643e339 f170 T4-platform T4-platform
34823711563 2158 334e0b0849241430d26c3fdb6ffc8ec7a676965c f141 T2-extract T2-extract
34823714967 2159 63cdde702d9eb366b58006986e1498a18ae7f7d8 f013 T3-mid T3-mid
34823719320 2160 c1dd44151fee8cfd815c693392a7c1c72def98fd f125 T4-platform T4-platform
34823723515 2161 80f8bda5670c8ac1956e2a8fac7221c40c12da47 f091 T3-mid T3-mid
34833703378 2203 3b7f2559213ee80f5cfe0e2329c418211e09749e f168 T4-platform T4-platform
34853838753 2207 45c94be627a3088c275ff4f6acdc1f118515849a f171 T4-platform T4-platform
34858382857 1767 036562df35486924c62cdb11a8c9518e479959d1 f172 T4-platform T4-platform
34876200704 2202 9724d903046626df0640ced218259cb58070e4e8 f173 T5-core T5-core
34884655464 2202 9e69cb7d9b7ad4b40fa5f8cbbb653dc70e108130 f173 T5-core T5-core
34886874258 2202 d89ccabef064661794269c0de726021736d72b51 f173 T5-core T5-core
34891515627 2202 d70b79417978a9f3ab0d1aa849879dbf049793cf f173 T5-core T5-core
34898586377 1823 d4ae05a03dae831972a9dae7d2a2dd2ffce39e35 f076 T5-core T5-core
34943783091 1781 7c88e64eb954e9400273d3ebb9f6f88f1aebc165 f174 T4-platform T4-platform
34986091885 1823 955e4c9c58504b3c8e7d3e42866d16fadbb6ca7d f076 T5-core T5-core
34993109423 2212 172787de9c18352bb29545da2426d9d61b260fc3 f175 T5-core T5-core
35010526411 2213 d401186ca09e3992bc0e804af3945fe1cf81d6d2 f123 T4-platform T4-platform
35010533052 2214 1f1f8334ca25502c3ed1d4e91e8c6144d07bb7e9 f013 T3-mid T3-mid
35010542675 2216 729c3acc0fedae0ff77629281caf6dba1ac487fb f013 T3-mid T3-mid
35010543243 2215 783547e4fd79bb6b38f0741b6118a719e4109059 f127 T1-tests T1-tests
35010546755 2217 193c60d66db7eb36c58a7d0803d3936f5dae1d70 f149 T4-platform T4-platform
35010550773 2218 0a1d229ba89c5a7ad6c63c1587f5a938cc890270 f124 T2-extract T2-extract
35010559332 2219 a335422f63be3b5711ad40742bbaba40b2600f25 f158 T1-tests T1-tests
35016014963 1907 50ee111714baa9d3bfe9bdba3b81e27cd1bdfe38 f033 T3-mid T3-mid
35018285825 1913 da7619474d5ad27ff5073cf505b0b6cd5d0db36e f041 T3-mid T3-mid
35037109751 2220 62fd80b842f00bd72e655d23c6afbfe12e199b6b f176 T4-platform T4-platform
35037798343 2221 22ce867efd88dff9c219f789967ed2612329ee6d f001 T0a-docs T0a-docs
35047093950 2072 e5076d215e3a31ee7f7528d9e5ceda83fd8e8a8a f100 T4-platform T4-platform
35048046352 2072 8199efa4710893eb77d7c98d2730683a6c51327f f100 T4-platform T4-platform
35072023020 2223 17a326a58b591e786790e79ceb3be6dc14ee3988 f177 T0b-nonproduct T0b-nonproduct
35113639467 2223 43620d39d27554548fc2d613ed707efc8676748c f177 T0b-nonproduct T0b-nonproduct
35129477140 2225 58c1c0caa7061ae868305dbe59a6c5e67004849d f178 T0a-docs T0a-docs
35142303658 2227 dce2c5be9710c364eaa3a654d417ffcc392b62a4 f179 T4-platform T4-platform
35145042859 2227 3d848acaa7d22885d5040c957b3aecdcc4376107 f180 T4-platform T4-platform
35186676917 2140 b74c03558045bf29b7ef95c8aca803c15d3c7594 f140 T4-platform T4-platform
35272232918 2230 e0becdeb094dd5e898997a2add826a9f84befbd7 f181 T3-mid T3-mid
35273896570 2231 7054e4ff8af76f4fc1fe89407874e9910d0e412d f017 T2-extract T2-extract
35295711667 2233 f0a86ef2c2bc352a479162cca79d1aa64598f09e f182 T5-core T5-core
35303378012 2140 b52df4b0ce5c38667277716cd97ede964684802c f183 T4-platform T4-platform
35303488221 2140 ca3461170fefc959fa7688fae4e212d6e9f2f99a f183 T4-platform T4-platform
35312734228 2140 d85fe1d513ac97c8044b1065bbd0b7b9ebc87ec9 f184 T4-platform T4-platform
35314622268 2234 9b4d697a57a03f242164db956843a5313168005d f185 T4-platform T4-platform
35330927778 2236 949492b50c7dd81271f0180d8425d32316becbcf f186 T5-core T5-core
35397596933 2233 1d58385b513a414ead34544346e86549dda3c4b1 f187 T5-core T5-core
35443109227 2203 e176ae552b99f0fd08a2dde8bdf52025a3e20059 f168 T4-platform T4-platform
35446976110 2233 8c1a9d61c27292bf7a52f10fb54186d4e24de28f f188 T5-core T5-core
35448941856 2233 9db5217bdbb779212c5d066ed1a84f7ca6fc0282 f188 T5-core T5-core
35467285032 2233 9db483402ea2fd4e0a5ff3bdd2b1fc50a7518ec9 f188 T5-core T5-core
35470450948 808 bf2c54e0ef45d0d9767d7945a160f630f890f27d f016 T4-platform T5-core makefile-by-name
35470489882 808 80d277834a233f2e7a2df68b8c08a1f233cf3dfa f016 T4-platform T5-core makefile-by-name
35472952969 808 5515ca4ec6b20b62ed0530489785e4f131fd5159 f016 T4-platform T5-core makefile-by-name
35473919864 2225 c067c771b9f93d08a3e614e68aee7d710d2f256c f178 T0a-docs T0a-docs
35475706415 2241 6166934defd9bc5b687ab9c8de3779e71a844395 f014 T3-mid T3-mid
35475754479 2242 e8a46c16e233bff111092dff18985cca94890bcc f189 T2-extract T2-extract
35481840120 2236 aac550b732040acfe6ab6c0bfe5d0cbef5d9f9e9 f186 T5-core T5-core
35483670414 2243 243b405afeb3998707ea4d556f3178c8e8419cab f150 T5-core T5-core
35483768695 2236 d2dd3b347bbdcc695f911e80b1b7f7a32d790e5a f190 T5-core T5-core
35486887056 1999 aa4da14d23dc28df58b9881dc66d89c0d1c0d788 f021 T2-extract T2-extract
35493947102 2236 2caafc0c41bd083092afeb692824e718a8e1e007 f191 T5-core T5-core
35495099219 808 83667268d9c435c2634994efef45b09daa98904a f016 T4-platform T5-core makefile-by-name
35498544795 2227 656fe99f9934654bbab927d29add13f7822e1bc8 f180 T4-platform T4-platform
35499978643 2244 56de845327dce03cdd1269b8aa73c6a1ecdb5cfa f125 T4-platform T4-platform
35500380927 2245 ed78ff056e89da814ff5ae05da6fb32b7e10c0db f192 T4-platform T4-platform
35501823298 2089 e171bd48b5e5845ec6f00db65ff300caac4f7304 f046 T4-platform T4-platform
35502589515 2140 618fbca0c39da7157c7f01a26071ef3a4d4a5b00 f140 T4-platform T4-platform
35502728596 1766 81820262e61f6e7f11920a5c4596b4297e793cf8 f193 T3-mid T3-mid
35502852336 2246 fed0c1b4e3563f1567b3ff85652ec51229014a83 f194 T5-core T5-core
35502954849 2246 2eeb15f434d9520e0050fe1f5b6c4c8316a4a69e f195 T5-core T5-core
35503248954 1766 4eb3af36c7b055987d5c0764482135658b28c550 f193 T3-mid T3-mid
35503753154 2246 5b4ea78927772d00f11abc41f217f89e714ace54 f195 T5-core T5-core
35508860520 2247 9b1dfda875736a91073c57badce3dea6d0f03080 f196 T3-mid T3-mid
35509728718 2248 96a2fc35dfcf14662cee491430366f4e8d8b8149 f094 T1-tests T1-tests
35514366896 2249 051702c6f9163f0a19dda19ea14ac1b72dc7eb3d f197 T5-core T5-core
35515754123 2090 7036c4d5996253c05d720ec4d0b3008509b4cad4 f198 T2-extract T2-extract
35517816031 2090 f9350caae0ccccf823d8cde85683a900b257f239 f198 T2-extract T2-extract
35517948650 2251 151d6077cf418a42628bc7e38bda4f873877f147 f199 T4-platform T4-platform
35518289769 2252 4ecd8ab2c3995cc92c1a8df4f5e2a1e2d3a8c342 f200 T2-extract T2-extract
35519906910 2253 458b0db1ba3274ceee1e975cfd7300f13aef82e9 f201 T4-platform T4-platform
35519951214 2254 8aa77f89ce1ef56c98d989b03bc35fa4c6289b8a f017 T2-extract T2-extract
35520101152 2249 238f9e6c6cad0e086eb06c19f1a6a303fbb30fdc f197 T5-core T5-core
35520168086 2247 9af73b327e3899c45fc1faf13b590c09efa2078e f196 T3-mid T3-mid
35520178069 2005 fcf451c6a6dda768dfec5a8f61a01ac1e1cfa6bf f034 T2-extract T2-extract
35520796432 2255 0541b033fa874c38167e4f4d204ad452f1d96d08 f202 T2-extract T2-extract
35520869334 2256 b565e9d8b06e8703f1c15a4263a987837dd0c5f3 f013 T3-mid T3-mid
35522168154 2257 af3e003ad8e67cd1fe3b20dafaf4d33a4b351852 f203 T3-mid T3-mid
35522190984 2258 de4a88f22242f53f724fc6cc8dcaed79642dc5cd f204 T2-extract T2-extract
35523164377 2259 460ebefc87df4dd5142bdc339702edd4feec4696 f078 T3-mid T3-mid
35523331086 2260 25d08258e567ec417bf7b18b14a63e30162fc7da f205 T4-platform T4-platform
35532624016 2261 28d62001fd1e5d0a18085a8a662a1b806747815f f206 T0a-docs T0a-docs
35532636527 2262 16a7291b56aafdcde27928f892d477e8b94269eb f207 T0a-docs T0a-docs
35533089935 2257 c1128db8187db2dac27a7ad14120fc09802c8018 f208 T3-mid T3-mid
35533328007 2263 cda5e3c9e5c4d2251467ced00ff305205c368d17 f209 T4-platform T4-platform
35537157817 2005 6eb8992462e506bf0c58aec88274a9a62a07676b f210 T3-mid T3-mid
35537525864 2005 fcf451c6a6dda768dfec5a8f61a01ac1e1cfa6bf f034 T2-extract T2-extract
35551212771 2236 39a8bc3f2a7fbbc4d86ec8379b669a31ed1f8318 f191 T5-core T5-core
35556558511 2140 af4604189bf23c14dceee9bccd89a16f5bfcad8c f140 T4-platform T4-platform
35561381902 1724 42a3d7105548c65ee12f6dffabea718573501601 f211 T5-core T5-core
35565389237 1809 19d9e4ac4d0a8c6839b627c0cd5890188121fc2b f046 T4-platform T4-platform
35565392645 2075 e321b1fceaaf4c9f03fbe82547f02c1079e3b760 f104 T4-platform T4-platform
35565401625 1784 381f64c15ebd0a676b85347acfcfd3f45c806a11 f009 T4-platform T4-platform
35565472800 2260 2dba3cf81750f245d8c1d912997f04be5615df99 f205 T4-platform T4-platform
35565493742 2005 7d01d3ec031dc26759283fff96e979c3e0ce270e f034 T2-extract T2-extract
35565496531 2259 6f2f83618134bee208c98c47931579f27a712809 f078 T3-mid T3-mid
35565502343 1768 6115e5785dc01f5b403cbfe7f184ffbfba058e45 f055 T1-tests T1-tests
35572275939 2158 d4931241adaef732066afb5ebf1cb225e2474403 f141 T2-extract T2-extract
35573014946 1716 95fa63441bf80c9ed871fa090ba925b8b6106d83 f096 T5-core T5-core
35574966373 1724 81ff2cabcfeb4619e24307c0267a1244f38afb40 f211 T5-core T5-core
35575687750 2160 2e4b45931b69671a92402f8fdf51f85542eff268 f125 T4-platform T4-platform
35578606399 2199 fa138acf44525ef94cfd6e16772d601da5623aef f166 T5-core T5-core
35585540293 2198 9574c316e43527d6ca90eb007a9c8fc24fcfd88b f212 T5-core T5-core
35597253045 2268 f0fac62b7140f2cc3ccc011c74e798be7681ec61 f213 T5-core T5-core
35606545382 1724 93f86be139b4bff1d079205fc776babc67f67349 f211 T5-core T5-core
35607169136 1716 bc5b3dd216568c864b41303a674020a2d456ced9 f096 T5-core T5-core
35623513958 1823 c017685d66ed9bca5476313ede52837ad07f17db f076 T5-core T5-core
35632304230 1724 bc81c4a84f19f2f5d90d985ec69dcd5fc357ea25 f214 T5-core T5-core
35637728335 2140 fca33717d1b6a9171decaf4d1755c88f48dcb572 f140 T4-platform T4-platform
35638029971 1724 d48afe8d3070e360ca908d841d85adad344b2ed6 f214 T5-core T5-core
35638134068 1724 3743fe95a0fc40f584b88bf4cbaa0f9bdfd8e52c f214 T5-core T5-core
35639932135 2227 df83d25ace9885e252363cb04be25a17da85c9e8 f180 T4-platform T4-platform
35640451450 2269 65d60e72f5b86f5982b15f0d88826ba79a085aa2 f215 T5-core T5-core
35640454709 2270 f428d583008ce87281bac138c02bd4375c56d735 f216 T0a-docs T0a-docs
35641634636 1999 e032a8ee9409c7a53a5d732ddff67c15721bf1ae f021 T2-extract T2-extract
35641641970 2158 61cfb09b52ba40e8fb3ee9703c618b28739e3298 f141 T2-extract T2-extract
35643957306 1724 5033f97d5f5154cddecee760e5b2a5ad290b181a f217 T5-core T5-core
35664547053 2272 da4bd25a01b612517512ee628bf0ec652000e80a f218 T1-tests T1-tests
35665885098 2273 c314dea4c1409a3a68fb33706b49e272809b5079 f219 T2-extract T2-extract
35665890640 2274 343d7dd5e47b817999098605370f0eab706675f0 f017 T2-extract T2-extract
35666550961 2275 a041794f8e51ddf42342899be7b2ac26f951d777 f128 T4-platform T4-platform
35667708865 1741 c0bfd073ff25484c2d7b3b59dea937be6c92ed08 f072 T0b-nonproduct T0b-nonproduct
35667710693 1742 dfae618b0e9508cd4998b583ebbeb36cf458278e f073 T1-tests T1-tests
35667714864 2170 36730c7d895ae7541f780d650151b3590fc766cd f053 T3-mid T3-mid
35667829346 1724 879aa5f9623bab14e8fb903da23bf84a8d0bc32e f217 T5-core T5-core
35669146114 1724 89030aacb21df9b72f23b42286e3a4eee63fd43e f217 T5-core T5-core
35669196507 2140 2f557f53986976b2df1eb2a1a540c9d48cf91a8e f140 T4-platform T4-platform
35670303027 1724 b47ba846bbd1a6fe7359c86314c81fb33f5adaa4 f217 T5-core T5-core
35696787516 1832 6ae6cd4c3f280cb8e3ffec2858269055e390ffec f220 T4-platform T5-core makefile-by-name
35697012777 1832 9a9534cbc45d40afa4e8bf070030a806acea1f5f f221 T4-platform T5-core makefile-by-name
35697183623 2022 7498e2b75b535171b5a560c6493fe86b5ad881cc f222 T4-platform T4-platform
35697752061 1724 3889b47527084eccc7c54c8a3c7e2592c5d5d30a f217 T5-core T5-core
35698464152 1724 08d5e2062f1ee9aaf8dce8eaf77662cc7c96a1c0 f223 T5-core T5-core
35714885737 2280 f930ed6026c6f1e5f5b146a07664efed9d4da9c7 f224 T2-extract T2-extract
35730933863 2198 f913f0eaf5ee308619ef1fdd4f60534d4509fab5 f212 T5-core T5-core
35734984657 2281 34d172c9038a9b7cee78e42bb7768b93fbb8ed70 f225 T5-core T5-core
35734995796 2282 1d2bbc425988eb78fcd966dc8c9cfbdb814f0685 f226 T4-platform T4-platform
35766202700 2288 427bb73c638481842bd13d73ed171953469eca02 f227 T3-mid T3-mid
35767446435 2288 d7eba5a71d2a99d7b7bc588c666d22516ac0b563 f227 T3-mid T3-mid
35772088984 2289 38fc6ea995c9db78cedb10b9bba2b6aa79f616fc f228 T4-platform T4-platform
35797101033 2290 255463126650ab6deab3e62aab441b71479704f1 f229 T4-platform T4-platform
35797928618 1702 aecfb315d8ddbfab2cacbbd6e4a26b408b973975 f085 T3-mid T3-mid
35800424142 1702 26bd387753a9a08fa6de647481bf36cf338e5c28 f085 T3-mid T3-mid
35812291988 1702 6233394d8e893546af92876fb8d8da81e592cc2c f085 T3-mid T3-mid
35813226724 1702 5770ed83741009e71c12876c40b655758f4f4440 f085 T3-mid T3-mid
35822291393 1702 8165c34015699379d31fd31060c0f16fbe5447de f085 T3-mid T3-mid
35824077381 1702 24795fc665df85200ddc112aaddfe34572171ef8 f085 T3-mid T3-mid
35824588131 1430 6bb8ad6a7a1c99849d101322ca8fe81b6adfbc50 f230 T4-platform T4-platform
35826272552 1430 0d2f840c8ed2695db206acbcad1ad1c36a1f3958 f230 T4-platform T4-platform
35836874903 1702 f5ec03f0e1a7e90ace14c21490dca39c4c8bb092 f085 T3-mid T3-mid
35841856084 1702 492e59a181a27af324c82a8db3c4e2e37382d567 f085 T3-mid T3-mid
35841866366 1702 9e92e67649d62a2de63e57893290851d7247476a f085 T3-mid T3-mid
35842147817 2158 246e4190c3086d1074adbe8afe39bf4261db59b8 f141 T2-extract T2-extract
35846258328 1702 e9339ff1f5b28cb85d2810f434471700e2fc3b5e f085 T3-mid T3-mid
35862574659 2294 54c143ac932b6e291a17be0327fb1b5e86b99984 f013 T3-mid T3-mid
35899891384 1823 4a8c2eb422d9083ac3bf1e01e18e558e470ad099 f076 T5-core T5-core
35918117863 2295 f4c897110cb1f02edee37db7c8849e63894ed8a4 f231 T3-mid T3-mid
35936681521 2296 59345314187021f50c5e5c12da793b27b8485d7b f232 T2-extract T2-extract
35948281533 1724 48f9de8ccd7cc75d7e466b7d5167e2930dfe33f3 f217 T5-core T5-core
35948963693 1724 a8da9a366353bb0a67806b40f840500444ac963f f217 T5-core T5-core
35948982011 2272 9b5bf70dc400fdfe989ed2bf1b623b2103bbf2f0 f218 T1-tests T1-tests
35952295014 2181 282fbfcdabfec79c19c36f5bec5f75c22eb36df5 f091 T3-mid T3-mid
35957866374 2158 cf7eecd7d0b1288c7f2e290a1d1bfc1b1b6f8698 f141 T2-extract T2-extract
35960900747 1716 a6d3d5d820b07ba8544ddae1668e8107d7961532 f096 T5-core T5-core
35960904447 2199 f07c18702ac82957d0eb73f96f37d30184b7e6b5 f166 T5-core T5-core
35960910227 2263 fcdc6bedb7d05489462639da38c4ac755137734f f209 T4-platform T4-platform
35960928169 1768 92cafd1ec888362ef3223ad669a5aa4f378a54d5 f055 T1-tests T1-tests
36006341620 2301 64297ff3e8e142887fc36c933d57fd4d32924cc2 f233 T4-platform T4-platform
36057012376 2304 6f91a16fc2a753db248f122dea7e4b50763972d1 f055 T1-tests T1-tests
36057063533 2305 69b84cacff1b08f449e563c2d24861e6f10694e4 f234 T3-mid T3-mid
36081432074 2307 2a6dd92f658b15d571130b0eee14a67b95a36465 f235 T2-extract T2-extract
36081473175 2308 707f6f0db58a16b706b29d249bced477094ef92c f236 T3-mid T3-mid
36081733263 2309 a9a25357ec2dc57f3c6f2e353e7ca61a7575df06 f168 T4-platform T4-platform
36081918547 2310 2f922141972a18f0d95de4505b33f5362f51164b f237 T4-platform T4-platform
36082054353 2310 8955d98983e0ca1e1df4a2173b4b4273904566c7 f237 T4-platform T4-platform
36087900420 2310 7c24b020457924fee9a7acdd6cb8c5f7e262ca4e f237 T4-platform T4-platform
36093255940 2310 b2e9199773977f28d73a052d41c37b8c111750a5 f237 T4-platform T4-platform
36102327028 2310 d636aa7f21fb001325b61cd41fc431d5f255e21e f237 T4-platform T4-platform
36137231318 2315 9527694e90ee9dfd29d64940425f9f23290ed702 f238 T4-platform T4-platform
36146313096 2310 9b10ba281cfdba2676e9d24c445470c4b6f7d4e3 f237 T4-platform T4-platform
36150749742 2022 7a307abbf8c1c879edee3193dd482ef427c83287 f222 T4-platform T4-platform
36160883414 1766 9d53f32dc1753fcece6c0d2af27a5958f8ce1799 f193 T3-mid T3-mid
36164363201 2318 6d508350c7713e10e0e935625ad74d8401fe89e2 f239 T3-mid T3-mid
36164368040 2319 3faecc78144175a81acf76721c4f695cf80b5676 f240 T3-mid T3-mid
36164372390 2320 4f5c951dde792c9cee4e33e45119cef8fc62765e f017 T2-extract T2-extract
36164376373 2321 c6ccdd9395dbbafc6f66a49040b478b445387946 f241 T2-extract T2-extract
36164382846 2322 19f50de3d2e2bdea5f9054caf07af70f46d74244 f242 T4-platform T4-platform
36164406666 2323 e9f6596e82cac5b3377cf212e7199a286815418f f243 T4-platform T4-platform
36164410674 2324 a808295986d7599ddbfe8324beefacfdd5161c04 f244 T4-platform T4-platform
36164415830 2325 d56e046ddd08ac6bf8b14f5049a88382a10a1636 f245 T3-mid T3-mid
36164420947 2326 5ef8ccff7023db106df18cef0615776e7ed94686 f246 T2-extract T5-core makefile-by-name
36164427320 2327 098fcc883e2c720ee838df90f3475938715d4056 f247 T5-core T5-core
36164450931 2328 a8c4040e8a92dcf831c64f57458694ff8fa6e9f9 f248 T2-extract T2-extract
36164460893 2329 12f51855cab461cda3bd4c4d63d79f09fcb69732 f249 T5-core T5-core
36164465591 2330 e7324bb6904c6d0a295b956e83c8585219198358 f250 T2-extract T2-extract
36164472858 2331 a0c71225f7c0c5e4bee36ef235f966866abffb24 f251 T3-mid T3-mid
36164477427 2332 b9ad9ed1449540e06e9f814ded548d9f2bf52f49 f252 T3-mid T3-mid
36164497120 2333 e65a4e70be9c87366f66e93e252c0f307aa50ac2 f253 T4-platform T4-platform
36164502402 2334 357d71e198ae3cd4614b9e06e54957423291e5c4 f013 T3-mid T3-mid
36164505003 2335 1c73f8676c31e227c6ed478ccbb55662b34d6db2 f254 T4-platform T4-platform
36164512328 2336 b8b88bcf088b8cf3f6b44949e917d172aaa82d2e f255 T2-extract T2-extract
36164516453 2337 8e12fc8a69dc6f83296e660191448ef8ba7819bc f256 T5-core T5-core
36164537929 2338 dad40ccdb633c10557baba4049aec35049fa3b15 f257 T4-platform T4-platform
36164542709 2339 793e0b6127db3c8333b1dc29faf0b35167e3ceda f258 T3-mid T3-mid
36164547188 2340 b3bc2a80aad766d37477a163ae5b158913a20f45 f259 T2-extract T2-extract
36164550014 2341 40dad610eec783fe243e6b6141c1bebc75c23f0d f260 T4-platform T5-core makefile-by-name
36164556221 2342 c650292433534127b5f45edd066e56a06b3376d1 f261 T3-mid T5-core makefile-by-name
36165117354 2181 65fc0bb4f77fdbb6449199c147e1ed112654b151 f091 T3-mid T3-mid
36165123674 2158 d772b1e5af0ce80d08aa6603ea47d1f51f324193 f141 T2-extract T2-extract
36165128132 1716 ff127ea44c56ce21bd5a4f9cf9f51bb27523d0aa f096 T5-core T5-core
36165431134 2343 45f0c430d28cf188753f44536d2cac26564a04ba f262 T3-mid T3-mid
36165866718 2344 4b2b3b90adc44694d70624c9d0372752ffd0e2bf f013 T3-mid T3-mid
36171533714 2326 c0a06b3d0a90513e0d80f79514b869f91cb958f1 f246 T2-extract T5-core makefile-by-name
36171575767 2345 772ea06a6115128c3160f9631322b089aeb85a53 f263 T3-mid T3-mid
36176174515 2347 b220ecec48010db71ac0a77b2888fefe802c9182 f264 T3-mid T3-mid
36176260818 2348 886e6da479891b6dfe386ef25dfe5abe7ed2f080 f265 T4-platform T4-platform
36181164078 2289 8cd2ee5f421d0b7c06022091d59dbe71002de30d f266 T4-platform T4-platform
36183268529 2315 c27a53512da9469dc50fb243c1ecd3a55975ebfc f267 T3-mid T3-mid
36183549298 2350 99ccc7d64a275bba1c015b30df036597637363f5 f268 T0a-docs T0a-docs
36184450486 2351 951b2fddf68a6e6605b1664bd3252a2a6578ef35 f014 T3-mid T3-mid
36185562216 2352 8093d10b812d9a7840ae99d8f9d24a04cb0f9559 f269 T3-mid T3-mid
36185568093 2353 c64ea20feae160d1eeddadf09e7e0df36b08e818 f055 T1-tests T1-tests
36185818550 2354 3ef7d8cdc201115dd76cc373ea2c64a567a2defe f012 T2-extract T2-extract
36186982935 2318 b7bcb15c219761f27ff146063a50481303527563 f239 T3-mid T3-mid
36186985986 2331 6b7628bb6bbf041a8bdf069dd9d6bc8861d84e73 f251 T3-mid T3-mid
36186989409 2332 1d5ff68e508e78b149589396d547166ad41362e0 f270 T3-mid T3-mid
36186989799 2334 87471e1f1d6a01ca8be6ca45b968145d0de95362 f013 T3-mid T3-mid
36186993787 2342 7abaa91dea653af3e890759a63e40a09cfcb81d8 f261 T3-mid T5-core makefile-by-name
36187019606 2327 f5c8631f2032c5598b1bfe12d329b875532f814d f247 T5-core T5-core
36188745373 2355 ae578bc1cc32bb8f159a69c486f938ad745c643f f086 T4-platform T4-platform
36189334816 2356 d5a3110d9e1e82de5416807d1f81e3ef85049c1a f271 T5-core T5-core
36189454232 2357 28b41c35b71cb039988f39f72689dd657e30522d f272 T4-platform T4-platform
36189574830 2358 f3754d6197b626d5432ac84b0363b0d3a5544c5c f273 T3-mid T3-mid
36197747139 2301 ff8dd47011c43c622d3dc82b33be32b4493e4ea9 f274 T4-platform T4-platform
36199747285 2344 bac3fc80b532eb294fd6eda6a2cc93d656df9369 f013 T3-mid T3-mid
36199749518 2345 1810a8964caae06e33b7f3c6f944de570c21c292 f263 T3-mid T3-mid
36201323199 1702 5557f0ec4a54bcaf99bd7442b866579ab68578f4 f085 T3-mid T3-mid
36210099904 2359 efb231285d188ffda16a58f7a5ce8e0e6448638f f101 T1-tests T1-tests
36222963285 2315 2d2c0fd0c008973c63565c3e7f4591ed0b1bb379 f275 T4-platform T4-platform
36227652952 2160 bc960d87d663b7b4fc4c3ba558013c4609073762 f055 T1-tests T1-tests
36232110014 2360 1ccbb432ae1281ac96f743850be317fb541cf1db f276 T3-mid T3-mid
36238137680 2361 4dfeb2cefc402c7d1f1d3d01603a2e8409ed24cc f277 T3-mid T3-mid
36239758139 2281 9dca8d2fede860605943ee8dbd57ba6b927ae41b f225 T5-core T5-core
36239789418 2362 ed991154f212928df63ce5a5cd1159cfc8196f00 f278 T4-platform T4-platform
36239835633 2290 26e2d0cba6df7b1cb53e655d0b902bdb7debdbd4 f279 T4-platform T4-platform
36241153105 1816 3e67838b05e67915d472ec7698c80649ab8ad03a f071 T4-platform T4-platform
36241168954 1766 8f441c19fb8a32b8ccf43ffcf65df7f4dc2bc16a f193 T3-mid T3-mid
36242675954 2363 363575af920b05f911fc76ca964dcfa7ef1c62bb f150 T5-core T5-core
36242676040 2294 6e3d8c296f2f99375d9693e706b4818c72caad13 f280 T3-mid T3-mid
36242724677 2364 e94f5a70d01e4cf62b0c9f496175dcd930bcbd0e f151 T0b-nonproduct T0b-nonproduct
36242808005 2365 7549e6d09c021f6d3a385a37898f591626ae62b8 f150 T5-core T5-core
36245108554 2366 06f97613a28a18bef0145f37c1036fa60e73533e f281 T3-mid T3-mid
36248365534 2367 8f803f7262d1d1e58068f677f3e365048050b728 f012 T2-extract T2-extract
36248371079 2368 a59d53a14b779401621c44aa757e3bc9f0588ef0 f282 T5-core T5-core
36259321198 2370 79a0c92e33a53758c36d87d4124b12cc83dac5be f236 T3-mid T3-mid
36259389383 2371 0dba49208e8537eda2c3355387c0307f6ee95075 f283 T2-extract T2-extract
36259523618 2372 6f17caa7a763d9987ebcd7ed33063d47d34bf635 f284 T4-platform T4-platform
36262536017 2370 12de590a4935488d83380a33865cbb59a7a16c32 f236 T3-mid T3-mid
36266366437 2373 77ab4bea87008547acee60435a8e3df29d90a5cb f285 T2-extract T2-extract
36266372862 2374 7c7811f81aabb4c1fdc0914e78d46b71493a9dce f013 T3-mid T3-mid
36266444913 2375 3ad76fd7728f3b19ca1100b10830e986b7f631b0 f235 T2-extract T2-extract
36266447071 2376 5a9c72d8f8c65b38c66a29a34184b49edf3378ff f104 T4-platform T4-platform
36268746769 1702 8ffd03ecd9ccae5a32508801645fb1cac7e4a4d1 f085 T3-mid T3-mid
36270666016 1784 ca58149ae4757325158af07e4e3b8968d315e5da f009 T4-platform T4-platform
36271539681 1784 71ac6ecc15d3ab3ebcd31f0e467071a506006ca2 f009 T4-platform T4-platform
36271900288 2367 075f40e9d4ccbfead4df0e199c7730f94c2154ff f012 T2-extract T2-extract
36274019750 2304 262d7ca7659b3f1f6ee619cda2b5f6aad2815314 f055 T1-tests T1-tests
36274021183 2305 3d9e5ed5cf1ad1e4bf89b385d0be28c8f011f52f f234 T3-mid T3-mid
36284225343 2370 208be7d9374bb27d2875e3568265b591ae7c4b5b f236 T3-mid T3-mid
36284230521 2371 eafaab15c802c46ee22233b50f0e9474b0a3085d f283 T2-extract T2-extract
36284315887 2280 756d7f42d4aab0045d75e91651cead399e4a7b35 f224 T2-extract T2-extract
36285289867 2315 a1ac80740ed800d84a9d5e44d725bdc29d99b88e f275 T4-platform T4-platform
36286798412 2072 1a37f0dd52ab62cfbb439c5a2f72afddca4cb009 f286 T5-core T5-core
36299419426 2207 631317536426136f580e34d3c25dd5a951d443a8 f171 T4-platform T4-platform
36313881874 2381 9d8f51d7f3340381afa450aa33cfd320879d6ae9 f060 T4-platform T4-platform
36323957389 1702 b46c6d43f2efe6f0a1de8c77b73085183aa5da1a f085 T3-mid T3-mid
36324735265 2376 07eb426c95386e299a6aa6adbf2b4eb9f272ff3b f104 T4-platform T4-platform
36339435944 2370 e9058e11db9ed94e401ad711b2016249970a9040 f236 T3-mid T3-mid
36339438890 2371 4d21c3bbd34b420c8987e8588b88b5fc5446116f f283 T2-extract T2-extract
36346241955 2360 09f704d23f0d990dfc72b916d9daea80acce73ce f287 T3-mid T3-mid
36347358048 2361 0d720d52b253cfb64e7bfb1eee034bc3035f1a04 f288 T3-mid T3-mid
36348236142 2366 06ef2f64caea016fc792e9069d5a3befc30bca4a f289 T3-mid T3-mid
36352906531 2375 7707c9d7918263cd446a4b54d4eb48c3fea91048 f235 T2-extract T2-extract
36353006100 2247 fa2292111802b5f3b66db3e4dd168280b53ac17e f196 T3-mid T3-mid
36353528883 2249 a45a99c9c73dd54a4625d680da700f7cb0387eff f197 T5-core T5-core
36353741468 2385 1b2e8f1fac105b0eaf15e15b95c3ef336bf0a44e f290 T4-platform T4-platform
36354332711 2181 82a6ddff81c6c8583c73f3d628037e41a9b412f0 f091 T3-mid T3-mid
36354336229 1781 fd2456316e7b5dc27125497f640dc7e41760c104 f174 T4-platform T4-platform
36354339289 2065 87ba464596337fc6574a825ef88392e70a733dc9 f093 T3-mid T3-mid
36354790786 2252 df8dd1011bd1987d0ca5a45f324775aad2411f54 f200 T2-extract T2-extract
36355512051 2089 11957ae6554b7d768095dcda95168331bd654363 f046 T4-platform T4-platform
36355610699 2371 ee9d1365e2c17d4e2bbdd84ac10ec6b89cffca5f f283 T2-extract T2-extract
36357921046 2344 7f649b29dc778b0f54c28dfcc2f25b76f3d1947a f013 T3-mid T3-mid
36357921739 2326 259dbb67bee1d3547ae61a98c9d0c1cf4e491d44 f246 T2-extract T5-core makefile-by-name
36357923794 2352 ea6fa49fc2c209de69c1b0f393d9d2cc25b61419 f291 T5-core T5-core
36358160313 2389 27c87104a57eefe33543a04e3e43eb0721df8b4a f292 T2-extract T5-core makefile-by-name
36358451648 2207 8bd9d37b156641ecf1c82b16b0ec0a8246e8c201 f171 T4-platform T4-platform
36358713716 2295 9f5f5ab56fce2baf095b75f1e7a8dc98368dbcad f231 T3-mid T3-mid
36359201669 2073 9130fab63d23b5386f12fa4cfa57961b247ff07b f101 T1-tests T1-tests
36359210035 2385 c03575ef1ab1abb51793d03303ae3e5d4cd3b708 f290 T4-platform T4-platform
36359646097 2391 d450275e20b663e3c5c8a06627030702e61e4184 f017 T2-extract T2-extract
36359899206 2385 cb307a496de36c9803dffea0815f3ea96bf849db f290 T4-platform T4-platform
36360333971 2389 5ff3e8d6fd3fd87d46807084058e2944f6486d76 f293 T2-extract T5-core makefile-by-name
36360464106 2305 8a49fc684ad08ac2d99591e0b364249570c9e676 f234 T3-mid T3-mid
36364475838 2392 19d144477b9558dad148d3f555ec6ede16caa3f5 f294 T4-platform T4-platform
36364478399 2393 dc735c01fbbb4eddbbb73ff4df6007ab68413a4d f295 T4-platform T4-platform
36364479687 2394 26cbccd998819c428589156a82e167db061522ae f296 T5-core T5-core
36370343201 2395 f200d7477f192caedaa5b4cda7d9eccc2022a069 f297 T2-extract T2-extract
36371911963 2396 629e9c2c1f87e2ac8b5d644d38ff4e935928752a f298 T3-mid T3-mid
36371921838 2397 4c7bc08ff02d49afa07808309d5586bc61724ba4 f299 T5-core T5-core
36416318776 2315 3327ee5935a7b03ef5f850c12c163c11ef9d91e2 f300 T4-platform T4-platform
36440070485 2305 853192aa7e76eedceb1fce6e2a6874948cf1c1a4 f301 T3-mid T3-mid
36440993073 2207 343cc949270a3e18e317fd6cd781167ed2fb3dad f171 T4-platform T4-platform
36441585217 2368 f277c5e0a9aad35c730e4189b969b74964614d87 f302 T5-core T5-core
36451035334 2207 1c840df7495e3b71924b8efd7e3bbf53c9076abc f171 T4-platform T4-platform
36453341395 2401 9d5b6e998514b76dcc95b55f362325b3d49945da f264 T3-mid T3-mid
36455603700 2401 874286abb9817db94e7789c7938921f6a03f3448 f264 T3-mid T3-mid
36456464356 2402 555727b50da6086f00c00f57876e2632caa69c45 f303 T2-extract T2-extract
36459403945 2394 78a2ec399fec3dc28e61439a4bcd900b7c64c23f f296 T5-core T5-core
36459418705 2344 b205f0b51dacd89da57f6fcbeb46f55617606091 f013 T3-mid T3-mid
36459497707 2403 95eaf01755d18c501fa8d1c5dae2255cf460b9a2 f001 T0a-docs T0a-docs
36459501942 2404 fe3c285fdc63bd9abe027026edd7adffe5f62677 f013 T3-mid T3-mid
36459506386 2405 460153204ba9833913e5245ca894f783c1d7229d f304 T4-platform T4-platform
36459537782 2406 a1b7c05685e18597f3461783d4bc98661f05427d f305 T2-extract T2-extract
36459545006 2407 c02dff113a90aa6bbc3d89fedda962a53f8b4c64 f306 T2-extract T2-extract
36460081689 2280 6ba9a21eed2165932b90f7ede772d77b046eb08a f224 T2-extract T2-extract
36460156038 2408 23d48336d3367fb27cbde54049b1414a2aea1b48 f307 T1-tests T1-tests
36460162959 2409 4c6ecffdcdb8d78c0704f0d7e825f676ad77c240 f308 T1-tests T1-tests
36460990240 1907 b74cecc53cea20abdc64dbdc484f5ba0b44f5789 f033 T3-mid T3-mid
36461038395 1913 71ebbb9a9b54f2dcef84321de939429dd7707600 f041 T3-mid T3-mid
36466048726 2410 533eee07ece1d01ff2c88da55972c7720cd1604b f309 T4-platform T4-platform
36466057374 2411 0d783bcca4edde896d5716e8a4060c3100595973 f076 T5-core T5-core
36466065600 2412 ed76cf8e88cfb1f26c82110e46a518c13577f788 f310 T4-platform T4-platform
36469132661 2360 5a77358277a83d7651965f762a673be76fb157b3 f311 T3-mid T3-mid
36474434156 2402 0a658840b8f029e7632910284281e32286c1f0aa f303 T2-extract T2-extract
36474499483 2402 f88aaeb8f9db801545c21e085812559df9f80083 f303 T2-extract T2-extract
36474752681 2418 3db66f22ad7d5c435559869a34b6e14b1e54793b f312 T3-mid T3-mid
36476594662 2402 33a9bbe85eb922bf01c5297f09cc50044f39e351 f303 T2-extract T2-extract
36476662334 2419 3f53a308f4034f4d280130fbcc275f545d7bf05e f313 T2-extract T2-extract
36476726058 2420 e36930742a63efd2bbca491996ba8e37bfa5822c f314 T2-extract T2-extract
36476760149 2421 fd1b88374c0c21892c991c29909d22ed499c20fa f315 T3-mid T3-mid
36476819919 2422 ffa0b320047f4db99dc53f09e228c94703436ae1 f316 T2-extract T2-extract
36477490833 2423 ba5e7dd639a3d29c54023d1203ef18fc4e330c7f f013 T3-mid T3-mid
36484216122 2391 f297737db0510ecc573edf57a2a30a25e5fbfcfc f317 T3-mid T3-mid
36484725527 2391 538ac25edda676ca7d8cb8a4c7c5fb3a3a36fda2 f317 T3-mid T3-mid
36488109160 2418 702c3afb6bbab15e18e13f22880d5a61fa3317e1 f312 T3-mid T3-mid
36488232420 2419 82361ffdd66ffe39efc1ff9e00e0cdcc61223e4f f313 T2-extract T2-extract
36488247857 2420 a46dc5a9fbb39310027812234d0459d4bbcbe99c f314 T2-extract T2-extract
36488262530 2421 a85bef05f169e4f2c36cf4cf121a7b6d1309e155 f315 T3-mid T3-mid
36488285902 2422 c695cb728e4850b4152cd7cc54f06b6520372371 f316 T2-extract T2-extract
36491809136 2309 48265f5367d93c22cdaf2203d19dc65fd6eefea4 f318 T4-platform T4-platform
36492368152 2424 904eccabe39b03d8104de989a56f224ccf6d81d3 f319 T3-mid T3-mid
36495192814 2425 e548d971b3e471ca5a149201c39ba62ab1a72261 f013 T3-mid T3-mid
36499773006 2385 534274c1601034b5eca97ec286c0346389076a8f f290 T4-platform T4-platform
36500785757 2360 bb3f1cdd6f4f45ab416ba97ad09a404aebf21e36 f311 T3-mid T3-mid
36500790466 2361 8116eaaa426ff39a37b4078e0abd018b09398e58 f320 T3-mid T3-mid
36500796009 2366 4a0f9660623b53d4f9eccd7149d54482707fdc22 f321 T3-mid T3-mid
36504138642 2371 96f91005f0777e3fee5f5b5f9da69962ddf93695 f283 T2-extract T2-extract
36506100535 2427 f4efd0b86844bf2ac965ba9ec30c8805cc774e40 f322 T3-mid T3-mid
36508613643 2315 232730cb091d852bd7820bf1d5aeedc43aa860f8 f300 T4-platform T4-platform
36509039978 2170 5a3e82520408db86161b8fff08175aa30a0adc6c f323 T3-mid T3-mid
36535949326 2368 cc71a2087d32a7c06b0dcc2f925a9a623335c2ef f302 T5-core T5-core
36538846045 2170 1ebd1e40fe715ac9efb328b2c7cebd2c0a4e140f f323 T3-mid T3-mid
1 # Every PR push of September 2026 (one pr.yml run each) with the tier the
2 # lane analysis assigned it (sel_classify.py) and the tier the selector must
3 # produce. They differ only where a deviation is documented in `note`:
4 # makefile-by-name the analysis classified Makefile.cbm by its DIFF
5 # (source registration / test flags -> no tier of its
6 # own); a file list cannot show that, so the selector
7 # treats every Makefile.cbm change as a build-flag
8 # change: T5-core, full. Deliberately conservative.
9 # Columns (TAB): run_id, pr, head_sha, fileset, analysis_tier, expected_tier, note.
10 33464275799 1697 22f90fe883f641c2c460a210a1de3a8df74c4285 f001 T0a-docs T0a-docs
11 33464725171 1986 9d91891cb5dc6b9d67a0d4919d44d6346799af23 f002 T5-core T5-core
12 33484752078 1939 e11d5900cd8735fd2ff5ca5308bc028fbe5ed2a5 f003 T3-mid T3-mid
13 33492041913 1657 7f158e950f874bea6d9530eba16f44b0032f5cac f004 T5-core T5-core
14 33512870122 1941 35efca813cb2e280fd98168e037cc2a098e365c3 f005 T3-mid T5-core makefile-by-name
15 33512876012 1971 a5af586d9275249ff9ab8fd7504c9a1d12f7ef85 f005 T3-mid T5-core makefile-by-name
16 33512878163 1968 9e954104ff5c23bbbb05eccce024b74e6e0a1a6a f006 T5-core T5-core
17 33525282952 1739 2a2e817bbc11c64755e9d9e3c2e3bb951cf29083 f007 T4-platform T4-platform
18 33525334366 1817 e7bf16ac616d3d04733caa075a59902fa204d664 f008 T4-platform T4-platform
19 33529411666 1784 fad564e1e1ad683657ae604bec2c5f0852f19151 f009 T4-platform T4-platform
20 33530652927 1992 abd82a108b459d70a923d7b4a43651fbeb835912 f001 T0a-docs T0a-docs
21 33530742647 1697 22f90fe883f641c2c460a210a1de3a8df74c4285 f001 T0a-docs T0a-docs
22 33533600441 1993 da61c81af243e3277e3089fb474ee5c08fe5c148 f010 T2-extract T2-extract
23 33533605337 1697 4b844b7d9e782ff26284248c072578215cda3d2f f001 T0a-docs T0a-docs
24 33541770974 1697 957b27fca50d2c3b1ac86d771733f39612bae841 f001 T0a-docs T0a-docs
25 33542424474 1811 fb2e38f5bbd45a2504d9daee09fc4c57a8d7db7a f011 T3-mid T3-mid
26 33542805417 1697 4f3412b78a5fa64ad01cb9ffedea603d101262ec f001 T0a-docs T0a-docs
27 33542807266 1896 50eaeebeaa2edb2ce14cabbf0d128e47f47b9c5b f012 T2-extract T2-extract
28 33542815580 1703 359f7360ddd39dc155637076e76a4e855c27611f f013 T3-mid T3-mid
29 33543233252 1811 b149247a0fd78d912ec906f44c2cf30bae7e5209 f011 T3-mid T3-mid
30 33549536985 1918 738174f74a2fbc2ad53ee9f4b2aed1eea2214d8e f014 T3-mid T3-mid
31 33550775043 1996 5e328814fc99ffe588daae4f0f1e8cf5066e084b f015 T4-platform T4-platform
32 33556880174 808 c19373cd4eba3dc836ccd9b12569cceb9aeab970 f016 T4-platform T5-core makefile-by-name
33 33557912833 808 909d01dd80de2c2d2b3518b60e33901dd4f284d0 f016 T4-platform T5-core makefile-by-name
34 33558106767 808 4b33971df0796fe841df867ca202f28a59a5824f f016 T4-platform T5-core makefile-by-name
35 33558842421 1697 40e5e373ab27bba3129405f346aa5937f9c93850 f001 T0a-docs T0a-docs
36 33558848235 1895 230f42454d76e103209e3baa54f6b7bf563cd2bd f017 T2-extract T2-extract
37 33560684540 1998 45b97287c75a0d2f49a28ac900c2dc00d518e594 f014 T3-mid T3-mid
38 33564199875 1986 b2ac3c58a99c534d4882674b5651deb61296be42 f018 T5-core T5-core
39 33564348882 1697 40e5e373ab27bba3129405f346aa5937f9c93850 f001 T0a-docs T0a-docs
40 33564426767 1976 82e1de8cef4121db80c53f4e144530dcf5dcb747 f019 T5-core T5-core
41 33565344188 1876 9d4ac2b98b9c19633ce2cbf8d9c6bf9edc57bba6 f020 T4-platform T4-platform
42 33569142503 808 89f1edcb50ecdec1e0db64002f1d8eed07164d46 f016 T4-platform T5-core makefile-by-name
43 33569301401 1999 2641ec858dff11307d42e60e8e3dccc627d02f0e f021 T2-extract T2-extract
44 33571644044 1875 25a7aacc87250bff752ff61cd33e9236be5d1310 f014 T3-mid T3-mid
45 33571903536 1877 62b44519c68b403ea44d9a85ba8ee74cddee66e6 f022 T3-mid T3-mid
46 33572811960 1912 63f79365e3217d2ef7a3615567bb96627f3fd6b3 f023 T3-mid T3-mid
47 33574864607 1968 a3e8fa3d08691f050f16d75879fd8a84b73ad76a f024 T5-core T5-core
48 33576560737 1986 e8204092333ee1c38cd681a81abb36f125288894 f018 T5-core T5-core
49 33577534823 1999 a739a2d81b43eb6938d014e005b306ab27e02396 f021 T2-extract T2-extract
50 33578326689 2002 976f312ea24acb8b1840ee5f766f3e2588af333e f014 T3-mid T3-mid
51 33578871318 1723 b769ad6324f866a580d731f5579a9d076cc45cf0 f025 T5-core T5-core
52 33580573384 1832 844a36b78aa10f16338d24a1a819ca9a31525129 f026 T4-platform T5-core makefile-by-name
53 33586571817 1836 2f37f61e8848f31f308bce8623f66dc2fdb50619 f027 T3-mid T3-mid
54 33588509339 1824 fd73c347fbcfd7f66c210577b4d713fc6ce7780b f028 T3-mid T3-mid
55 33590570368 1862 0e8fc16abce48346d07af78acf3293a805085e23 f029 T4-platform T4-platform
56 33593858493 1874 c0173a3362878e3cb9e7bf9b65c7ab79b46e73ef f030 T4-platform T4-platform
57 33593864739 1871 4772f4715172173559ba3f9130ddd90df4416ed7 f013 T3-mid T3-mid
58 33596260489 1779 98e386e58b753bcf63f67aeeacbfcb94961d035c f013 T3-mid T3-mid
59 33599715238 1739 4c1b23477b8e0de27feee74eac11c21a1fd64151 f007 T4-platform T4-platform
60 33600443796 808 53a97f0f74e34a55076ea3d1f1dc0be562b1c7b9 f016 T4-platform T5-core makefile-by-name
61 33600512620 808 003f8f4c2efbc22fe72e2dcab7fec5cfd1b42cd0 f016 T4-platform T5-core makefile-by-name
62 33605426923 1931 5cb90f64adaf9c5a38b591971aa7c09e3d9e958c f031 T2-extract T2-extract
63 33605453120 1936 27d4d34f7973823530922ec5485ce19a64589e22 f032 T3-mid T3-mid
64 33605453184 1907 651ae3c2af1a22ec61a498b39350785aee480973 f033 T3-mid T3-mid
65 33605453306 1913 a6b1a69a7c9a9ba67ac16c88e9c11de3524f87cc f032 T3-mid T3-mid
66 33605454090 1915 fcbd12ee1ba8580c1c83ce7ba797613ff5af00e7 f032 T3-mid T3-mid
67 33608282620 2005 256b3d3722247f451a2213023dc36a1ebd5827af f034 T2-extract T2-extract
68 33610039868 1811 cd8a5ed0d7c5674072f5d422b12302aa6424fda3 f011 T3-mid T3-mid
69 33610440955 1946 4bfb074b2892ec181e6b16dee3ee984af6dda201 f035 T3-mid T3-mid
70 33612193345 2006 349aecba9528427fa635975e246f5b8959b14f30 f036 T3-mid T3-mid
71 33612686565 2002 3d9a6cf5cb7fd39c6b5fb4dfa43011aa70f736ea f014 T3-mid T3-mid
72 33614840408 1947 ec0ca565a6571cad6e1c90914400b8dd4c2098e9 f037 T3-mid T3-mid
73 33615574338 1912 97517a4652f376cb6bf096255e235edf456f86b1 f038 T3-mid T3-mid
74 33615892072 1777 7fedaba331d8fbd241582f3ceeccd70af0ade149 f039 T4-platform T4-platform
75 33616176807 1949 91a3c6a5173de50fb47c4ebc5a8ba448598f7d5a f040 T2-extract T2-extract
76 33617264651 1913 dbba7573016e17f20fe5afaafe5cc2cddd98744e f041 T3-mid T3-mid
77 33617265496 1936 ac46c17fc37f78ae518145e73d51b46ca3013fac f042 T3-mid T3-mid
78 33617265877 1915 adff62e7f20dd439129d17c5dc028c7069af92f2 f042 T3-mid T3-mid
79 33620018950 2008 c36b4fbc446f9085704a6073b81ec743fd4180fc f043 T2-extract T2-extract
80 33620040078 2009 592894a4387a50e1d128ff6a2695ff48f8cc32a5 f044 T2-extract T2-extract
81 33641828637 2013 40f2722d6166c1f97fa1667c1e63e8c97e6f05f0 f045 T2-extract T2-extract
82 33653455785 1809 88269ac2ee70ab88f3ee8d84bd509399e3a8f3d6 f046 T4-platform T4-platform
83 33654490848 1809 b026a6907807f7a411e7da4de45d0cedc2e93dc9 f046 T4-platform T4-platform
84 33656319321 1819 64ea496a7b3623f5f8b28343aba99535b282d86a f047 T0b-nonproduct T0b-nonproduct
85 33658603433 1773 572725e61a00b9deda7407971dc9290477170515 f048 T2-extract T2-extract
86 33658798739 1811 3f08fac5139a4acd3f393e9ca78538c1ba8b94d7 f011 T3-mid T3-mid
87 33663175876 2016 0e3c9a3d52fbdd234db180894e94f434efce3505 f049 T4-platform T4-platform
88 33663200103 2017 1e92269d7562d89a816271c4d99904ee53a94606 f050 T4-platform T4-platform
89 33669707175 2016 e0785b353af1af3812d5d14cad55c839efc5749d f049 T4-platform T4-platform
90 33683309620 1649 ff8714662728f55a5ff368462fd8daedc80b89a6 f051 T4-platform T4-platform
91 33687771558 1832 69a9c9005a6d81889cc7fe5e0355e610d2a08e6a f026 T4-platform T5-core makefile-by-name
92 33688645901 1723 83ca40ced11f51e479d4d868ed167b47cfe993d9 f025 T5-core T5-core
93 33700059940 1896 c4e902849f8062e37a0f0ddaed1c6cc042780cb3 f012 T2-extract T2-extract
94 33700061299 1976 7b528b3247257fdc8d503a4af617007bbe3f8454 f012 T2-extract T2-extract
95 33702992083 1874 4529db7eb56c5793227bdce34723827030796a8b f030 T4-platform T4-platform
96 33702997094 1871 2283bcb9eb85b17863786608a0c41b0937521b29 f013 T3-mid T3-mid
97 33703360466 1998 41e649281bb9b27ead5d6ec223a0f0c78924c8f8 f014 T3-mid T3-mid
98 33705255783 1918 426e415f5427f2fb2e9c6f918cc7cc9abd9c109b f014 T3-mid T3-mid
99 33705617151 1881 57ef0a6d8662e9854dc5711922f801c3da381071 f052 T3-mid T3-mid
100 33706085211 1897 2c76563a137876b110cec15b9b6fa08d35069f73 f053 T3-mid T3-mid
101 33706763915 1880 6b801eec68d6465aad2020de438e629cead99f5c f054 T4-platform T4-platform
102 33708048488 2019 ad04339030bbfea4e77139bcfa9a87356c1e0e5b f055 T1-tests T1-tests
103 33727721488 2022 c846ed7b37331696543e71a70a8e5c8dd301467d f056 T4-platform T4-platform
104 33731062466 1874 c38d19139ad9657ab563951ccbf83dcbd1bb2435 f030 T4-platform T4-platform
105 33731071275 1871 adb49b3a28826e6eef871b1777b63668199c0463 f013 T3-mid T3-mid
106 33732861010 1811 da09b73bc0c03f3909a0153d75b8479220c31e2c f011 T3-mid T3-mid
107 33739760521 1811 e79a7d73b45644d86ab3ca3c7af84aa04aebe75f f011 T3-mid T3-mid
108 33740406928 2024 1770d68a2c934501b889e625dc94ef933e3a1815 f057 T3-mid T3-mid
109 33745916176 1811 bbecc9b9e92af203a03433e8cb6968e1e8d21cb2 f011 T3-mid T3-mid
110 33751987784 1996 7c4b5fc8a7c9ed2f1cf3b675bb821dbbccf2c1bb f015 T4-platform T4-platform
111 33752032210 1811 b64e2170009159a5111b1339312512f4e66b1b57 f011 T3-mid T3-mid
112 33754008677 1874 956489dcf88d0fd571efc493e6a93bb1d96760c7 f030 T4-platform T4-platform
113 33757191193 1767 06576511ce84b54b2177a75ae82c3af7dc897160 f058 T4-platform T4-platform
114 33758229230 2026 73f4a432e4ac1d575635a490d96e0f5ea588344d f059 T4-platform T4-platform
115 33763283492 2027 0ac290e2e243148b9e84bb682cf1101fe38bc6e3 f060 T4-platform T4-platform
116 33765101593 2028 1d38be3331576f50ce555d99717974060a6a16a8 f061 T5-core T5-core
117 33765957247 2029 be66a2d2c6f1538a4a831eb2c91622997be5b5ec f062 T4-platform T4-platform
118 33771464451 1809 e65378e6a5faf49abd7a0987994d7c98a9f834ad f046 T4-platform T4-platform
119 33775413774 1809 d402623c20e1dbff62e560a865fd4955798dfbfe f046 T4-platform T4-platform
120 33779258079 2030 3507d9555d73aa746cde23139588081c6c02d2e2 f063 T4-platform T4-platform
121 33799475629 1819 b25b1f65b402ba5395da846f05a306746055594f f047 T0b-nonproduct T0b-nonproduct
122 33799483900 1918 74700067c48b967edc4ef723b658a0fbb04f291c f014 T3-mid T3-mid
123 33799487158 1880 e63292a8d4036fc2618ecbee973bd6cccaaaffc4 f054 T4-platform T4-platform
124 33799492841 1881 4d4af28672729a5c1326974684728c9ee150926a f052 T3-mid T3-mid
125 33799495512 1897 429141261daefc04fc94f35300f7dbd899ed28f2 f053 T3-mid T3-mid
126 33799501343 1767 8a74d814e5149be17f2fc70f9a59d0c166604273 f058 T4-platform T4-platform
127 33799505794 2024 380b1c5af1d5f3e94248752266031b3035080793 f057 T3-mid T3-mid
128 33799508762 1811 f47cd47afe9e7e144f3c6d49df37c5b53d3d1cdb f011 T3-mid T3-mid
129 33809990745 2040 12f5e2c3636b694cbcc76b197815a983349fff5a f064 T4-platform T4-platform
130 33816056229 2041 588cd5543a617c73646be61de6926b9ac7004064 f055 T1-tests T1-tests
131 33816348076 2042 9933548586760fd81f2cf76673ec42c338ff62eb f065 T0b-nonproduct T0b-nonproduct
132 33818108315 2043 7109e5e7b013f43230f735f93043ae608be5d98f f066 T4-platform T4-platform
133 33831172851 2005 ec28e66434bc03ec33dcd05563b3bfd7f58efe86 f034 T2-extract T2-extract
134 33849949280 2047 9104feb67f78959e64ddff78d236e0dea475a434 f067 T4-platform T4-platform
135 33850387435 1976 aa3627d3bfb38627d700043a599622bb0e80ef2f f012 T2-extract T2-extract
136 33850532415 1709 2efb7a6323f992d5c407cc85e37b6d167c184ca1 f068 T2-extract T2-extract
137 33850538524 1721 dc703dad4a706a0f2c8130a869922677dba09ae3 f044 T2-extract T2-extract
138 33850542851 1730 90ba8990764674b2eeaca6fa372c654a63a04f51 f069 T2-extract T2-extract
139 33864581134 2048 f404dfbc7ae47f9912b18a0554a5e5d91f65be1a f070 T0b-nonproduct T0b-nonproduct
140 33868849245 1816 2ea73059f894df564e21be409908c2b5d3d53b37 f071 T4-platform T4-platform
141 33879400189 1996 8a1f2503c33bdd9a4d87782493af5640a505594f f015 T4-platform T4-platform
142 33879404796 2041 e74f041b3387e89fc3d3bc727cae772516099e54 f055 T1-tests T1-tests
143 33879411345 1809 b435bd87b6d0e6a8da43c2964829927fff331f5c f046 T4-platform T4-platform
144 33879415252 2002 c6ff57df0c216dc0fc86442f111607d8b3bd7ef8 f014 T3-mid T3-mid
145 33879421262 1741 fbda6bacf78b47e4c498dfb44ae653f9d10a4add f072 T0b-nonproduct T0b-nonproduct
146 33879427336 1742 e63bc6f06a03e93fad9e962c9b9aaf4b0309306e f073 T1-tests T1-tests
147 33879430001 1976 6f7a4af4a3a1716a5aeb5cae60a112e2231195a5 f012 T2-extract T2-extract
148 33879554682 2043 7e47043acce9bdfd87e7becc7fe7a348b02753ec f066 T4-platform T4-platform
149 33883643915 2042 6b0c44afcfeae65eed82e2dbbd58f00b043f21f6 f065 T0b-nonproduct T0b-nonproduct
150 33883715988 1649 f195c6aa81f425882626cca1186116ec64a0143e f051 T4-platform T4-platform
151 33884431041 1649 89186a26bcc37ae51f943b785270aa66716c832c f051 T4-platform T4-platform
152 33885636468 1933 8d46d2586255fc85e35f00ff8b814424b140e623 f074 T4-platform T4-platform
153 33887517545 2051 e2195dcb8f6fba33b72da60c1f3fffc83cdcd714 f075 T3-mid T3-mid
154 33891234168 1874 8069c7cd4c73ff7ea4513d38d7dfd08c350472a4 f030 T4-platform T4-platform
155 33892259683 2055 36bee9039848829c7d2271c44a46515cdaa8d870 f076 T5-core T5-core
156 33892321505 2056 24f54c0eb64c516941abee2ad0d7de758b34ac86 f077 T2-extract T2-extract
157 33892540012 2055 132e8fc32309e843ccd97138c8f032e317333673 f076 T5-core T5-core
158 33910645341 2051 1067e519c04f6273dd6e2b95b16bd0a9db4c3056 f078 T3-mid T3-mid
159 33913569180 1851 20c7dd3dbbba55770b82a99f6fcec6120f362873 f079 T4-platform T4-platform
160 33914255147 1914 7b92d2a6218936767b112081b9aa4e79d5b81dd9 f080 T5-core T5-core
161 33917205956 1849 39c74bdd5c8b5dc3bcd43f962195add0ba794150 f081 T4-platform T4-platform
162 33918309198 1796 ad479b61e9f23c01606f42c7d44ca174f104a2c8 f082 T4-platform T4-platform
163 33921848127 1597 335279ee403922c0b84c3972c3fc4782ffe878bc f083 T4-platform T4-platform
164 33923008470 1597 0094133cca7d92e587c4d876ccdb37140515fa7c f084 T4-platform T4-platform
165 33923354326 1766 797a11a15eff6154428aeb268c0b55edb5647a7a f085 T3-mid T3-mid
166 33924300711 2059 e0f37d7cb305cd2ff1d709c3ba1198cf950e83df f086 T4-platform T4-platform
167 33924381537 1597 3cd0043d0a3dc282c6a36ae0a262ebe02a4cec0f f084 T4-platform T4-platform
168 33932138466 2060 3a8b8e82120872a56ca4d0157260569d90abe85c f087 T4-platform T4-platform
169 33934626037 1784 4cf2acf2b7483ffa0fe3918f1a38e6d72d08b743 f009 T4-platform T4-platform
170 33935475331 1597 b900338a71c7395ade83c4e371ca277f2cb5498b f084 T4-platform T4-platform
171 33940288019 2062 9b4a3bc91e2f4d50e00deccf236c6b0db177f4eb f065 T0b-nonproduct T0b-nonproduct
172 33941972045 1597 10d05b38a63ff1efa62bfe2d419b01c72bcb2bc9 f084 T4-platform T4-platform
173 33944160492 1597 a15d99e39fd9ea04722f064ed02dc726b13d2943 f084 T4-platform T4-platform
174 33947308282 1874 9ae4ba7648101603eada6e7cd54c6cdc284617f6 f030 T4-platform T4-platform
175 33947974349 2063 f8132b42ac9ee1fde464866b5f3b08939528f267 f088 T0b-nonproduct T0b-nonproduct
176 33948072527 2064 3979f12a749232e1f934949f58f68c83a8b66014 f089 T0b-nonproduct T0b-nonproduct
177 33949446878 1597 9427f435158662074d6576053a6675a403696543 f084 T4-platform T4-platform
178 33951152502 2065 7dfd2c064911a7f2b246b7edd610fab4f993240c f090 T3-mid T3-mid
179 33951945765 2066 4b73a518d720bbc8773970402ca96f5661726b78 f013 T3-mid T3-mid
180 33963617997 1874 41f52a0f3b948468c468ddcca36489eb5f42fff3 f030 T4-platform T4-platform
181 33970672289 2063 3b173288feafebac3f0845f53e481f69adcd70b1 f088 T0b-nonproduct T0b-nonproduct
182 33971609329 1784 b9da6dca03126ef0c8cacccb3321dd5d69ccf34d f009 T4-platform T4-platform
183 33979110285 1822 7fbaf02904363d38c88f9b749b4523a614feecd5 f091 T3-mid T3-mid
184 33979848957 1759 683d4bc312625a3d26819635673529ab02a0d0e1 f092 T2-extract T5-core makefile-by-name
185 33980369725 2056 4f82a1e56f0eef435fed0eac5c8dd06b5b30c2fc f077 T2-extract T2-extract
186 33980647419 2065 3e1a22897ccea3404b95126112009e8b1a719a39 f093 T3-mid T3-mid
187 33980897876 2065 73c509f4246b0f97ca772b6d9a3159181a7fa6b6 f093 T3-mid T3-mid
188 33980899537 2066 c4bec4a6b38ed66ccd97df06490e051d3d9d81cd f094 T1-tests T1-tests
189 33982832791 2067 b7e18c8940d2bb58d85339d27da0754e5aba92e5 f095 T3-mid T3-mid
190 33984123524 1716 439659b333895a49174dca1fe4343f356616210c f096 T5-core T5-core
191 33985769258 1894 b49c7a736c0789069e5c2d07a301143e382af53c f097 T4-platform T4-platform
192 33986188532 1739 b51411753e9423f1c67f810ecf54f9fce903cefc f007 T4-platform T4-platform
193 33986691207 2069 3b9052c955a9aafbb3baff874a93bba19afeaace f076 T5-core T5-core
194 33990039141 1941 b04f54506ab42eddcbd89232a0d47d8355edd596 f005 T3-mid T5-core makefile-by-name
195 33990592318 1971 f89f01e9456c8241a28fb909d930ec8d88b23239 f005 T3-mid T5-core makefile-by-name
196 33993950950 2059 3007dbf8426764a92ace4da111836b86bec6b508 f086 T4-platform T4-platform
197 34001348423 2056 e384f88f90d1f13ac95220f15de72e888a654c98 f077 T2-extract T2-extract
198 34001350759 1822 eb3bd818b597b9c1f317906b350cbf323f2a4995 f091 T3-mid T3-mid
199 34001352917 1716 26682d58c7595c4166d7b7d274885115f2dac324 f096 T5-core T5-core
200 34001355464 1759 b35f73123302d8d101323c3621e6fe755d4970c6 f092 T2-extract T5-core makefile-by-name
201 34001357884 2069 f8951524252b71be8d8d3ea1f86d03eb97d6e9d5 f076 T5-core T5-core
202 34001359042 2066 7da8bf15838c42f8e3f366fc2188ca63f2b61942 f094 T1-tests T1-tests
203 34001473433 1739 9a5e3f36a9580f76211300faa8b7ed5942169b47 f007 T4-platform T4-platform
204 34002279390 2070 c1b9c451c7f124936e635835ca4dd0cf8bf7d5b3 f098 T4-platform T4-platform
205 34003689551 1860 a44732b6f8ee6409cd12b135f14867f1b5ecab33 f099 T5-core T5-core
206 34010341126 1998 04ba2fa368db12a80091315bfa649626a36145e1 f014 T3-mid T3-mid
207 34011344933 1968 17b6dfe68b65ae7fae797487c69efdf110b0cbae f024 T5-core T5-core
208 34012479012 2065 c72fe3222a5f3caac2e87636252931f3cfce2619 f093 T3-mid T3-mid
209 34012788868 2041 874a972fb7c4eb06e739dce0a390fffb85d31f83 f055 T1-tests T1-tests
210 34012788874 1976 eb80a4ecff04d8bfacef20e1aa2ee29445a782ff f012 T2-extract T2-extract
211 34014013202 1874 a3500040e69af8284e3cac1398e00ae3d00a0524 f030 T4-platform T4-platform
212 34014527172 2065 60449f895dbaedb4e74c4f155a8e214d78486e12 f093 T3-mid T3-mid
213 34018899805 2072 b0f9398d59c0c8a5bb03b80eba84d30e630160ec f100 T4-platform T4-platform
214 34019712929 2072 06644428b6d0a6295414d23d2c0613f29a73e1d7 f100 T4-platform T4-platform
215 34019861168 2072 bae6977e91daf703b812136c70556cefe4e3c283 f100 T4-platform T4-platform
216 34035459918 1968 f3e1253859c299d194c608d91d5c89065ca0c9ff f024 T5-core T5-core
217 34037162475 2073 5abd77f5d9580d75f8600914b46495be21e9a4a5 f101 T1-tests T1-tests
218 34037415003 1971 9540d64c52aecf89ce8b0524b8fea69c2aafb0e9 f005 T3-mid T5-core makefile-by-name
219 34037489952 2056 9cb2e9dd1c9fd4e6b3cb9f939ec46264b05c75ab f077 T2-extract T2-extract
220 34037491375 1716 8ab6bb6edd69a4ec64df07c3910a5c035b42c937 f096 T5-core T5-core
221 34037493874 1739 53f55ebe72870695c15ace98101df5af6eeef38e f007 T4-platform T4-platform
222 34037496632 1759 bb21edbbbba442364b88598af97478a97e62243a f092 T2-extract T5-core makefile-by-name
223 34037543448 2041 99fa463023fe63520d5a23c29f777b9d91d553b7 f055 T1-tests T1-tests
224 34038315061 1941 ddb6533cd9c16e8e2be12aa791521420fb427881 f005 T3-mid T5-core makefile-by-name
225 34038319912 2059 306c02d44e9a464faf68640f183fa6e82fea0907 f086 T4-platform T4-platform
226 34038901925 1971 ede0e3b111054c165b0237124084894b45e0e052 f005 T3-mid T5-core makefile-by-name
227 34039040074 2070 9846c3f1711d557c78426913efaff340e37feb4e f102 T4-platform T4-platform
228 34044235267 2074 6db22aadb6e5a92b9b193608e2ac3f420e461ec3 f103 T3-mid T3-mid
229 34044659567 1998 c8ea1cad0dd34e6f6f8c542014a1a22885f67ee3 f014 T3-mid T3-mid
230 34044858364 2075 086422020f5fd9396b46384b10413cdca6994c96 f104 T4-platform T4-platform
231 34044911261 2065 d550dcbe73558bb773211a0ff9463daff91721fd f093 T3-mid T3-mid
232 34045088264 2076 a08b9ecbbda54793275cbd4e93fecf0ad4ff1c0a f105 T5-core T5-core
233 34063281067 1739 1d35def402f586b4c18bba41de33c881eda8cfa2 f007 T4-platform T4-platform
234 34064340654 2079 e410c86f270a013d79639577d82009698b207e8b f106 T3-mid T3-mid
235 34065487385 1998 98899ba5d62c9a66d038b24f3857b137d03d44b0 f014 T3-mid T3-mid
236 34066407580 1716 e3c0010267e783b4270e0ac7c8ee6332b61aeabb f096 T5-core T5-core
237 34066408930 2076 1b89228d90b32ddd46f494df8755f496f6aba030 f105 T5-core T5-core
238 34066410797 1759 fbe9eae11c237459da50a06ebd83f709d655d491 f092 T2-extract T5-core makefile-by-name
239 34066413612 2065 8eb63ecfe79ea1f33fd911b8913a8b704ecde9d8 f093 T3-mid T3-mid
240 34066415650 1739 76d0382f8f958238119c8e36bbdd204838d15db5 f007 T4-platform T4-platform
241 34066415871 2079 dab58c0701d8806ddac2783949f1a478225f80bb f106 T3-mid T3-mid
242 34066418484 1998 a143b0882d0457f2f56b2d734128f914325d555f f014 T3-mid T3-mid
243 34068996256 2082 16a739d32314faeee5539386a92afd2363dc1879 f107 T3-mid T3-mid
244 34069613782 2083 77a0c7d94156a43f19047330c7356be4ee12cba5 f108 T3-mid T3-mid
245 34071276309 1874 4ef5672c53777f9dd316510dacfa3b88e2166fe2 f030 T4-platform T4-platform
246 34074515665 1894 088fedb2d25b869621fd93db05b2f3aa69e30e65 f097 T4-platform T4-platform
247 34074587051 1894 4d3d06ac2aba832ba774cad396a3f445b290f0b4 f097 T4-platform T4-platform
248 34081576339 1971 e5c17de6217ec5938d18d8c51e2c0d7ead116a19 f048 T2-extract T2-extract
249 34081831353 1968 2b1dafd83d6df8b0ad8a064e1b23bc27ecf4de7d f109 T5-core T5-core
250 34085524473 2084 4e0b7e1d168744438f0602735c4fee3970f3caf1 f008 T4-platform T4-platform
251 34086907163 2082 d8dbf01c3f90257d98c7e23a21c5c6f7f64d3a4d f107 T3-mid T3-mid
252 34090735558 1723 c8e7f7e3cc26f1faf8eaff5889d6ff0abd75ae9e f025 T5-core T5-core
253 34090831729 1724 70b411d5cf392e1ad6528e9f78d391304368ee30 f110 T5-core T5-core
254 34090999743 1725 8152277aebdd974badfbe0ffce13dfd231a23732 f111 T5-core T5-core
255 34091031555 1725 980934b2a2cf4c1dd32163894b500e0c96587bc6 f111 T5-core T5-core
256 34091168476 1726 fa11ee4193bb7537e18865fb649d1e73e8be1db9 f111 T5-core T5-core
257 34091245271 1727 f9a7e1fece6264d1677ed5dbbc2894553f7ecebf f112 T5-core T5-core
258 34091261353 1728 4602cb100fb0dd404d49531158204fa3f00d9ac2 f112 T5-core T5-core
259 34091378540 2072 c72cb8bb950019b5451325e52940da956250f0d0 f100 T4-platform T4-platform
260 34091504731 2072 bc945a4d6c6716ee22d314b6b53a7d50ed007081 f100 T4-platform T4-platform
261 34091593211 1725 ca6a5d5bc408d59af6ae9ca1edab0d169664092c f111 T5-core T5-core
262 34091642820 1726 f870ce4cd80c0882568ba19438a3cb6ebb2e43a4 f111 T5-core T5-core
263 34091649819 1727 8d4c416579db0148869f3f5c7ffed3e0e90c4417 f112 T5-core T5-core
264 34091656935 1728 37e213860e8f8390f54880bfff717345d2da5689 f112 T5-core T5-core
265 34091819685 2022 84fffefa48cbb60ca1533d2433921787c8ea1031 f056 T4-platform T4-platform
266 34092213233 2072 838fb7090f3a08920363e9ee793824b4d6202bef f100 T4-platform T4-platform
267 34098390879 2085 9f57edfa576072398728eda2c6fc2585d2ecb063 f113 T3-mid T3-mid
268 34109049475 2086 b9d550b6f3e1c08ce6cf59140601205d609dcd52 f114 T5-core T5-core
269 34109055503 2087 45b28f0b11d3e74e28e492c6c234a0f7ed0b30e4 f115 T5-core T5-core
270 34113927999 2086 2082b2d2890ceb007e04b9ba77129d14a8f4df52 f114 T5-core T5-core
271 34113933101 2087 9a7445e93de55eaca53855057ce6ce1a6f234564 f115 T5-core T5-core
272 34115081932 2073 8e29e1568f442ed9422e0fcd24d2594ae0ba7893 f101 T1-tests T1-tests
273 34118689934 2089 dc34e63a0a0aa29cb70456b692733124aba65350 f046 T4-platform T4-platform
274 34126118251 2090 6b56d78e1bbb5715c9d0c715453244187bd299bc f116 T2-extract T2-extract
275 34132427862 2073 aec9bfe3751d8c1f2eb2cb48d8fe584b9d3fd973 f101 T1-tests T1-tests
276 34134175243 1725 8d57fa45ae548e23b11083072447a62ff053da86 f111 T5-core T5-core
277 34134183283 1726 c263f48d0307540b0837726ba5a3c2cd9d2ea012 f111 T5-core T5-core
278 34134191503 1727 94119b3803e276a4cc45f4b92ddaf3d1377e3889 f112 T5-core T5-core
279 34134201351 1728 f588bc374ab91697ccb4467f5df9686ada5cb62c f112 T5-core T5-core
280 34138662959 1784 85482e3c599d454d76ccef5e862a5dc6c38a898e f009 T4-platform T4-platform
281 34139187715 2091 b974adf6ed9b82d8669d1a65b1757816ce0d12df f117 T2-extract T2-extract
282 34139262637 1784 a21562666317f752f9921109a0c70427f3febe04 f009 T4-platform T4-platform
283 34141564461 2092 d1abca20c091b9623187c5cfc61f251e7bd7796e f118 T4-platform T4-platform
284 34143404498 2091 ec56ea8f60f0058bd1cd0fffb38cc0153b99f895 f117 T2-extract T2-extract
285 34143899077 2092 1f08f82adcd979ba2f0efb38a1ccfed13901f76c f118 T4-platform T4-platform
286 34146676636 2092 4f8290aae42ad3f2ca4767089057e74756311743 f119 T4-platform T4-platform
287 34159409001 1561 b939a2d6ee76e482288a0dbbf712cd94d0d57164 f120 T3-mid T3-mid
288 34168455959 2094 7219a24cc692500cdeb5cc192adbd47ac8bb6ee1 f086 T4-platform T4-platform
289 34168458726 2095 d3eb7e9823b43a8e2e5917d2d234367c1f88436d f091 T3-mid T3-mid
290 34168461878 2096 9e1faa7327f8192101f16d8d6d81020937475166 f121 T4-platform T4-platform
291 34180076668 1561 6875483ffe40017f79884872a325e2ff6b37befb f122 T5-core T5-core
292 34180195791 1561 a7941a4d2094aa6bbb0683e3a17371a0463de209 f122 T5-core T5-core
293 34186497464 2097 d653052508998144e39357a37f72a12448c769f1 f123 T4-platform T4-platform
294 34187802611 2098 5f9ac15818e3bbda9e031db54867dfab2b6c531e f124 T2-extract T2-extract
295 34187888044 2099 95f252447de4ae08707b199f02f296f28f8dcb21 f125 T4-platform T4-platform
296 34188664789 2100 2a47a6556cc14dd5ac036b3c2f3225c9925cdeec f013 T3-mid T3-mid
297 34190172584 2060 8622d6a0ed4935aedaa336d216d8b6b269732de5 f126 T4-platform T4-platform
298 34192737413 2098 f6c310c08e6dad38b92e953c85c7c5876f6df839 f124 T2-extract T2-extract
299 34194384003 2097 d9dec5dd3b334a514113670b42732ca9ea4a15de f123 T4-platform T4-platform
300 34194639858 2100 669f6a3741ef7b875aa5eaeda317acf1900bcd33 f013 T3-mid T3-mid
301 34195390569 2101 0c1d6f68dbf3f2c8215ae9c52003d785ac874a22 f127 T1-tests T1-tests
302 34195424010 2060 3c854ae217c4c5d2f48d8aaeb136541bb0d8b52a f126 T4-platform T4-platform
303 34196235240 2103 9f876f70ed13636a8f9a25ba043e8f682c6688f6 f128 T4-platform T4-platform
304 34236364592 2105 22e0335d94552dc1a9d9ef0ee8221e7665608209 f013 T3-mid T3-mid
305 34240237866 1784 18b2f8670c64fd83221617c5e7970fb95af17454 f009 T4-platform T4-platform
306 34244310689 2105 dcb10fe902905926eaf9325bb03254dde9cea6ab f013 T3-mid T3-mid
307 34249699686 1823 b09ef3df3289e74bae129df4df29886fefc75484 f076 T5-core T5-core
308 34249700997 1823 b09ef3df3289e74bae129df4df29886fefc75484 f076 T5-core T5-core
309 34253442941 2109 e65fffc5b1ac269c16d4371fc8c1ccbd102e267a f129 T5-core T5-core
310 34255410324 2109 840de91e384d2ea36dc2360ee12ace26d25b680f f129 T5-core T5-core
311 34257609138 2111 7b4eb4a00ac600060be4836ee4e1bbfa7bbedf24 f130 T4-platform T4-platform
312 34257629348 2112 21591d51168d26f931f64d62edc36013cd1f6249 f131 T4-platform T4-platform
313 34257632907 2113 00060cec854dbd38d7864aeb95a2d8ba1baa8ab2 f132 T4-platform T4-platform
314 34260896139 2110 d40ed1a6bbc968bbddf90abd3b38e74a131a1b5a f133 T4-platform T5-core makefile-by-name
315 34261770203 2113 0567dc70a097f36a953368978d6728b0a412b229 f132 T4-platform T4-platform
316 34261855843 1768 e8dcb28ba719e4272f605f3b961422bb1f3c3fad f055 T1-tests T1-tests
317 34263433901 2110 7bf366f63b3fd5211cec19e483ee2ed27145ca14 f133 T4-platform T5-core makefile-by-name
318 34263688699 2115 013e1f7ec7d4b981a1cd28bf35d5767c195f8447 f134 T4-platform T4-platform
319 34264299704 2116 3a4160b4610f83ea26daf69f1ffe7cb898bf48ad f135 T4-platform T4-platform
320 34273160491 2119 8135be28a764fad4ee55abe466762bbda7ebab74 f136 T4-platform T4-platform
321 34274955966 2116 7e73b48ef155a0e14a6e058be56287232e3976ea f135 T4-platform T4-platform
322 34275249072 2111 91e31211df9dd61459617d857d46b71699e34df8 f130 T4-platform T4-platform
323 34278140735 2110 a3c24a71b4c28e805b912f9f928b6b0b151f9495 f133 T4-platform T5-core makefile-by-name
324 34282381014 2113 ecfb9642e153ffc656d0bc2344b17e9d454ff916 f132 T4-platform T4-platform
325 34288397854 2115 92cb3b9a4bebbc79bd283e447104669ca068ea64 f134 T4-platform T4-platform
326 34315414268 2116 e45d705105a63f2fd9b35ab9e37ffbc640be8231 f135 T4-platform T4-platform
327 34315782814 2124 8134712dc43b159fd863244ce53ba23641f20f8c f070 T0b-nonproduct T0b-nonproduct
328 34327374288 1723 21ae02154a198265d7dbed13af4280aa2b1e5690 f025 T5-core T5-core
329 34327382260 1724 87da4422c34feb4df8ff369dab8d770c33d142eb f110 T5-core T5-core
330 34327387577 1725 d82f760040412d51d424f3c287d277e89d4fb199 f111 T5-core T5-core
331 34327494146 1726 9380b22a24b3ec85482fd209475f5b1641b5f699 f111 T5-core T5-core
332 34327502315 1727 579336cbdaa6493f47ed4de10f64387b6f7f16b8 f112 T5-core T5-core
333 34327526954 1728 9d5cfa53098003cb68bf1812a94ca51f5e046056 f112 T5-core T5-core
334 34379806132 1823 5e5e433d94880c283400215ba79094225a8b33df f076 T5-core T5-core
335 34392601650 2116 9ef7da8cbfba8de5526ccd05e7196f5fa73af8f1 f135 T4-platform T4-platform
336 34392735468 2110 91c63be5d4e41d44e0746ef533af4a0b1967d6b3 f133 T4-platform T5-core makefile-by-name
337 34392998828 2136 557cb0102863e66cbd5cc622509a29a4964d1831 f137 T5-core T5-core
338 34393093741 2137 4fa11d1ab0b5807a7eaed645a6e3675f0663def8 f125 T4-platform T4-platform
339 34408696449 2138 8116672a7e3a02aecb5cb0e6fd9e685cd5066fe1 f138 T4-platform T4-platform
340 34413434571 2139 df579830f3941e5ebaef73dc87d183aee358fbcb f139 T4-platform T4-platform
341 34414749652 2139 618849512d01a3d14fcb704532911f9d02c23436 f139 T4-platform T4-platform
342 34415791973 2139 62bafb76b380d3af1cd1d6228b4afea0082c2a7b f139 T4-platform T4-platform
343 34433093443 2140 a1d11e79087bb1284d6126b02b22c3ce7e72ab07 f140 T4-platform T4-platform
344 34446903088 2089 8f3c15044b8200a15ee1895c5127f4bf117da493 f046 T4-platform T4-platform
345 34456518090 2160 f7b56b900d4726221de2882d5a5d076b29f6758f f125 T4-platform T4-platform
346 34456521294 2161 c8e244d8ff1d51e57c9d757a8e6bad80dc67f107 f091 T3-mid T3-mid
347 34460157213 2159 8d353a573dcc4be7ae69a74685eeb819a5626b5f f013 T3-mid T3-mid
348 34460157936 2158 b5f119c3f27bc3601bc048b16bbfaf57af3908f2 f141 T2-extract T2-extract
349 34464851279 2161 6790e1ac0fc134360cda6544befc1af2e29b9605 f091 T3-mid T3-mid
350 34464853698 2160 b3af44832790fbc6dd6a7e09d18362f55149a4da f125 T4-platform T4-platform
351 34465154636 2164 70926a2956ce986bfacbf6e7ebfd398fd91ff266 f142 T2-extract T2-extract
352 34465653349 2164 9115f5154383bd091e125c956aa4f241ebac1c7b f143 T3-mid T3-mid
353 34470429949 2159 7e111f877b85b79efdd371475d7f71e61174f95e f013 T3-mid T3-mid
354 34484066063 2092 b2bb18cfc79e129f50b5da940413e5735bce09b1 f144 T4-platform T4-platform
355 34491583983 2092 b317506b5a62a3383e763011e5b148a5afd92a89 f144 T4-platform T4-platform
356 34491670375 2092 b037b488be4620a5e92424e7e390ee625cd7e5af f144 T4-platform T4-platform
357 34524166740 2170 5fc107a0a61419faf0a2495ab923aba25a37ddb5 f053 T3-mid T3-mid
358 34528400072 1823 ac75e587425c60154dbcaa7392c0522764c93647 f076 T5-core T5-core
359 34551561701 2171 4842e11b2d9f5e6c2ced39d12dc487bcc7d915be f145 T3-mid T3-mid
360 34595459266 2030 e65bc6b71e32d20aeafb42c00b6097a0e2ded2c0 f146 T4-platform T4-platform
361 34595482014 2030 eeff3f19ff97d60af4f34298aa38a652fe0705b2 f146 T4-platform T4-platform
362 34636198205 2179 6091db7104bcaa0b61566272290e04ce448851a7 f147 T4-platform T4-platform
363 34636557089 2116 f6a29270b72a5373e4d949ed3f4078a40830c57d f135 T4-platform T4-platform
364 34640188970 2180 527016258e0da33ef883966f618609f4cf58abf8 f148 T4-platform T4-platform
365 34649361704 2139 53700ee1c72f1a5c2551a83c74d20278e4ac664b f139 T4-platform T4-platform
366 34649395431 2137 ca36316c6b34a6081de24d69699665a20b16de0e f125 T4-platform T4-platform
367 34649399412 2113 ca86fdb057b3d7f6d31e179ccbe64629fb1b0921 f132 T4-platform T4-platform
368 34649404928 2110 33534653f10c052f2a5c7f1953756aeb10499be9 f133 T4-platform T5-core makefile-by-name
369 34649406520 2115 78a6d19f1eaaa73054639ab0f14c372c53cd1bb3 f134 T4-platform T4-platform
370 34649409769 2111 951b9ab022df391410528fe98769d091c203fc60 f130 T4-platform T4-platform
371 34651276769 2181 c8184ed94887a6b5e130726885b44a07c63a47b7 f091 T3-mid T3-mid
372 34656706072 2182 14bacf8919b64bea19b537b74c894f0f82783bdf f149 T4-platform T4-platform
373 34676336284 2185 eb5e34cfc141454e849daf413f648e73f74c455a f150 T5-core T5-core
374 34676377540 2186 f6281874bba9a78ac7543564ff8f76b5262daeef f151 T0b-nonproduct T0b-nonproduct
375 34676429093 2187 cea35fcc0520a0ba95139bd0210052f09b9cfa20 f150 T5-core T5-core
376 34696991657 2115 9a460b389ce3002ac164605c3627bb86aa3b9be3 f152 T4-platform T4-platform
377 34700514102 2188 734906379f235c07cce960ce6a57746ea3be48bb f153 T5-core T5-core
378 34703996478 2115 fa99ff64eb6579fb15f3aae1aff7da349cb2999a f154 T5-core T5-core
379 34708249832 2189 22cdf7d03db87bba19a76a072dd517dd88a54d40 f155 T5-core T5-core
380 34708361984 2190 8c2692c1ee32284c813c05746e833dea5dfedfe0 f156 T0b-nonproduct T0b-nonproduct
381 34710253103 2189 1ad52f5fb1b3d96255756de01ce30912b1899e09 f157 T5-core T5-core
382 34712092850 2191 27cacee1c2d3022fa0fe915b03ca4310042b7514 f124 T2-extract T2-extract
383 34713347879 2190 044fef7957d48a097b48f4ac3c505aa67de8164f f156 T0b-nonproduct T0b-nonproduct
384 34713405483 2192 9073fe4469bd222ca01bd48249b52bbf7ed5710d f158 T1-tests T1-tests
385 34720983404 2076 2b80b31306b3dbb9ac8bc0ad50372f724f1dd243 f105 T5-core T5-core
386 34725488475 2194 09c0e88a6484c5fb696d7fc7b5a5e3f32b8cba68 f159 T1-tests T1-tests
387 34729075944 1561 e0b9642010cbf003bef9b8b29ba4ebb02b38171c f120 T3-mid T3-mid
388 34729170802 1561 789c60c86bc75b342c52ddedcaf174d12ccdfa73 f120 T3-mid T3-mid
389 34745069386 2195 469c3dd996f23cdb629f1bbd6a751bc0f29a99f2 f160 T1-tests T1-tests
390 34746469011 2195 f9038b84eb3bf82c2dff4cc4ea7f1ec1d8d13f01 f161 T4-platform T4-platform
391 34747474867 2195 d21e6cc762a1816d8c274174c9bba01601b888ee f162 T5-core T5-core
392 34747919526 2195 085017ab2172c2f3d4cd3e27251033ecbc5b814b f162 T5-core T5-core
393 34748079069 2195 ac8f5b8a8137f15e95478be71ee6742e928ba5bc f162 T5-core T5-core
394 34751940360 2073 232764a12295a59f05619a2bd032c8f92af6f17d f101 T1-tests T1-tests
395 34752354822 1716 8d4fed61db63559a0961f11f821db21c26e7d24f f096 T5-core T5-core
396 34752961647 2196 81a8a03c820cb8bccafd0ebac355eede21b5e8bf f163 T5-core T5-core
397 34752965817 2197 823e720982e1bce047a9243bd9b24d9688dadb72 f164 T5-core T5-core
398 34752971903 2198 b5b69af7f8d0d1083974e993f0e9b92a435fa253 f165 T5-core T5-core
399 34753122921 2199 d14bc7051c8a32b2678bf10d5ee711637d606edf f166 T5-core T5-core
400 34758237901 2201 1167972031d427be6773b86ec6dc9903317360d7 f013 T3-mid T3-mid
401 34764451388 2202 7fedfe85c63446d3695b75d463a9b8767655730f f167 T5-core T5-core
402 34769627346 2203 848ce778766d73ab4eb09ba8c98001da8f587fe0 f168 T4-platform T4-platform
403 34769883104 2203 00411ce7bc41995783bfb16aada673515585446d f168 T4-platform T4-platform
404 34769958569 2203 d64c9f2afe6b2add648ee1e3eead48acda025579 f168 T4-platform T4-platform
405 34797944694 2203 581f0c74006beb3edd28babbebeda7e48b3fa1d6 f168 T4-platform T4-platform
406 34805101784 2202 7b77fd4834afe355cc4c7326ac3a55f0502f7264 f169 T5-core T5-core
407 34822774278 2205 e0a6133293556d87de6c2ca43de674e3a643e339 f170 T4-platform T4-platform
408 34823711563 2158 334e0b0849241430d26c3fdb6ffc8ec7a676965c f141 T2-extract T2-extract
409 34823714967 2159 63cdde702d9eb366b58006986e1498a18ae7f7d8 f013 T3-mid T3-mid
410 34823719320 2160 c1dd44151fee8cfd815c693392a7c1c72def98fd f125 T4-platform T4-platform
411 34823723515 2161 80f8bda5670c8ac1956e2a8fac7221c40c12da47 f091 T3-mid T3-mid
412 34833703378 2203 3b7f2559213ee80f5cfe0e2329c418211e09749e f168 T4-platform T4-platform
413 34853838753 2207 45c94be627a3088c275ff4f6acdc1f118515849a f171 T4-platform T4-platform
414 34858382857 1767 036562df35486924c62cdb11a8c9518e479959d1 f172 T4-platform T4-platform
415 34876200704 2202 9724d903046626df0640ced218259cb58070e4e8 f173 T5-core T5-core
416 34884655464 2202 9e69cb7d9b7ad4b40fa5f8cbbb653dc70e108130 f173 T5-core T5-core
417 34886874258 2202 d89ccabef064661794269c0de726021736d72b51 f173 T5-core T5-core
418 34891515627 2202 d70b79417978a9f3ab0d1aa849879dbf049793cf f173 T5-core T5-core
419 34898586377 1823 d4ae05a03dae831972a9dae7d2a2dd2ffce39e35 f076 T5-core T5-core
420 34943783091 1781 7c88e64eb954e9400273d3ebb9f6f88f1aebc165 f174 T4-platform T4-platform
421 34986091885 1823 955e4c9c58504b3c8e7d3e42866d16fadbb6ca7d f076 T5-core T5-core
422 34993109423 2212 172787de9c18352bb29545da2426d9d61b260fc3 f175 T5-core T5-core
423 35010526411 2213 d401186ca09e3992bc0e804af3945fe1cf81d6d2 f123 T4-platform T4-platform
424 35010533052 2214 1f1f8334ca25502c3ed1d4e91e8c6144d07bb7e9 f013 T3-mid T3-mid
425 35010542675 2216 729c3acc0fedae0ff77629281caf6dba1ac487fb f013 T3-mid T3-mid
426 35010543243 2215 783547e4fd79bb6b38f0741b6118a719e4109059 f127 T1-tests T1-tests
427 35010546755 2217 193c60d66db7eb36c58a7d0803d3936f5dae1d70 f149 T4-platform T4-platform
428 35010550773 2218 0a1d229ba89c5a7ad6c63c1587f5a938cc890270 f124 T2-extract T2-extract
429 35010559332 2219 a335422f63be3b5711ad40742bbaba40b2600f25 f158 T1-tests T1-tests
430 35016014963 1907 50ee111714baa9d3bfe9bdba3b81e27cd1bdfe38 f033 T3-mid T3-mid
431 35018285825 1913 da7619474d5ad27ff5073cf505b0b6cd5d0db36e f041 T3-mid T3-mid
432 35037109751 2220 62fd80b842f00bd72e655d23c6afbfe12e199b6b f176 T4-platform T4-platform
433 35037798343 2221 22ce867efd88dff9c219f789967ed2612329ee6d f001 T0a-docs T0a-docs
434 35047093950 2072 e5076d215e3a31ee7f7528d9e5ceda83fd8e8a8a f100 T4-platform T4-platform
435 35048046352 2072 8199efa4710893eb77d7c98d2730683a6c51327f f100 T4-platform T4-platform
436 35072023020 2223 17a326a58b591e786790e79ceb3be6dc14ee3988 f177 T0b-nonproduct T0b-nonproduct
437 35113639467 2223 43620d39d27554548fc2d613ed707efc8676748c f177 T0b-nonproduct T0b-nonproduct
438 35129477140 2225 58c1c0caa7061ae868305dbe59a6c5e67004849d f178 T0a-docs T0a-docs
439 35142303658 2227 dce2c5be9710c364eaa3a654d417ffcc392b62a4 f179 T4-platform T4-platform
440 35145042859 2227 3d848acaa7d22885d5040c957b3aecdcc4376107 f180 T4-platform T4-platform
441 35186676917 2140 b74c03558045bf29b7ef95c8aca803c15d3c7594 f140 T4-platform T4-platform
442 35272232918 2230 e0becdeb094dd5e898997a2add826a9f84befbd7 f181 T3-mid T3-mid
443 35273896570 2231 7054e4ff8af76f4fc1fe89407874e9910d0e412d f017 T2-extract T2-extract
444 35295711667 2233 f0a86ef2c2bc352a479162cca79d1aa64598f09e f182 T5-core T5-core
445 35303378012 2140 b52df4b0ce5c38667277716cd97ede964684802c f183 T4-platform T4-platform
446 35303488221 2140 ca3461170fefc959fa7688fae4e212d6e9f2f99a f183 T4-platform T4-platform
447 35312734228 2140 d85fe1d513ac97c8044b1065bbd0b7b9ebc87ec9 f184 T4-platform T4-platform
448 35314622268 2234 9b4d697a57a03f242164db956843a5313168005d f185 T4-platform T4-platform
449 35330927778 2236 949492b50c7dd81271f0180d8425d32316becbcf f186 T5-core T5-core
450 35397596933 2233 1d58385b513a414ead34544346e86549dda3c4b1 f187 T5-core T5-core
451 35443109227 2203 e176ae552b99f0fd08a2dde8bdf52025a3e20059 f168 T4-platform T4-platform
452 35446976110 2233 8c1a9d61c27292bf7a52f10fb54186d4e24de28f f188 T5-core T5-core
453 35448941856 2233 9db5217bdbb779212c5d066ed1a84f7ca6fc0282 f188 T5-core T5-core
454 35467285032 2233 9db483402ea2fd4e0a5ff3bdd2b1fc50a7518ec9 f188 T5-core T5-core
455 35470450948 808 bf2c54e0ef45d0d9767d7945a160f630f890f27d f016 T4-platform T5-core makefile-by-name
456 35470489882 808 80d277834a233f2e7a2df68b8c08a1f233cf3dfa f016 T4-platform T5-core makefile-by-name
457 35472952969 808 5515ca4ec6b20b62ed0530489785e4f131fd5159 f016 T4-platform T5-core makefile-by-name
458 35473919864 2225 c067c771b9f93d08a3e614e68aee7d710d2f256c f178 T0a-docs T0a-docs
459 35475706415 2241 6166934defd9bc5b687ab9c8de3779e71a844395 f014 T3-mid T3-mid
460 35475754479 2242 e8a46c16e233bff111092dff18985cca94890bcc f189 T2-extract T2-extract
461 35481840120 2236 aac550b732040acfe6ab6c0bfe5d0cbef5d9f9e9 f186 T5-core T5-core
462 35483670414 2243 243b405afeb3998707ea4d556f3178c8e8419cab f150 T5-core T5-core
463 35483768695 2236 d2dd3b347bbdcc695f911e80b1b7f7a32d790e5a f190 T5-core T5-core
464 35486887056 1999 aa4da14d23dc28df58b9881dc66d89c0d1c0d788 f021 T2-extract T2-extract
465 35493947102 2236 2caafc0c41bd083092afeb692824e718a8e1e007 f191 T5-core T5-core
466 35495099219 808 83667268d9c435c2634994efef45b09daa98904a f016 T4-platform T5-core makefile-by-name
467 35498544795 2227 656fe99f9934654bbab927d29add13f7822e1bc8 f180 T4-platform T4-platform
468 35499978643 2244 56de845327dce03cdd1269b8aa73c6a1ecdb5cfa f125 T4-platform T4-platform
469 35500380927 2245 ed78ff056e89da814ff5ae05da6fb32b7e10c0db f192 T4-platform T4-platform
470 35501823298 2089 e171bd48b5e5845ec6f00db65ff300caac4f7304 f046 T4-platform T4-platform
471 35502589515 2140 618fbca0c39da7157c7f01a26071ef3a4d4a5b00 f140 T4-platform T4-platform
472 35502728596 1766 81820262e61f6e7f11920a5c4596b4297e793cf8 f193 T3-mid T3-mid
473 35502852336 2246 fed0c1b4e3563f1567b3ff85652ec51229014a83 f194 T5-core T5-core
474 35502954849 2246 2eeb15f434d9520e0050fe1f5b6c4c8316a4a69e f195 T5-core T5-core
475 35503248954 1766 4eb3af36c7b055987d5c0764482135658b28c550 f193 T3-mid T3-mid
476 35503753154 2246 5b4ea78927772d00f11abc41f217f89e714ace54 f195 T5-core T5-core
477 35508860520 2247 9b1dfda875736a91073c57badce3dea6d0f03080 f196 T3-mid T3-mid
478 35509728718 2248 96a2fc35dfcf14662cee491430366f4e8d8b8149 f094 T1-tests T1-tests
479 35514366896 2249 051702c6f9163f0a19dda19ea14ac1b72dc7eb3d f197 T5-core T5-core
480 35515754123 2090 7036c4d5996253c05d720ec4d0b3008509b4cad4 f198 T2-extract T2-extract
481 35517816031 2090 f9350caae0ccccf823d8cde85683a900b257f239 f198 T2-extract T2-extract
482 35517948650 2251 151d6077cf418a42628bc7e38bda4f873877f147 f199 T4-platform T4-platform
483 35518289769 2252 4ecd8ab2c3995cc92c1a8df4f5e2a1e2d3a8c342 f200 T2-extract T2-extract
484 35519906910 2253 458b0db1ba3274ceee1e975cfd7300f13aef82e9 f201 T4-platform T4-platform
485 35519951214 2254 8aa77f89ce1ef56c98d989b03bc35fa4c6289b8a f017 T2-extract T2-extract
486 35520101152 2249 238f9e6c6cad0e086eb06c19f1a6a303fbb30fdc f197 T5-core T5-core
487 35520168086 2247 9af73b327e3899c45fc1faf13b590c09efa2078e f196 T3-mid T3-mid
488 35520178069 2005 fcf451c6a6dda768dfec5a8f61a01ac1e1cfa6bf f034 T2-extract T2-extract
489 35520796432 2255 0541b033fa874c38167e4f4d204ad452f1d96d08 f202 T2-extract T2-extract
490 35520869334 2256 b565e9d8b06e8703f1c15a4263a987837dd0c5f3 f013 T3-mid T3-mid
491 35522168154 2257 af3e003ad8e67cd1fe3b20dafaf4d33a4b351852 f203 T3-mid T3-mid
492 35522190984 2258 de4a88f22242f53f724fc6cc8dcaed79642dc5cd f204 T2-extract T2-extract
493 35523164377 2259 460ebefc87df4dd5142bdc339702edd4feec4696 f078 T3-mid T3-mid
494 35523331086 2260 25d08258e567ec417bf7b18b14a63e30162fc7da f205 T4-platform T4-platform
495 35532624016 2261 28d62001fd1e5d0a18085a8a662a1b806747815f f206 T0a-docs T0a-docs
496 35532636527 2262 16a7291b56aafdcde27928f892d477e8b94269eb f207 T0a-docs T0a-docs
497 35533089935 2257 c1128db8187db2dac27a7ad14120fc09802c8018 f208 T3-mid T3-mid
498 35533328007 2263 cda5e3c9e5c4d2251467ced00ff305205c368d17 f209 T4-platform T4-platform
499 35537157817 2005 6eb8992462e506bf0c58aec88274a9a62a07676b f210 T3-mid T3-mid
500 35537525864 2005 fcf451c6a6dda768dfec5a8f61a01ac1e1cfa6bf f034 T2-extract T2-extract
501 35551212771 2236 39a8bc3f2a7fbbc4d86ec8379b669a31ed1f8318 f191 T5-core T5-core
502 35556558511 2140 af4604189bf23c14dceee9bccd89a16f5bfcad8c f140 T4-platform T4-platform
503 35561381902 1724 42a3d7105548c65ee12f6dffabea718573501601 f211 T5-core T5-core
504 35565389237 1809 19d9e4ac4d0a8c6839b627c0cd5890188121fc2b f046 T4-platform T4-platform
505 35565392645 2075 e321b1fceaaf4c9f03fbe82547f02c1079e3b760 f104 T4-platform T4-platform
506 35565401625 1784 381f64c15ebd0a676b85347acfcfd3f45c806a11 f009 T4-platform T4-platform
507 35565472800 2260 2dba3cf81750f245d8c1d912997f04be5615df99 f205 T4-platform T4-platform
508 35565493742 2005 7d01d3ec031dc26759283fff96e979c3e0ce270e f034 T2-extract T2-extract
509 35565496531 2259 6f2f83618134bee208c98c47931579f27a712809 f078 T3-mid T3-mid
510 35565502343 1768 6115e5785dc01f5b403cbfe7f184ffbfba058e45 f055 T1-tests T1-tests
511 35572275939 2158 d4931241adaef732066afb5ebf1cb225e2474403 f141 T2-extract T2-extract
512 35573014946 1716 95fa63441bf80c9ed871fa090ba925b8b6106d83 f096 T5-core T5-core
513 35574966373 1724 81ff2cabcfeb4619e24307c0267a1244f38afb40 f211 T5-core T5-core
514 35575687750 2160 2e4b45931b69671a92402f8fdf51f85542eff268 f125 T4-platform T4-platform
515 35578606399 2199 fa138acf44525ef94cfd6e16772d601da5623aef f166 T5-core T5-core
516 35585540293 2198 9574c316e43527d6ca90eb007a9c8fc24fcfd88b f212 T5-core T5-core
517 35597253045 2268 f0fac62b7140f2cc3ccc011c74e798be7681ec61 f213 T5-core T5-core
518 35606545382 1724 93f86be139b4bff1d079205fc776babc67f67349 f211 T5-core T5-core
519 35607169136 1716 bc5b3dd216568c864b41303a674020a2d456ced9 f096 T5-core T5-core
520 35623513958 1823 c017685d66ed9bca5476313ede52837ad07f17db f076 T5-core T5-core
521 35632304230 1724 bc81c4a84f19f2f5d90d985ec69dcd5fc357ea25 f214 T5-core T5-core
522 35637728335 2140 fca33717d1b6a9171decaf4d1755c88f48dcb572 f140 T4-platform T4-platform
523 35638029971 1724 d48afe8d3070e360ca908d841d85adad344b2ed6 f214 T5-core T5-core
524 35638134068 1724 3743fe95a0fc40f584b88bf4cbaa0f9bdfd8e52c f214 T5-core T5-core
525 35639932135 2227 df83d25ace9885e252363cb04be25a17da85c9e8 f180 T4-platform T4-platform
526 35640451450 2269 65d60e72f5b86f5982b15f0d88826ba79a085aa2 f215 T5-core T5-core
527 35640454709 2270 f428d583008ce87281bac138c02bd4375c56d735 f216 T0a-docs T0a-docs
528 35641634636 1999 e032a8ee9409c7a53a5d732ddff67c15721bf1ae f021 T2-extract T2-extract
529 35641641970 2158 61cfb09b52ba40e8fb3ee9703c618b28739e3298 f141 T2-extract T2-extract
530 35643957306 1724 5033f97d5f5154cddecee760e5b2a5ad290b181a f217 T5-core T5-core
531 35664547053 2272 da4bd25a01b612517512ee628bf0ec652000e80a f218 T1-tests T1-tests
532 35665885098 2273 c314dea4c1409a3a68fb33706b49e272809b5079 f219 T2-extract T2-extract
533 35665890640 2274 343d7dd5e47b817999098605370f0eab706675f0 f017 T2-extract T2-extract
534 35666550961 2275 a041794f8e51ddf42342899be7b2ac26f951d777 f128 T4-platform T4-platform
535 35667708865 1741 c0bfd073ff25484c2d7b3b59dea937be6c92ed08 f072 T0b-nonproduct T0b-nonproduct
536 35667710693 1742 dfae618b0e9508cd4998b583ebbeb36cf458278e f073 T1-tests T1-tests
537 35667714864 2170 36730c7d895ae7541f780d650151b3590fc766cd f053 T3-mid T3-mid
538 35667829346 1724 879aa5f9623bab14e8fb903da23bf84a8d0bc32e f217 T5-core T5-core
539 35669146114 1724 89030aacb21df9b72f23b42286e3a4eee63fd43e f217 T5-core T5-core
540 35669196507 2140 2f557f53986976b2df1eb2a1a540c9d48cf91a8e f140 T4-platform T4-platform
541 35670303027 1724 b47ba846bbd1a6fe7359c86314c81fb33f5adaa4 f217 T5-core T5-core
542 35696787516 1832 6ae6cd4c3f280cb8e3ffec2858269055e390ffec f220 T4-platform T5-core makefile-by-name
543 35697012777 1832 9a9534cbc45d40afa4e8bf070030a806acea1f5f f221 T4-platform T5-core makefile-by-name
544 35697183623 2022 7498e2b75b535171b5a560c6493fe86b5ad881cc f222 T4-platform T4-platform
545 35697752061 1724 3889b47527084eccc7c54c8a3c7e2592c5d5d30a f217 T5-core T5-core
546 35698464152 1724 08d5e2062f1ee9aaf8dce8eaf77662cc7c96a1c0 f223 T5-core T5-core
547 35714885737 2280 f930ed6026c6f1e5f5b146a07664efed9d4da9c7 f224 T2-extract T2-extract
548 35730933863 2198 f913f0eaf5ee308619ef1fdd4f60534d4509fab5 f212 T5-core T5-core
549 35734984657 2281 34d172c9038a9b7cee78e42bb7768b93fbb8ed70 f225 T5-core T5-core
550 35734995796 2282 1d2bbc425988eb78fcd966dc8c9cfbdb814f0685 f226 T4-platform T4-platform
551 35766202700 2288 427bb73c638481842bd13d73ed171953469eca02 f227 T3-mid T3-mid
552 35767446435 2288 d7eba5a71d2a99d7b7bc588c666d22516ac0b563 f227 T3-mid T3-mid
553 35772088984 2289 38fc6ea995c9db78cedb10b9bba2b6aa79f616fc f228 T4-platform T4-platform
554 35797101033 2290 255463126650ab6deab3e62aab441b71479704f1 f229 T4-platform T4-platform
555 35797928618 1702 aecfb315d8ddbfab2cacbbd6e4a26b408b973975 f085 T3-mid T3-mid
556 35800424142 1702 26bd387753a9a08fa6de647481bf36cf338e5c28 f085 T3-mid T3-mid
557 35812291988 1702 6233394d8e893546af92876fb8d8da81e592cc2c f085 T3-mid T3-mid
558 35813226724 1702 5770ed83741009e71c12876c40b655758f4f4440 f085 T3-mid T3-mid
559 35822291393 1702 8165c34015699379d31fd31060c0f16fbe5447de f085 T3-mid T3-mid
560 35824077381 1702 24795fc665df85200ddc112aaddfe34572171ef8 f085 T3-mid T3-mid
561 35824588131 1430 6bb8ad6a7a1c99849d101322ca8fe81b6adfbc50 f230 T4-platform T4-platform
562 35826272552 1430 0d2f840c8ed2695db206acbcad1ad1c36a1f3958 f230 T4-platform T4-platform
563 35836874903 1702 f5ec03f0e1a7e90ace14c21490dca39c4c8bb092 f085 T3-mid T3-mid
564 35841856084 1702 492e59a181a27af324c82a8db3c4e2e37382d567 f085 T3-mid T3-mid
565 35841866366 1702 9e92e67649d62a2de63e57893290851d7247476a f085 T3-mid T3-mid
566 35842147817 2158 246e4190c3086d1074adbe8afe39bf4261db59b8 f141 T2-extract T2-extract
567 35846258328 1702 e9339ff1f5b28cb85d2810f434471700e2fc3b5e f085 T3-mid T3-mid
568 35862574659 2294 54c143ac932b6e291a17be0327fb1b5e86b99984 f013 T3-mid T3-mid
569 35899891384 1823 4a8c2eb422d9083ac3bf1e01e18e558e470ad099 f076 T5-core T5-core
570 35918117863 2295 f4c897110cb1f02edee37db7c8849e63894ed8a4 f231 T3-mid T3-mid
571 35936681521 2296 59345314187021f50c5e5c12da793b27b8485d7b f232 T2-extract T2-extract
572 35948281533 1724 48f9de8ccd7cc75d7e466b7d5167e2930dfe33f3 f217 T5-core T5-core
573 35948963693 1724 a8da9a366353bb0a67806b40f840500444ac963f f217 T5-core T5-core
574 35948982011 2272 9b5bf70dc400fdfe989ed2bf1b623b2103bbf2f0 f218 T1-tests T1-tests
575 35952295014 2181 282fbfcdabfec79c19c36f5bec5f75c22eb36df5 f091 T3-mid T3-mid
576 35957866374 2158 cf7eecd7d0b1288c7f2e290a1d1bfc1b1b6f8698 f141 T2-extract T2-extract
577 35960900747 1716 a6d3d5d820b07ba8544ddae1668e8107d7961532 f096 T5-core T5-core
578 35960904447 2199 f07c18702ac82957d0eb73f96f37d30184b7e6b5 f166 T5-core T5-core
579 35960910227 2263 fcdc6bedb7d05489462639da38c4ac755137734f f209 T4-platform T4-platform
580 35960928169 1768 92cafd1ec888362ef3223ad669a5aa4f378a54d5 f055 T1-tests T1-tests
581 36006341620 2301 64297ff3e8e142887fc36c933d57fd4d32924cc2 f233 T4-platform T4-platform
582 36057012376 2304 6f91a16fc2a753db248f122dea7e4b50763972d1 f055 T1-tests T1-tests
583 36057063533 2305 69b84cacff1b08f449e563c2d24861e6f10694e4 f234 T3-mid T3-mid
584 36081432074 2307 2a6dd92f658b15d571130b0eee14a67b95a36465 f235 T2-extract T2-extract
585 36081473175 2308 707f6f0db58a16b706b29d249bced477094ef92c f236 T3-mid T3-mid
586 36081733263 2309 a9a25357ec2dc57f3c6f2e353e7ca61a7575df06 f168 T4-platform T4-platform
587 36081918547 2310 2f922141972a18f0d95de4505b33f5362f51164b f237 T4-platform T4-platform
588 36082054353 2310 8955d98983e0ca1e1df4a2173b4b4273904566c7 f237 T4-platform T4-platform
589 36087900420 2310 7c24b020457924fee9a7acdd6cb8c5f7e262ca4e f237 T4-platform T4-platform
590 36093255940 2310 b2e9199773977f28d73a052d41c37b8c111750a5 f237 T4-platform T4-platform
591 36102327028 2310 d636aa7f21fb001325b61cd41fc431d5f255e21e f237 T4-platform T4-platform
592 36137231318 2315 9527694e90ee9dfd29d64940425f9f23290ed702 f238 T4-platform T4-platform
593 36146313096 2310 9b10ba281cfdba2676e9d24c445470c4b6f7d4e3 f237 T4-platform T4-platform
594 36150749742 2022 7a307abbf8c1c879edee3193dd482ef427c83287 f222 T4-platform T4-platform
595 36160883414 1766 9d53f32dc1753fcece6c0d2af27a5958f8ce1799 f193 T3-mid T3-mid
596 36164363201 2318 6d508350c7713e10e0e935625ad74d8401fe89e2 f239 T3-mid T3-mid
597 36164368040 2319 3faecc78144175a81acf76721c4f695cf80b5676 f240 T3-mid T3-mid
598 36164372390 2320 4f5c951dde792c9cee4e33e45119cef8fc62765e f017 T2-extract T2-extract
599 36164376373 2321 c6ccdd9395dbbafc6f66a49040b478b445387946 f241 T2-extract T2-extract
600 36164382846 2322 19f50de3d2e2bdea5f9054caf07af70f46d74244 f242 T4-platform T4-platform
601 36164406666 2323 e9f6596e82cac5b3377cf212e7199a286815418f f243 T4-platform T4-platform
602 36164410674 2324 a808295986d7599ddbfe8324beefacfdd5161c04 f244 T4-platform T4-platform
603 36164415830 2325 d56e046ddd08ac6bf8b14f5049a88382a10a1636 f245 T3-mid T3-mid
604 36164420947 2326 5ef8ccff7023db106df18cef0615776e7ed94686 f246 T2-extract T5-core makefile-by-name
605 36164427320 2327 098fcc883e2c720ee838df90f3475938715d4056 f247 T5-core T5-core
606 36164450931 2328 a8c4040e8a92dcf831c64f57458694ff8fa6e9f9 f248 T2-extract T2-extract
607 36164460893 2329 12f51855cab461cda3bd4c4d63d79f09fcb69732 f249 T5-core T5-core
608 36164465591 2330 e7324bb6904c6d0a295b956e83c8585219198358 f250 T2-extract T2-extract
609 36164472858 2331 a0c71225f7c0c5e4bee36ef235f966866abffb24 f251 T3-mid T3-mid
610 36164477427 2332 b9ad9ed1449540e06e9f814ded548d9f2bf52f49 f252 T3-mid T3-mid
611 36164497120 2333 e65a4e70be9c87366f66e93e252c0f307aa50ac2 f253 T4-platform T4-platform
612 36164502402 2334 357d71e198ae3cd4614b9e06e54957423291e5c4 f013 T3-mid T3-mid
613 36164505003 2335 1c73f8676c31e227c6ed478ccbb55662b34d6db2 f254 T4-platform T4-platform
614 36164512328 2336 b8b88bcf088b8cf3f6b44949e917d172aaa82d2e f255 T2-extract T2-extract
615 36164516453 2337 8e12fc8a69dc6f83296e660191448ef8ba7819bc f256 T5-core T5-core
616 36164537929 2338 dad40ccdb633c10557baba4049aec35049fa3b15 f257 T4-platform T4-platform
617 36164542709 2339 793e0b6127db3c8333b1dc29faf0b35167e3ceda f258 T3-mid T3-mid
618 36164547188 2340 b3bc2a80aad766d37477a163ae5b158913a20f45 f259 T2-extract T2-extract
619 36164550014 2341 40dad610eec783fe243e6b6141c1bebc75c23f0d f260 T4-platform T5-core makefile-by-name
620 36164556221 2342 c650292433534127b5f45edd066e56a06b3376d1 f261 T3-mid T5-core makefile-by-name
621 36165117354 2181 65fc0bb4f77fdbb6449199c147e1ed112654b151 f091 T3-mid T3-mid
622 36165123674 2158 d772b1e5af0ce80d08aa6603ea47d1f51f324193 f141 T2-extract T2-extract
623 36165128132 1716 ff127ea44c56ce21bd5a4f9cf9f51bb27523d0aa f096 T5-core T5-core
624 36165431134 2343 45f0c430d28cf188753f44536d2cac26564a04ba f262 T3-mid T3-mid
625 36165866718 2344 4b2b3b90adc44694d70624c9d0372752ffd0e2bf f013 T3-mid T3-mid
626 36171533714 2326 c0a06b3d0a90513e0d80f79514b869f91cb958f1 f246 T2-extract T5-core makefile-by-name
627 36171575767 2345 772ea06a6115128c3160f9631322b089aeb85a53 f263 T3-mid T3-mid
628 36176174515 2347 b220ecec48010db71ac0a77b2888fefe802c9182 f264 T3-mid T3-mid
629 36176260818 2348 886e6da479891b6dfe386ef25dfe5abe7ed2f080 f265 T4-platform T4-platform
630 36181164078 2289 8cd2ee5f421d0b7c06022091d59dbe71002de30d f266 T4-platform T4-platform
631 36183268529 2315 c27a53512da9469dc50fb243c1ecd3a55975ebfc f267 T3-mid T3-mid
632 36183549298 2350 99ccc7d64a275bba1c015b30df036597637363f5 f268 T0a-docs T0a-docs
633 36184450486 2351 951b2fddf68a6e6605b1664bd3252a2a6578ef35 f014 T3-mid T3-mid
634 36185562216 2352 8093d10b812d9a7840ae99d8f9d24a04cb0f9559 f269 T3-mid T3-mid
635 36185568093 2353 c64ea20feae160d1eeddadf09e7e0df36b08e818 f055 T1-tests T1-tests
636 36185818550 2354 3ef7d8cdc201115dd76cc373ea2c64a567a2defe f012 T2-extract T2-extract
637 36186982935 2318 b7bcb15c219761f27ff146063a50481303527563 f239 T3-mid T3-mid
638 36186985986 2331 6b7628bb6bbf041a8bdf069dd9d6bc8861d84e73 f251 T3-mid T3-mid
639 36186989409 2332 1d5ff68e508e78b149589396d547166ad41362e0 f270 T3-mid T3-mid
640 36186989799 2334 87471e1f1d6a01ca8be6ca45b968145d0de95362 f013 T3-mid T3-mid
641 36186993787 2342 7abaa91dea653af3e890759a63e40a09cfcb81d8 f261 T3-mid T5-core makefile-by-name
642 36187019606 2327 f5c8631f2032c5598b1bfe12d329b875532f814d f247 T5-core T5-core
643 36188745373 2355 ae578bc1cc32bb8f159a69c486f938ad745c643f f086 T4-platform T4-platform
644 36189334816 2356 d5a3110d9e1e82de5416807d1f81e3ef85049c1a f271 T5-core T5-core
645 36189454232 2357 28b41c35b71cb039988f39f72689dd657e30522d f272 T4-platform T4-platform
646 36189574830 2358 f3754d6197b626d5432ac84b0363b0d3a5544c5c f273 T3-mid T3-mid
647 36197747139 2301 ff8dd47011c43c622d3dc82b33be32b4493e4ea9 f274 T4-platform T4-platform
648 36199747285 2344 bac3fc80b532eb294fd6eda6a2cc93d656df9369 f013 T3-mid T3-mid
649 36199749518 2345 1810a8964caae06e33b7f3c6f944de570c21c292 f263 T3-mid T3-mid
650 36201323199 1702 5557f0ec4a54bcaf99bd7442b866579ab68578f4 f085 T3-mid T3-mid
651 36210099904 2359 efb231285d188ffda16a58f7a5ce8e0e6448638f f101 T1-tests T1-tests
652 36222963285 2315 2d2c0fd0c008973c63565c3e7f4591ed0b1bb379 f275 T4-platform T4-platform
653 36227652952 2160 bc960d87d663b7b4fc4c3ba558013c4609073762 f055 T1-tests T1-tests
654 36232110014 2360 1ccbb432ae1281ac96f743850be317fb541cf1db f276 T3-mid T3-mid
655 36238137680 2361 4dfeb2cefc402c7d1f1d3d01603a2e8409ed24cc f277 T3-mid T3-mid
656 36239758139 2281 9dca8d2fede860605943ee8dbd57ba6b927ae41b f225 T5-core T5-core
657 36239789418 2362 ed991154f212928df63ce5a5cd1159cfc8196f00 f278 T4-platform T4-platform
658 36239835633 2290 26e2d0cba6df7b1cb53e655d0b902bdb7debdbd4 f279 T4-platform T4-platform
659 36241153105 1816 3e67838b05e67915d472ec7698c80649ab8ad03a f071 T4-platform T4-platform
660 36241168954 1766 8f441c19fb8a32b8ccf43ffcf65df7f4dc2bc16a f193 T3-mid T3-mid
661 36242675954 2363 363575af920b05f911fc76ca964dcfa7ef1c62bb f150 T5-core T5-core
662 36242676040 2294 6e3d8c296f2f99375d9693e706b4818c72caad13 f280 T3-mid T3-mid
663 36242724677 2364 e94f5a70d01e4cf62b0c9f496175dcd930bcbd0e f151 T0b-nonproduct T0b-nonproduct
664 36242808005 2365 7549e6d09c021f6d3a385a37898f591626ae62b8 f150 T5-core T5-core
665 36245108554 2366 06f97613a28a18bef0145f37c1036fa60e73533e f281 T3-mid T3-mid
666 36248365534 2367 8f803f7262d1d1e58068f677f3e365048050b728 f012 T2-extract T2-extract
667 36248371079 2368 a59d53a14b779401621c44aa757e3bc9f0588ef0 f282 T5-core T5-core
668 36259321198 2370 79a0c92e33a53758c36d87d4124b12cc83dac5be f236 T3-mid T3-mid
669 36259389383 2371 0dba49208e8537eda2c3355387c0307f6ee95075 f283 T2-extract T2-extract
670 36259523618 2372 6f17caa7a763d9987ebcd7ed33063d47d34bf635 f284 T4-platform T4-platform
671 36262536017 2370 12de590a4935488d83380a33865cbb59a7a16c32 f236 T3-mid T3-mid
672 36266366437 2373 77ab4bea87008547acee60435a8e3df29d90a5cb f285 T2-extract T2-extract
673 36266372862 2374 7c7811f81aabb4c1fdc0914e78d46b71493a9dce f013 T3-mid T3-mid
674 36266444913 2375 3ad76fd7728f3b19ca1100b10830e986b7f631b0 f235 T2-extract T2-extract
675 36266447071 2376 5a9c72d8f8c65b38c66a29a34184b49edf3378ff f104 T4-platform T4-platform
676 36268746769 1702 8ffd03ecd9ccae5a32508801645fb1cac7e4a4d1 f085 T3-mid T3-mid
677 36270666016 1784 ca58149ae4757325158af07e4e3b8968d315e5da f009 T4-platform T4-platform
678 36271539681 1784 71ac6ecc15d3ab3ebcd31f0e467071a506006ca2 f009 T4-platform T4-platform
679 36271900288 2367 075f40e9d4ccbfead4df0e199c7730f94c2154ff f012 T2-extract T2-extract
680 36274019750 2304 262d7ca7659b3f1f6ee619cda2b5f6aad2815314 f055 T1-tests T1-tests
681 36274021183 2305 3d9e5ed5cf1ad1e4bf89b385d0be28c8f011f52f f234 T3-mid T3-mid
682 36284225343 2370 208be7d9374bb27d2875e3568265b591ae7c4b5b f236 T3-mid T3-mid
683 36284230521 2371 eafaab15c802c46ee22233b50f0e9474b0a3085d f283 T2-extract T2-extract
684 36284315887 2280 756d7f42d4aab0045d75e91651cead399e4a7b35 f224 T2-extract T2-extract
685 36285289867 2315 a1ac80740ed800d84a9d5e44d725bdc29d99b88e f275 T4-platform T4-platform
686 36286798412 2072 1a37f0dd52ab62cfbb439c5a2f72afddca4cb009 f286 T5-core T5-core
687 36299419426 2207 631317536426136f580e34d3c25dd5a951d443a8 f171 T4-platform T4-platform
688 36313881874 2381 9d8f51d7f3340381afa450aa33cfd320879d6ae9 f060 T4-platform T4-platform
689 36323957389 1702 b46c6d43f2efe6f0a1de8c77b73085183aa5da1a f085 T3-mid T3-mid
690 36324735265 2376 07eb426c95386e299a6aa6adbf2b4eb9f272ff3b f104 T4-platform T4-platform
691 36339435944 2370 e9058e11db9ed94e401ad711b2016249970a9040 f236 T3-mid T3-mid
692 36339438890 2371 4d21c3bbd34b420c8987e8588b88b5fc5446116f f283 T2-extract T2-extract
693 36346241955 2360 09f704d23f0d990dfc72b916d9daea80acce73ce f287 T3-mid T3-mid
694 36347358048 2361 0d720d52b253cfb64e7bfb1eee034bc3035f1a04 f288 T3-mid T3-mid
695 36348236142 2366 06ef2f64caea016fc792e9069d5a3befc30bca4a f289 T3-mid T3-mid
696 36352906531 2375 7707c9d7918263cd446a4b54d4eb48c3fea91048 f235 T2-extract T2-extract
697 36353006100 2247 fa2292111802b5f3b66db3e4dd168280b53ac17e f196 T3-mid T3-mid
698 36353528883 2249 a45a99c9c73dd54a4625d680da700f7cb0387eff f197 T5-core T5-core
699 36353741468 2385 1b2e8f1fac105b0eaf15e15b95c3ef336bf0a44e f290 T4-platform T4-platform
700 36354332711 2181 82a6ddff81c6c8583c73f3d628037e41a9b412f0 f091 T3-mid T3-mid
701 36354336229 1781 fd2456316e7b5dc27125497f640dc7e41760c104 f174 T4-platform T4-platform
702 36354339289 2065 87ba464596337fc6574a825ef88392e70a733dc9 f093 T3-mid T3-mid
703 36354790786 2252 df8dd1011bd1987d0ca5a45f324775aad2411f54 f200 T2-extract T2-extract
704 36355512051 2089 11957ae6554b7d768095dcda95168331bd654363 f046 T4-platform T4-platform
705 36355610699 2371 ee9d1365e2c17d4e2bbdd84ac10ec6b89cffca5f f283 T2-extract T2-extract
706 36357921046 2344 7f649b29dc778b0f54c28dfcc2f25b76f3d1947a f013 T3-mid T3-mid
707 36357921739 2326 259dbb67bee1d3547ae61a98c9d0c1cf4e491d44 f246 T2-extract T5-core makefile-by-name
708 36357923794 2352 ea6fa49fc2c209de69c1b0f393d9d2cc25b61419 f291 T5-core T5-core
709 36358160313 2389 27c87104a57eefe33543a04e3e43eb0721df8b4a f292 T2-extract T5-core makefile-by-name
710 36358451648 2207 8bd9d37b156641ecf1c82b16b0ec0a8246e8c201 f171 T4-platform T4-platform
711 36358713716 2295 9f5f5ab56fce2baf095b75f1e7a8dc98368dbcad f231 T3-mid T3-mid
712 36359201669 2073 9130fab63d23b5386f12fa4cfa57961b247ff07b f101 T1-tests T1-tests
713 36359210035 2385 c03575ef1ab1abb51793d03303ae3e5d4cd3b708 f290 T4-platform T4-platform
714 36359646097 2391 d450275e20b663e3c5c8a06627030702e61e4184 f017 T2-extract T2-extract
715 36359899206 2385 cb307a496de36c9803dffea0815f3ea96bf849db f290 T4-platform T4-platform
716 36360333971 2389 5ff3e8d6fd3fd87d46807084058e2944f6486d76 f293 T2-extract T5-core makefile-by-name
717 36360464106 2305 8a49fc684ad08ac2d99591e0b364249570c9e676 f234 T3-mid T3-mid
718 36364475838 2392 19d144477b9558dad148d3f555ec6ede16caa3f5 f294 T4-platform T4-platform
719 36364478399 2393 dc735c01fbbb4eddbbb73ff4df6007ab68413a4d f295 T4-platform T4-platform
720 36364479687 2394 26cbccd998819c428589156a82e167db061522ae f296 T5-core T5-core
721 36370343201 2395 f200d7477f192caedaa5b4cda7d9eccc2022a069 f297 T2-extract T2-extract
722 36371911963 2396 629e9c2c1f87e2ac8b5d644d38ff4e935928752a f298 T3-mid T3-mid
723 36371921838 2397 4c7bc08ff02d49afa07808309d5586bc61724ba4 f299 T5-core T5-core
724 36416318776 2315 3327ee5935a7b03ef5f850c12c163c11ef9d91e2 f300 T4-platform T4-platform
725 36440070485 2305 853192aa7e76eedceb1fce6e2a6874948cf1c1a4 f301 T3-mid T3-mid
726 36440993073 2207 343cc949270a3e18e317fd6cd781167ed2fb3dad f171 T4-platform T4-platform
727 36441585217 2368 f277c5e0a9aad35c730e4189b969b74964614d87 f302 T5-core T5-core
728 36451035334 2207 1c840df7495e3b71924b8efd7e3bbf53c9076abc f171 T4-platform T4-platform
729 36453341395 2401 9d5b6e998514b76dcc95b55f362325b3d49945da f264 T3-mid T3-mid
730 36455603700 2401 874286abb9817db94e7789c7938921f6a03f3448 f264 T3-mid T3-mid
731 36456464356 2402 555727b50da6086f00c00f57876e2632caa69c45 f303 T2-extract T2-extract
732 36459403945 2394 78a2ec399fec3dc28e61439a4bcd900b7c64c23f f296 T5-core T5-core
733 36459418705 2344 b205f0b51dacd89da57f6fcbeb46f55617606091 f013 T3-mid T3-mid
734 36459497707 2403 95eaf01755d18c501fa8d1c5dae2255cf460b9a2 f001 T0a-docs T0a-docs
735 36459501942 2404 fe3c285fdc63bd9abe027026edd7adffe5f62677 f013 T3-mid T3-mid
736 36459506386 2405 460153204ba9833913e5245ca894f783c1d7229d f304 T4-platform T4-platform
737 36459537782 2406 a1b7c05685e18597f3461783d4bc98661f05427d f305 T2-extract T2-extract
738 36459545006 2407 c02dff113a90aa6bbc3d89fedda962a53f8b4c64 f306 T2-extract T2-extract
739 36460081689 2280 6ba9a21eed2165932b90f7ede772d77b046eb08a f224 T2-extract T2-extract
740 36460156038 2408 23d48336d3367fb27cbde54049b1414a2aea1b48 f307 T1-tests T1-tests
741 36460162959 2409 4c6ecffdcdb8d78c0704f0d7e825f676ad77c240 f308 T1-tests T1-tests
742 36460990240 1907 b74cecc53cea20abdc64dbdc484f5ba0b44f5789 f033 T3-mid T3-mid
743 36461038395 1913 71ebbb9a9b54f2dcef84321de939429dd7707600 f041 T3-mid T3-mid
744 36466048726 2410 533eee07ece1d01ff2c88da55972c7720cd1604b f309 T4-platform T4-platform
745 36466057374 2411 0d783bcca4edde896d5716e8a4060c3100595973 f076 T5-core T5-core
746 36466065600 2412 ed76cf8e88cfb1f26c82110e46a518c13577f788 f310 T4-platform T4-platform
747 36469132661 2360 5a77358277a83d7651965f762a673be76fb157b3 f311 T3-mid T3-mid
748 36474434156 2402 0a658840b8f029e7632910284281e32286c1f0aa f303 T2-extract T2-extract
749 36474499483 2402 f88aaeb8f9db801545c21e085812559df9f80083 f303 T2-extract T2-extract
750 36474752681 2418 3db66f22ad7d5c435559869a34b6e14b1e54793b f312 T3-mid T3-mid
751 36476594662 2402 33a9bbe85eb922bf01c5297f09cc50044f39e351 f303 T2-extract T2-extract
752 36476662334 2419 3f53a308f4034f4d280130fbcc275f545d7bf05e f313 T2-extract T2-extract
753 36476726058 2420 e36930742a63efd2bbca491996ba8e37bfa5822c f314 T2-extract T2-extract
754 36476760149 2421 fd1b88374c0c21892c991c29909d22ed499c20fa f315 T3-mid T3-mid
755 36476819919 2422 ffa0b320047f4db99dc53f09e228c94703436ae1 f316 T2-extract T2-extract
756 36477490833 2423 ba5e7dd639a3d29c54023d1203ef18fc4e330c7f f013 T3-mid T3-mid
757 36484216122 2391 f297737db0510ecc573edf57a2a30a25e5fbfcfc f317 T3-mid T3-mid
758 36484725527 2391 538ac25edda676ca7d8cb8a4c7c5fb3a3a36fda2 f317 T3-mid T3-mid
759 36488109160 2418 702c3afb6bbab15e18e13f22880d5a61fa3317e1 f312 T3-mid T3-mid
760 36488232420 2419 82361ffdd66ffe39efc1ff9e00e0cdcc61223e4f f313 T2-extract T2-extract
761 36488247857 2420 a46dc5a9fbb39310027812234d0459d4bbcbe99c f314 T2-extract T2-extract
762 36488262530 2421 a85bef05f169e4f2c36cf4cf121a7b6d1309e155 f315 T3-mid T3-mid
763 36488285902 2422 c695cb728e4850b4152cd7cc54f06b6520372371 f316 T2-extract T2-extract
764 36491809136 2309 48265f5367d93c22cdaf2203d19dc65fd6eefea4 f318 T4-platform T4-platform
765 36492368152 2424 904eccabe39b03d8104de989a56f224ccf6d81d3 f319 T3-mid T3-mid
766 36495192814 2425 e548d971b3e471ca5a149201c39ba62ab1a72261 f013 T3-mid T3-mid
767 36499773006 2385 534274c1601034b5eca97ec286c0346389076a8f f290 T4-platform T4-platform
768 36500785757 2360 bb3f1cdd6f4f45ab416ba97ad09a404aebf21e36 f311 T3-mid T3-mid
769 36500790466 2361 8116eaaa426ff39a37b4078e0abd018b09398e58 f320 T3-mid T3-mid
770 36500796009 2366 4a0f9660623b53d4f9eccd7149d54482707fdc22 f321 T3-mid T3-mid
771 36504138642 2371 96f91005f0777e3fee5f5b5f9da69962ddf93695 f283 T2-extract T2-extract
772 36506100535 2427 f4efd0b86844bf2ac965ba9ec30c8805cc774e40 f322 T3-mid T3-mid
773 36508613643 2315 232730cb091d852bd7820bf1d5aeedc43aa860f8 f300 T4-platform T4-platform
774 36509039978 2170 5a3e82520408db86161b8fff08175aa30a0adc6c f323 T3-mid T3-mid
775 36535949326 2368 cc71a2087d32a7c06b0dcc2f925a9a623335c2ef f302 T5-core T5-core
776 36538846045 2170 1ebd1e40fe715ac9efb328b2c7cebd2c0a4e140f f323 T3-mid T3-mid
@@ -0,0 +1,26 @@
# The 22 real (non-flake, non-infra) job failures of the September 2026 PR
# pushes, from the lane analysis ground truth: which lane caught each. The
# replay asserts the selector selects that lane for that push.
# Columns (TAB): run_id, attempt, pr, head_sha, lane, classification.
34263688699 1 2115 013e1f7ec7d4b981a1cd28bf35d5767c195f8447 unix-arm64 real: cli suite hang on slow runners (arm64, then intel)
34288397854 1 2115 92cb3b9a4bebbc79bd283e447104669ca068ea64 unix-arm64 real: cli suite hang on slow runners (arm64, then intel)
34649406520 1 2115 78a6d19f1eaaa73054639ab0f14c372c53cd1bb3 unix-arm64 real: cli suite hang on slow runners (arm64, then intel)
34649406520 2 2115 78a6d19f1eaaa73054639ab0f14c372c53cd1bb3 unix-arm64 real: cli suite hang on slow runners (arm64, then intel)
34676336284 1 2185 eb5e34cfc141454e849daf413f648e73f74c455a codeql-gate real: codeql-action bump broke CodeQL (still caught by requiring CodeQL/analyze)
34676429093 1 2187 cea35fcc0520a0ba95139bd0210052f09b9cfa20 codeql-gate real: codeql-action bump broke CodeQL (still caught by requiring CodeQL/analyze)
34696991657 1 2115 9a460b389ce3002ac164605c3627bb86aa3b9be3 unix-macos-intel real: cli suite hang on slow runners (arm64, then intel)
34805101784 1 2202 7b77fd4834afe355cc4c7326ac3a55f0502f7264 lsan-macos real: macOS-only LSan leaks (memory-core)
34805101784 1 2202 7b77fd4834afe355cc4c7326ac3a55f0502f7264 msan real?: MSan stack overflow (memory-core, branch-specific)
34805101784 1 2202 7b77fd4834afe355cc4c7326ac3a55f0502f7264 tsan-arm real?: backpressure nap-cycle bound (memory-core)
34805101784 1 2202 7b77fd4834afe355cc4c7326ac3a55f0502f7264 tsan-mac real?: backpressure nap-cycle bound (memory-core)
34876200704 1 2202 9724d903046626df0640ced218259cb58070e4e8 lsan-macos real: macOS-only LSan leaks (memory-core)
34876200704 1 2202 9724d903046626df0640ced218259cb58070e4e8 msan real?: MSan stack overflow (memory-core, branch-specific)
34876200704 1 2202 9724d903046626df0640ced218259cb58070e4e8 tsan-arm real?: backpressure nap-cycle bound (memory-core)
35448941856 1 2233 9db5217bdbb779212c5d066ed1a84f7ca6fc0282 tsan-arm real: TSan data race system_info.c:274
35448941856 1 2233 9db5217bdbb779212c5d066ed1a84f7ca6fc0282 tsan-mac real: TSan data race system_info.c:274
35448941856 1 2233 9db5217bdbb779212c5d066ed1a84f7ca6fc0282 tsan-x86 real: TSan data race system_info.c:274
35483768695 1 2236 d2dd3b347bbdcc695f911e80b1b7f7a32d790e5a pkg-wrappers real?: wrapper publication crash-gate (cache-management)
35483768695 1 2236 d2dd3b347bbdcc695f911e80b1b7f7a32d790e5a smoke-mac real: cache CLI broken on macOS/Windows
35483768695 1 2236 d2dd3b347bbdcc695f911e80b1b7f7a32d790e5a smoke-win real: cache CLI broken on macOS/Windows
35493947102 1 2236 2caafc0c41bd083092afeb692824e718a8e1e007 smoke-mac real: cache CLI broken on macOS/Windows
36242675954 1 2363 363575af920b05f911fc76ca964dcfa7ef1c62bb codeql-gate real: codeql-action bump broke CodeQL (still caught by requiring CodeQL/analyze)
1 # The 22 real (non-flake, non-infra) job failures of the September 2026 PR
2 # pushes, from the lane analysis ground truth: which lane caught each. The
3 # replay asserts the selector selects that lane for that push.
4 # Columns (TAB): run_id, attempt, pr, head_sha, lane, classification.
5 34263688699 1 2115 013e1f7ec7d4b981a1cd28bf35d5767c195f8447 unix-arm64 real: cli suite hang on slow runners (arm64, then intel)
6 34288397854 1 2115 92cb3b9a4bebbc79bd283e447104669ca068ea64 unix-arm64 real: cli suite hang on slow runners (arm64, then intel)
7 34649406520 1 2115 78a6d19f1eaaa73054639ab0f14c372c53cd1bb3 unix-arm64 real: cli suite hang on slow runners (arm64, then intel)
8 34649406520 2 2115 78a6d19f1eaaa73054639ab0f14c372c53cd1bb3 unix-arm64 real: cli suite hang on slow runners (arm64, then intel)
9 34676336284 1 2185 eb5e34cfc141454e849daf413f648e73f74c455a codeql-gate real: codeql-action bump broke CodeQL (still caught by requiring CodeQL/analyze)
10 34676429093 1 2187 cea35fcc0520a0ba95139bd0210052f09b9cfa20 codeql-gate real: codeql-action bump broke CodeQL (still caught by requiring CodeQL/analyze)
11 34696991657 1 2115 9a460b389ce3002ac164605c3627bb86aa3b9be3 unix-macos-intel real: cli suite hang on slow runners (arm64, then intel)
12 34805101784 1 2202 7b77fd4834afe355cc4c7326ac3a55f0502f7264 lsan-macos real: macOS-only LSan leaks (memory-core)
13 34805101784 1 2202 7b77fd4834afe355cc4c7326ac3a55f0502f7264 msan real?: MSan stack overflow (memory-core, branch-specific)
14 34805101784 1 2202 7b77fd4834afe355cc4c7326ac3a55f0502f7264 tsan-arm real?: backpressure nap-cycle bound (memory-core)
15 34805101784 1 2202 7b77fd4834afe355cc4c7326ac3a55f0502f7264 tsan-mac real?: backpressure nap-cycle bound (memory-core)
16 34876200704 1 2202 9724d903046626df0640ced218259cb58070e4e8 lsan-macos real: macOS-only LSan leaks (memory-core)
17 34876200704 1 2202 9724d903046626df0640ced218259cb58070e4e8 msan real?: MSan stack overflow (memory-core, branch-specific)
18 34876200704 1 2202 9724d903046626df0640ced218259cb58070e4e8 tsan-arm real?: backpressure nap-cycle bound (memory-core)
19 35448941856 1 2233 9db5217bdbb779212c5d066ed1a84f7ca6fc0282 tsan-arm real: TSan data race system_info.c:274
20 35448941856 1 2233 9db5217bdbb779212c5d066ed1a84f7ca6fc0282 tsan-mac real: TSan data race system_info.c:274
21 35448941856 1 2233 9db5217bdbb779212c5d066ed1a84f7ca6fc0282 tsan-x86 real: TSan data race system_info.c:274
22 35483768695 1 2236 d2dd3b347bbdcc695f911e80b1b7f7a32d790e5a pkg-wrappers real?: wrapper publication crash-gate (cache-management)
23 35483768695 1 2236 d2dd3b347bbdcc695f911e80b1b7f7a32d790e5a smoke-mac real: cache CLI broken on macOS/Windows
24 35483768695 1 2236 d2dd3b347bbdcc695f911e80b1b7f7a32d790e5a smoke-win real: cache CLI broken on macOS/Windows
25 35493947102 1 2236 2caafc0c41bd083092afeb692824e718a8e1e007 smoke-mac real: cache CLI broken on macOS/Windows
26 36242675954 1 2363 363575af920b05f911fc76ca964dcfa7ef1c62bb codeql-gate real: codeql-action bump broke CodeQL (still caught by requiring CodeQL/analyze)
+232
View File
@@ -0,0 +1,232 @@
#!/usr/bin/env python3
"""predict.py — map a detect_changes impact set to the C test suites it can
affect: the PR test-impact SHADOW (scripts/ci/test-impact-shadow.sh).
Report-only; nothing here decides a merge.
The mapping is the one the September 2026 test-impact replay measured
(detect_changes at depth 15 plus rule fallbacks, "dcF@15"):
- impacted test FUNCTIONS (graph rows in tests/*.c) map to the suites that
RUN_TEST them, resolved in their own file first; a nested suite (RUN_SUITE
inside another) resolves to the registered suite that runs it; only suites
registered in tests/test_main.c count, perf suites excluded;
- a changed tests/*.c file always selects its whole suite;
- a changed fixture (tests/fixtures/<key>/...) selects the suites whose test
files name <key>; a changed grammar selects the grammar suites;
- run-all: the lane selector's full, a changed test header or
test-infrastructure file, a saturated or truncated impact set, or a C
change the graph was not asked about -> every suite.
Usage: predict.py --root DIR --changed FILE --selection FILE [--detect FILE]
[--depth N] [--meta JSON] --out FILE [--summary FILE]
predict.py --needs-graph --changed FILE (prints yes|no)
"""
import argparse
import json
import os
import re
import sys
GRAMMAR_SUITES = re.compile(r"^(grammar_|lang_contract|parse_coverage|extraction|matrix_|"
r"edge_types_probe|convergence_probe|ac$|language$)")
C_SOURCE = re.compile(r"\.(c|h|cc|cpp|inc)$")
SUITE_DEF = re.compile(r"^\s*(?:SUITE\((\w+)\)|void\s+suite_(\w+)\s*\(\s*void\s*\))\s*\{", re.M)
RUN_TEST = re.compile(r"\bRUN_TEST\((\w+)\)")
RUN_SUITE = re.compile(r"\bRUN_SUITE\((\w+)\)")
def needs_graph(changed):
"""A C source change the graph can place (prod or test code)."""
return any(C_SOURCE.search(p) and (p.startswith(("src/", "tests/")) or (
p.startswith("internal/cbm/") and not p.startswith("internal/cbm/vendored/")))
for p in changed)
def suitemap(root):
"""file -> suites defined there, test -> suites running it, suite universe,
and the resolver from any suite to the registered suites that run it."""
file_suites, test_suites, sources, parents = {}, {}, {}, {}
for directory, dirs, names in os.walk(os.path.join(root, "tests")):
dirs[:] = sorted(d for d in dirs if not (directory == os.path.join(root, "tests")
and d == "fixtures"))
for name in sorted(names):
if not name.endswith(".c"):
continue
path = os.path.join(directory, name)
rel = os.path.relpath(path, root).replace(os.sep, "/")
with open(path, encoding="utf-8", errors="replace") as fh:
src = fh.read()
sources[rel] = src
for m in SUITE_DEF.finditer(src):
suite = m.group(1) or m.group(2)
start = depth = m.end() - 1
end = start
depth = 0
while end < len(src): # the body is the balanced { } block
if src[end] == "{":
depth += 1
elif src[end] == "}":
depth -= 1
if depth == 0:
break
end += 1
file_suites.setdefault(rel, []).append(suite)
for test in RUN_TEST.findall(src[start:end]):
test_suites.setdefault(test, [])
if suite not in test_suites[test]:
test_suites[test].append(suite)
for child in RUN_SUITE.findall(src[start:end]):
parents.setdefault(child, set()).add(suite)
main = sources.get("tests/test_main.c", "")
registered = set(re.findall(r"RUN_SELECTED_SUITE(?:_PERF)?\((\w+)\)", main))
universe = registered - set(re.findall(r"RUN_SELECTED_SUITE_PERF\((\w+)\)", main))
def runnable(suite, seen=frozenset()):
"""A registered suite runs itself; a nested one runs inside its parents."""
if suite in registered:
return {suite} & universe
out = set()
for parent in parents.get(suite, ()):
if parent not in seen:
out |= runnable(parent, seen | {suite})
return out
return file_suites, test_suites, universe, sources, runnable
def predict(root, changed, selection, detect, depth):
file_suites, test_suites, universe, sources, runnable = suitemap(root)
reasons, run_all = [], False
if selection.get("full"):
run_all = True
reasons.append(f"run-all: the lane selector ran everything ({selection.get('tier')})")
for p in changed:
if p.startswith("tests/") and not p.startswith("tests/fixtures/") and p.endswith(".h"):
run_all = True
reasons.append(f"run-all: test header {p}")
elif p.startswith("test-infrastructure/"):
run_all = True
reasons.append(f"run-all: test infrastructure {p}")
graph = {"consulted": detect is not None}
picked = {} # suite -> "ALL" | set(tests)
if detect is not None:
rows = detect.get("impacted", [])
total = detect.get("impacted_total", len(rows))
shown = detect.get("impacted_shown", len(rows))
graph.update(impacted_total=total, impacted_shown=shown,
saturated=bool(detect.get("engine_saturated")))
if graph["saturated"]:
run_all = True
reasons.append("run-all: detect_changes saturated")
if total > len(rows) or shown > len(rows):
run_all = True
reasons.append(f"run-all: impact set truncated ({len(rows)} of {total} rows)")
tests = 0
for r in rows:
fp, name = r.get("file", ""), r.get("qn", "").rsplit(".", 1)[-1]
if not fp.startswith("tests/") or r.get("label") != "Function" or r.get("hop", 0) > depth:
continue
local = file_suites.get(fp, [])
if name in local:
continue # the SUITE function itself
candidates = test_suites.get(name, [])
for suite in [s for s in candidates if s in local] or candidates:
for runner in runnable(suite):
picked.setdefault(runner, set()).add(name)
tests += 1
graph["test_rows"] = tests
elif needs_graph(changed):
run_all = True
reasons.append("run-all: C change but no impact set (graph not consulted)")
for p in changed:
if p.startswith("tests/fixtures/"): # fixture sources are data, not tests
key = p.split("/")[2] if p.count("/") >= 2 else p
for f, src in sources.items():
if key in src:
for suite in file_suites.get(f, []):
for runner in runnable(suite):
picked[runner] = "ALL"
reasons.append(f"changed fixture {p}: suites whose tests name '{key}'")
elif p.startswith("tests/") and p.endswith(".c"):
for suite in file_suites.get(p, []):
for runner in runnable(suite):
picked[runner] = "ALL"
reasons.append(f"changed test file {p}: suite {runner}")
elif p.startswith("internal/cbm/vendored/grammars/"):
for suite in universe:
if GRAMMAR_SUITES.match(suite):
picked[suite] = "ALL"
reasons.append(f"changed grammar {p}: grammar suites")
if run_all:
mode, suites = "all", {s: "ALL" for s in sorted(universe)}
else:
suites = {s: v if v == "ALL" else sorted(v) for s, v in sorted(picked.items())}
mode = "graph" if (graph["consulted"] or suites) else "none"
return {"version": 1, "mode": mode, "depth": depth,
"selector": {"tier": selection.get("tier"), "full": bool(selection.get("full"))},
"universe": len(universe), "selected_suites": len(suites), "suites": suites,
"graph": graph, "reasons": sorted(set(reasons))}
def summary(result, meta):
g = result["graph"]
whole = sum(1 for v in result["suites"].values() if v == "ALL")
tests = sum(len(v) for v in result["suites"].values() if v != "ALL")
lines = ["### Test-impact shadow (report-only, never gates)", "",
"| | |", "|---|---|",
f"| selector | {result['selector']['tier']} (full: {'yes' if result['selector']['full'] else 'no'}) |",
f"| prediction | **{result['mode']}**: {result['selected_suites']} of {result['universe']} "
f"suites ({whole} whole, {tests} tests in the rest) |"]
if g.get("consulted"):
lines.append(f"| graph | {g.get('impacted_shown')} of {g.get('impacted_total')} impacted rows at "
f"depth {result['depth']}, {g.get('test_rows')} test rows"
f"{', saturated' if g.get('saturated') else ''} |")
for key in sorted(meta):
lines.append(f"| {key} | {meta[key]} |")
if result["reasons"]:
lines += ["", "Reasons: " + "; ".join(result["reasons"][:12])]
if result["mode"] == "graph" and result["suites"]:
lines += ["", "Suites: " + ", ".join(result["suites"])[:1500]]
return "\n".join(lines) + "\n"
def main():
ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
ap.add_argument("--needs-graph", action="store_true")
ap.add_argument("--root")
ap.add_argument("--changed", required=True)
ap.add_argument("--selection")
ap.add_argument("--detect")
ap.add_argument("--depth", type=int, default=15)
ap.add_argument("--meta", default="{}")
ap.add_argument("--out")
ap.add_argument("--summary")
a = ap.parse_args()
with open(a.changed, encoding="utf-8") as fh:
changed = sorted({line.strip() for line in fh if line.strip()})
if a.needs_graph:
print("yes" if needs_graph(changed) else "no")
return 0
if not (a.root and a.selection and a.out):
ap.error("--root, --selection and --out are required")
with open(a.selection, encoding="utf-8") as fh:
selection = json.load(fh)
detect = None
if a.detect:
with open(a.detect, encoding="utf-8") as fh:
detect = json.load(fh)
result = predict(a.root, changed, selection, detect, a.depth)
meta = json.loads(a.meta)
result["meta"] = meta
with open(a.out, "w", encoding="utf-8") as fh:
json.dump(result, fh, indent=1, sort_keys=True)
fh.write("\n")
if a.summary:
with open(a.summary, "w", encoding="utf-8") as fh:
fh.write(summary(result, meta))
return 0
if __name__ == "__main__":
sys.exit(main())
+730
View File
@@ -0,0 +1,730 @@
"""Pure bounded single-image profile-map v2 encoding and row reduction.
Evidence fields are caller claims, not authentication or closure certificates.
Names/hashes/shapes retain exact profile identity; source names are not inferred.
Limits count logical data, not interpreter RSS. Runtime allocation/deallocation
is not preemptible. This module performs no I/O and leaves format 1 unchanged.
"""
from dataclasses import dataclass
from enum import Enum
import hashlib
import sys
from typing import Callable, Optional, Tuple
from profile_text_parser import (
LimitExceededError, MalformedProfileError, ParseCancelledError,
ParseLimits, parse_profile_text,
)
class SuiteOutcome(Enum):
PASSED = "passed"
FAILED = "failed"
TIMED_OUT = "timed_out"
UNKNOWN = "unknown"
class ObservationRole(Enum):
PARENT = "parent"
SETUP = "setup"
CHILD = "child"
@dataclass(frozen=True)
class ExpectedSuite:
__slots__ = ("suite", "tests", "outcome")
suite: bytes
tests: Tuple[bytes, ...]
outcome: SuiteOutcome
@dataclass(frozen=True)
class ProfileObservation:
__slots__ = ("suite", "test", "key", "role", "text")
suite: bytes
test: bytes
key: bytes
role: ObservationRole
text: Optional[bytes] # None means an explicitly unavailable observation.
@dataclass(frozen=True)
class RowEvidence:
__slots__ = (
"suite", "test", "interval_complete", "runtime_compatible",
"same_image", "children_complete",
)
suite: bytes
test: bytes
interval_complete: bool
runtime_compatible: bool
same_image: bool
children_complete: bool
@dataclass(frozen=True)
class ProfileMapLimits:
__slots__ = (
"max_input_bytes", "max_rows", "max_observations", "max_ids",
"max_records", "max_counter_values", "max_name_bytes",
"max_id_references", "max_output_bytes",
)
max_input_bytes: int
max_rows: int
max_observations: int
max_ids: int
max_records: int
max_counter_values: int
max_name_bytes: int
max_id_references: int
max_output_bytes: int
@dataclass(frozen=True)
class ProducedMap:
__slots__ = (
"profiles_tsv", "tests_tsv", "profiles_sha256", "tests_sha256",
)
profiles_tsv: bytes
tests_tsv: bytes
profiles_sha256: bytes
tests_sha256: bytes
class ProfileMapError(ValueError):
"""Base for classified build failures; no ProducedMap is returned."""
class ProfileMapInvalidArgumentError(ProfileMapError):
"""Invalid exact type, field, routing, membership, duplicate or callback result."""
class ProfileMapEnumerationError(ProfileMapError):
"""Enumeration text is malformed or unsupported, including empty text."""
class ProfileMapCompatibilityError(ProfileMapError):
"""An observation contains an unknown full identity or mismatching shape."""
class ProfileMapLimitExceededError(ProfileMapError):
"""A declared logical or fixed representation bound would be exceeded."""
class ProfileMapCancelledError(ProfileMapError):
"""The caller's pure prompt predicate requested cancellation."""
_INT_MAX = 2147483647
_SHA_BYTES_MAX = ((1 << 64) - 1) // 8
_POLL_INTERVAL = 4096
_HEX = b"0123456789abcdef"
_REASONS = (
b"suite_failed", b"suite_timeout", b"suite_unknown",
b"row_evidence_missing", b"interval_unproved", b"runtime_unproved",
b"image_unproved", b"children_unproved", b"missing_parent",
b"missing_setup", b"observation_unavailable", b"observation_malformed",
b"partial_stream",
)
_SUITE_FAILED, _SUITE_TIMEOUT, _SUITE_UNKNOWN = 0, 1, 2
_NO_EVIDENCE, _INTERVAL, _RUNTIME, _IMAGE, _CHILDREN = 3, 4, 5, 6, 7
_NO_PARENT, _NO_SETUP, _UNAVAILABLE, _MALFORMED, _PARTIAL = 8, 9, 10, 11, 12
class _Gate:
__slots__ = ("cancel", "context", "work", "callback_error")
def __init__(self, cancel, context):
self.cancel = cancel
self.context = context
self.work = 0
self.callback_error = None
def poll(self):
self.work = 0
if self.cancel is None:
return
try:
answer = self.cancel(self.context)
except BaseException as error:
# Identity distinguishes a callback's parser-shaped exception from
# a parser failure; neither its class nor message is sufficient.
self.callback_error = error
raise
if type(answer) is not bool:
raise ProfileMapInvalidArgumentError("cancel must return bool")
if answer:
raise ProfileMapCancelledError("profile map build cancelled")
def step(self):
self.work += 1
if self.work == _POLL_INTERVAL:
self.poll()
def parser_cancel(self, _context):
self.poll()
return False
def _bounded_add(current, amount, cap, label):
if amount > cap - current:
raise ProfileMapLimitExceededError(label)
return current + amount
class _Budget:
__slots__ = (
"limits", "input_bytes", "rows", "records", "counter_values",
"name_bytes", "id_references", "output_bytes",
)
def __init__(self, limits, enumeration_text):
self.limits = limits
self.input_bytes = _bounded_add(
32, len(enumeration_text), limits.max_input_bytes, "input byte limit"
)
self.rows = 0
self.records = 0
self.counter_values = 0
self.name_bytes = 0
self.id_references = 0
self.output_bytes = 0
def name(self, value):
self.input_bytes = _bounded_add(
self.input_bytes, len(value), self.limits.max_input_bytes,
"input byte limit",
)
self.name_bytes = _bounded_add(
self.name_bytes, len(value), self.limits.max_name_bytes,
"name byte limit",
)
def text(self, value):
self.input_bytes = _bounded_add(
self.input_bytes, len(value), self.limits.max_input_bytes,
"input byte limit",
)
def add_rows(self, count):
self.rows = _bounded_add(
self.rows, count, min(self.limits.max_rows, _INT_MAX), "row limit"
)
def parsed(self, records, gate):
self.records = _bounded_add(
self.records, len(records), self.limits.max_records, "record limit"
)
for record in records:
gate.step()
self.counter_values = _bounded_add(
self.counter_values, len(record.counters),
self.limits.max_counter_values, "counter value limit",
)
self.name_bytes = _bounded_add(
self.name_bytes, len(record.raw_name),
self.limits.max_name_bytes, "name byte limit",
)
def reference(self):
self.id_references = _bounded_add(
self.id_references, 1, self.limits.max_id_references,
"ID reference limit",
)
def output(self, count):
self.output_bytes = _bounded_add(
self.output_bytes, count,
min(self.limits.max_output_bytes, _SHA_BYTES_MAX), "output byte limit",
)
class _Row:
__slots__ = (
"suite", "test", "outcome", "evidence", "observations", "ids",
"reasons", "parent_seen", "primary_parsed",
)
def __init__(self, suite, test, outcome):
self.suite = suite
self.test = test
self.outcome = outcome
self.evidence = None
self.observations = []
self.ids = set()
self.reasons = 0
self.parent_seen = False
self.primary_parsed = False
def mark(self, reason):
self.reasons |= 1 << reason
def _exact(value, cls, label):
if type(value) is not cls:
raise ProfileMapInvalidArgumentError(label)
def _validate_top(image, enumeration, suites, observations, evidence, limits, cancel):
_exact(image, bytes, "image digest must be bytes")
if len(image) != 32:
raise ProfileMapInvalidArgumentError("image digest must contain 32 bytes")
_exact(enumeration, bytes, "enumeration must be bytes")
_exact(suites, tuple, "expected suites must be a tuple")
_exact(observations, tuple, "observations must be a tuple")
_exact(evidence, tuple, "row evidence must be a tuple")
_exact(limits, ProfileMapLimits, "invalid limits type")
for field in ProfileMapLimits.__slots__:
value = getattr(limits, field)
if type(value) is not int or value <= 0 or value > sys.maxsize:
raise ProfileMapInvalidArgumentError("limits must be positive bounded ints")
if limits.max_ids > _INT_MAX:
raise ProfileMapInvalidArgumentError("max_ids exceeds INT_MAX")
if cancel is not None and not callable(cancel):
raise ProfileMapInvalidArgumentError("cancel must be callable or None")
def _name(value, budget, gate, *, suite=False, declared_test=False):
_exact(value, bytes, "names and keys must be bytes")
if not value:
raise ProfileMapInvalidArgumentError("empty name or key")
budget.name(value)
for char in value:
gate.step()
if char in (0, 9, 10, 13) or (suite and char == 58):
raise ProfileMapInvalidArgumentError("unrepresentable name or key")
if declared_test and _is_setup(value):
raise ProfileMapInvalidArgumentError("setup name is reserved")
def _is_setup(test):
return len(test) == 1 and test[0] == 42
def _compare_bytes(left, right, gate):
for index in range(min(len(left), len(right))):
gate.step()
a, b = left[index], right[index]
if a != b:
return -1 if a < b else 1
gate.step()
return (len(left) > len(right)) - (len(left) < len(right))
def _compare_fields(left, right, fields, gate):
for field in fields:
gate.step()
order = _compare_bytes(getattr(left, field), getattr(right, field), gate)
if order:
return order
return 0
def _merge(source, target, start, middle, end, compare, gate):
left, right = start, middle
for destination in range(start, end):
gate.step()
take_left = right == end or (
left < middle and compare(source[left], source[right]) <= 0
)
if take_left:
target[destination] = source[left]
left += 1
else:
target[destination] = source[right]
right += 1
def _sort(values, compare, gate):
source, target = [], []
for value in values:
gate.step()
source.append(value)
target.append(None)
width = 1
count = len(source)
while width < count:
gate.step()
for start in range(0, count, width * 2):
gate.step()
middle, end = min(start + width, count), min(start + width * 2, count)
_merge(source, target, start, middle, end, compare, gate)
source, target = target, source
width *= 2
return source
def _sort_unique(values, fields, gate, label):
compare = lambda left, right: _compare_fields(left, right, fields, gate)
result = _sort(values, compare, gate)
for index in range(1, len(result)):
gate.step()
if compare(result[index - 1], result[index]) == 0:
raise ProfileMapInvalidArgumentError(label)
return result
def _suites(suites, budget, gate):
rows = []
if len(suites) > min(budget.limits.max_rows, _INT_MAX):
raise ProfileMapLimitExceededError("row limit")
for suite in suites:
gate.step()
_exact(suite, ExpectedSuite, "invalid expected suite type")
_name(suite.suite, budget, gate, suite=True)
_exact(suite.tests, tuple, "expected tests must be a tuple")
_exact(suite.outcome, SuiteOutcome, "invalid suite outcome")
budget.add_rows(1 + len(suite.tests))
rows.append(_Row(suite.suite, b"*", suite.outcome))
for test in suite.tests:
gate.step()
_name(test, budget, gate, declared_test=True)
rows.append(_Row(suite.suite, test, suite.outcome))
_sort_unique(suites, ("suite",), gate, "duplicate expected suite")
return _sort_unique(rows, ("suite", "test"), gate, "duplicate expected test")
def _compare_target(row, suite, test, gate):
order = _compare_bytes(row.suite, suite, gate)
return order if order else _compare_bytes(row.test, test, gate)
def _find_row(rows, suite, test, gate):
low, high = 0, len(rows)
while low < high:
gate.step()
middle = (low + high) // 2
order = _compare_target(rows[middle], suite, test, gate)
if order == 0:
return rows[middle]
if order < 0:
low = middle + 1
else:
high = middle
raise ProfileMapInvalidArgumentError("observation/evidence outside expected rows")
def _observation_schema(observation, budget, gate):
_exact(observation, ProfileObservation, "invalid observation type")
_name(observation.suite, budget, gate, suite=True)
_name(observation.test, budget, gate)
_name(observation.key, budget, gate)
_exact(observation.role, ObservationRole, "invalid observation role")
if observation.text is not None:
_exact(observation.text, bytes, "observation text must be bytes or None")
budget.text(observation.text)
def _route_observation(observation, row):
setup = _is_setup(row.test)
if ((setup and observation.role is ObservationRole.PARENT)
or (not setup and observation.role is ObservationRole.SETUP)):
raise ProfileMapInvalidArgumentError("invalid role for row")
if observation.role is ObservationRole.PARENT:
if row.parent_seen:
raise ProfileMapInvalidArgumentError("multiple parent observations")
row.parent_seen = True
row.observations.append(observation)
def _observations(observations, rows, budget, gate):
if len(observations) > budget.limits.max_observations:
raise ProfileMapLimitExceededError("observation limit")
for observation in observations:
gate.step()
_observation_schema(observation, budget, gate)
ordered = _sort_unique(
observations, ("suite", "test", "key"), gate, "duplicate observation key"
)
for observation in ordered:
gate.step()
row = _find_row(rows, observation.suite, observation.test, gate)
_route_observation(observation, row)
def _evidence(evidence, rows, budget, gate):
if len(evidence) > len(rows):
raise ProfileMapInvalidArgumentError("too many row evidence records")
for item in evidence:
gate.step()
_exact(item, RowEvidence, "invalid row evidence type")
_name(item.suite, budget, gate, suite=True)
_name(item.test, budget, gate)
for field in ("interval_complete", "runtime_compatible", "same_image", "children_complete"):
gate.step()
_exact(getattr(item, field), bool, "evidence claims must be bool")
ordered = _sort_unique(evidence, ("suite", "test"), gate, "duplicate row evidence")
for item in ordered:
gate.step()
_find_row(rows, item.suite, item.test, gate).evidence = item
def _preflight(suites, observations, evidence, budget, gate):
rows = _suites(suites, budget, gate)
_observations(observations, rows, budget, gate)
_evidence(evidence, rows, budget, gate)
gate.poll()
return rows
def _parser_limits(budget, enumeration):
limits = budget.limits
records = limits.max_records - budget.records
counters = limits.max_counter_values - budget.counter_values
names = limits.max_name_bytes - budget.name_bytes
if enumeration:
records = min(records, limits.max_ids)
if records <= 0 or counters <= 0 or names <= 0:
raise ProfileMapLimitExceededError("no remaining parser item allowance")
return ParseLimits(limits.max_input_bytes, records, counters, names)
def _parse(text, budget, gate, *, enumeration=False):
limits = _parser_limits(budget, enumeration)
try:
records = parse_profile_text(text, limits, cancel=gate.parser_cancel)
except MalformedProfileError as error:
if error is gate.callback_error:
raise
if enumeration:
raise ProfileMapEnumerationError("invalid profile enumeration") from error
return None
except LimitExceededError as error:
if error is gate.callback_error:
raise
raise ProfileMapLimitExceededError("profile parser resource limit") from error
except ParseCancelledError as error:
if error is gate.callback_error:
raise
raise ProfileMapCancelledError("profile parsing cancelled") from error
budget.parsed(records, gate)
return records
def _record_order(left, right, gate):
order = _compare_bytes(left.raw_name, right.raw_name, gate)
if order:
return order
gate.step()
return (left.function_hash > right.function_hash) - (left.function_hash < right.function_hash)
def _has_hit(record, gate):
for value in record.counters:
gate.step()
if value:
return True
return False
def _join(records, universe, row, budget, gate):
position = 0
for record in records:
gate.step()
while position < len(universe):
gate.step()
order = _record_order(universe[position], record, gate)
if order >= 0:
break
position += 1
if position == len(universe) or order != 0:
raise ProfileMapCompatibilityError("observation identity outside universe")
if len(record.counters) != len(universe[position].counters):
raise ProfileMapCompatibilityError("observation counter shape mismatch")
if _has_hit(record, gate) and position not in row.ids:
budget.reference()
row.ids.add(position)
position += 1
if len(records) != len(universe):
row.mark(_PARTIAL)
def _base_reasons(row, gate):
outcomes = (
(SuiteOutcome.FAILED, _SUITE_FAILED),
(SuiteOutcome.TIMED_OUT, _SUITE_TIMEOUT),
(SuiteOutcome.UNKNOWN, _SUITE_UNKNOWN),
)
for outcome, reason in outcomes:
gate.step()
if row.outcome is outcome:
row.mark(reason)
if row.evidence is None:
row.mark(_NO_EVIDENCE)
return
for field, reason in (
("interval_complete", _INTERVAL), ("runtime_compatible", _RUNTIME),
("same_image", _IMAGE), ("children_complete", _CHILDREN),
):
gate.step()
if not getattr(row.evidence, field):
row.mark(reason)
def _reduce_row(row, universe, budget, gate):
_base_reasons(row, gate)
for observation in row.observations:
gate.step()
if observation.text is None:
row.mark(_UNAVAILABLE)
continue
records = _parse(observation.text, budget, gate)
if records is None:
row.mark(_MALFORMED)
continue
_join(records, universe, row, budget, gate)
if observation.role in (ObservationRole.PARENT, ObservationRole.SETUP):
row.primary_parsed = True
if not row.primary_parsed:
row.mark(_NO_SETUP if _is_setup(row.test) else _NO_PARENT)
class _Wire:
__slots__ = ("budget", "gate", "data")
def __init__(self, budget, gate):
self.budget = budget
self.gate = gate
self.data = bytearray()
def emit(self, value):
self.budget.output(len(value))
for char in value:
self.gate.step()
self.data.append(char)
def hex(self, value):
self.budget.output(len(value) * 2)
for char in value:
self.gate.step()
self.gate.step()
self.data.append(_HEX[char >> 4])
self.gate.step()
self.data.append(_HEX[char & 15])
def decimal(self, value):
# All emitted integers are <= uint64, so conversion is <=20 digits.
self.gate.poll()
self.emit(str(value).encode("ascii"))
def _values(self):
for char in self.data:
self.gate.step()
yield char
def finish(self):
self.gate.poll()
result = bytes(self._values())
self.gate.poll()
return result
def _profile_wire(image, universe, budget, gate):
out = _Wire(budget, gate)
out.emit(b"CBM_PROFILE_MAP\t2\t")
out.hex(image)
out.emit(b"\t")
out.decimal(len(universe))
out.emit(b"\n")
for ident, record in enumerate(universe):
gate.step()
out.decimal(ident)
out.emit(b"\t")
out.hex(record.raw_name)
out.emit(b"\t")
gate.poll()
out.emit(format(record.function_hash, "016x").encode("ascii"))
out.emit(b"\t")
out.decimal(len(record.counters))
out.emit(b"\n")
return out.finish()
def _reason_wire(row, out, gate):
first = True
for index, reason in enumerate(_REASONS):
gate.step()
if row.reasons & (1 << index):
if not first:
out.emit(b";")
out.emit(reason)
first = False
def _id_wire(row, out, gate):
def compare(left, right):
gate.step()
return (left > right) - (left < right)
ordered = _sort(row.ids, compare, gate)
for index, ident in enumerate(ordered):
gate.step()
if index:
out.emit(b" ")
out.decimal(ident)
def _test_wire(rows, budget, gate):
out = _Wire(budget, gate)
for row in rows:
gate.step()
out.emit(row.suite)
out.emit(b":")
out.emit(row.test)
out.emit(b"\tincomplete\t" if row.reasons else b"\tcomplete\t")
_reason_wire(row, out, gate)
out.emit(b"\t")
_id_wire(row, out, gate)
out.emit(b"\n")
return out.finish()
def _digest(data, gate):
digest = hashlib.sha256()
for start in range(0, len(data), _POLL_INTERVAL):
gate.poll()
chunk = data[start:start + _POLL_INTERVAL]
gate.poll()
digest.update(chunk)
gate.poll()
return digest.digest()
def build_profile_map_v2(
image_sha256: bytes,
enumeration_text: bytes,
expected_suites: Tuple[ExpectedSuite, ...],
observations: Tuple[ProfileObservation, ...],
row_evidence: Tuple[RowEvidence, ...],
limits: ProfileMapLimits,
*,
cancel: Optional[Callable[[object], bool]] = None,
cancel_context: object = None,
) -> ProducedMap:
"""Produce exact v2 wires or raise, without I/O or a partial result.
Inputs are immutable exact types. Evidence is supplied by the caller;
parsing cannot establish image/process closure, provenance or admission.
"""
_validate_top(image_sha256, enumeration_text, expected_suites, observations,
row_evidence, limits, cancel)
gate = _Gate(cancel, cancel_context)
gate.poll()
budget = _Budget(limits, enumeration_text)
rows = _preflight(expected_suites, observations, row_evidence, budget, gate)
universe = _parse(enumeration_text, budget, gate, enumeration=True)
for row in rows:
gate.step()
_reduce_row(row, universe, budget, gate)
profiles = _profile_wire(image_sha256, universe, budget, gate)
tests = _test_wire(rows, budget, gate)
profiles_digest = _digest(profiles, gate)
tests_digest = _digest(tests, gate)
gate.poll()
return ProducedMap(profiles, tests, profiles_digest, tests_digest)
+295
View File
@@ -0,0 +1,295 @@
"""Strict data-only parser for the observed LLVM 22.1.8 frontend proftext.
parse_profile_text(bytes, ParseLimits, *, cancel=None, cancel_context=None)
returns an immutable tuple of ProfileRecord(raw_name, function_hash, counters),
ordered by unsigned raw-name bytes and then uint64 hash. Zero values survive.
Each record is a nonempty name line, '# Func Hash:', uint64, '# Num Counters:',
positive uint64, '# Counter Values:', exactly that many uint64 lines, and one
mandatory blank line. All delimiters are LF. Decimals are canonical. Empty
documents, CR/NUL, extra sections and duplicate name/hash tuples fail closed.
Names remain opaque bytes: no normalization, decoding or comment processing.
Four exact positive integer limits bound input bytes, records, total counter
values and total name bytes. Errors derive from ProfileTextError; MemoryError
and callback exceptions propagate without a partial result. A pure, prompt
cancel(context) predicate must return bool. It is called at entry/final publish
and within explicit loops at intervals of at most 4096 byte/element operations.
Python allocation/resizing/deallocation and callbacks are not preemptible.
Limits bound logical input and result items, not exact interpreter heap use.
This module performs no I/O and makes no image, authentication or closure claim.
"""
from __future__ import annotations
from dataclasses import dataclass
import sys
from typing import Callable
_UINT64_MAX = (1 << 64) - 1
_POLL_INTERVAL = 4096
@dataclass(frozen=True)
class ProfileRecord:
__slots__ = ("raw_name", "function_hash", "counters")
raw_name: bytes
function_hash: int
counters: tuple[int, ...]
@dataclass(frozen=True)
class ParseLimits:
__slots__ = ("max_input_bytes", "max_records", "max_counter_values", "max_name_bytes")
max_input_bytes: int
max_records: int
max_counter_values: int
max_name_bytes: int
class ProfileTextError(ValueError):
"""Base for parser failures; no partial result is published."""
class InvalidArgumentError(ProfileTextError):
"""Invalid public argument, limit, or callback return type."""
class MalformedProfileError(ProfileTextError):
"""Unsupported or malformed text, including duplicate identities."""
class LimitExceededError(ProfileTextError):
"""A configured logical resource bound would be exceeded."""
class ParseCancelledError(ProfileTextError):
"""The caller's predicate requested cancellation."""
class _Gate:
__slots__ = ("cancel", "context", "work")
def __init__(self, cancel, context):
self.cancel = cancel
self.context = context
self.work = 0
def poll(self):
self.work = 0
if self.cancel is None:
return
answer = self.cancel(self.context)
if type(answer) is not bool:
raise InvalidArgumentError("cancel must return bool")
if answer:
raise ParseCancelledError("profile parsing cancelled")
def step(self):
self.work += 1
if self.work == _POLL_INTERVAL:
self.poll()
class _Parser:
__slots__ = ("data", "limits", "gate", "position", "names", "values")
def __init__(self, data, limits, gate):
self.data = data
self.limits = limits
self.gate = gate
self.position = 0
self.names = 0
self.values = 0
def line(self):
start = self.position
while self.position < len(self.data):
self.gate.step()
value = self.data[self.position]
self.position += 1
if value == 0 or value == 13:
raise MalformedProfileError("NUL and CR are unsupported")
if value == 10:
return start, self.position - 1
raise MalformedProfileError("unterminated or missing line")
def marker(self, expected):
start, end = self.line()
if end - start != len(expected):
raise MalformedProfileError("unexpected record marker")
for index, value in enumerate(expected):
self.gate.step()
if self.data[start + index] != value:
raise MalformedProfileError("unexpected record marker")
def uint64(self):
start, end = self.line()
length = end - start
if length == 0 or length > 20:
raise MalformedProfileError("invalid unsigned decimal")
if length > 1 and self.data[start] == 48:
raise MalformedProfileError("noncanonical unsigned decimal")
value = 0
for index in range(start, end):
self.gate.step()
digit = self.data[index] - 48
if digit < 0 or digit > 9:
raise MalformedProfileError("invalid unsigned decimal")
if value > (_UINT64_MAX - digit) // 10:
raise MalformedProfileError("unsigned decimal overflow")
value = value * 10 + digit
return value
def name_values(self, start, end):
for index in range(start, end):
self.gate.step()
yield self.data[index]
def counter_values(self, count):
for _ in range(count):
self.gate.step()
yield self.uint64()
def record(self):
start, end = self.line()
length = end - start
if length == 0:
raise MalformedProfileError("empty record name")
if length > self.limits.max_name_bytes - self.names:
raise LimitExceededError("total name bytes limit exceeded")
self.names += length
self.marker(b"# Func Hash:")
function_hash = self.uint64()
self.marker(b"# Num Counters:")
count = self.uint64()
if count == 0:
raise MalformedProfileError("counter count must be positive")
if count > self.limits.max_counter_values - self.values:
raise LimitExceededError("total counter values limit exceeded")
self.values += count
self.marker(b"# Counter Values:")
# Each remaining value needs at least '0\n', then the final blank line.
remaining = len(self.data) - self.position
if remaining < 1 or count > (remaining - 1) // 2:
raise MalformedProfileError("truncated counter values")
self.gate.poll()
counters = tuple(self.counter_values(count))
self.gate.poll()
blank_start, blank_end = self.line()
if blank_start != blank_end:
raise MalformedProfileError("missing record blank line")
self.gate.poll()
name = bytes(self.name_values(start, end))
self.gate.poll()
return ProfileRecord(name, function_hash, counters)
def records(self):
self.gate.poll()
records = []
while self.position < len(self.data):
self.gate.step()
if len(records) == self.limits.max_records:
raise LimitExceededError("record count limit exceeded")
record = self.record()
self.gate.poll()
records.append(record)
if not records:
raise MalformedProfileError("empty document")
return records
def _compare(left, right, gate):
common = min(len(left.raw_name), len(right.raw_name))
for index in range(common):
gate.step()
a = left.raw_name[index]
b = right.raw_name[index]
if a != b:
return -1 if a < b else 1
gate.step()
if len(left.raw_name) != len(right.raw_name):
return -1 if len(left.raw_name) < len(right.raw_name) else 1
if left.function_hash == right.function_hash:
return 0
return -1 if left.function_hash < right.function_hash else 1
def _merge(source, target, start, middle, end, gate):
left, right = start, middle
for destination in range(start, end):
gate.step()
take_left = right == end or (
left < middle and _compare(source[left], source[right], gate) <= 0
)
if take_left:
target[destination] = source[left]
left += 1
else:
target[destination] = source[right]
right += 1
def _sorted_unique(records, gate):
gate.poll()
scratch = []
for _ in records:
gate.step()
scratch.append(None)
gate.poll()
width = 1
length = len(records)
while width < length:
for start in range(0, length, width * 2):
middle = min(start + width, length)
end = min(start + width * 2, length)
_merge(records, scratch, start, middle, end, gate)
records, scratch = scratch, records
width *= 2
for index in range(1, length):
gate.step()
if _compare(records[index - 1], records[index], gate) == 0:
raise MalformedProfileError("duplicate name/hash identity")
return records
def _record_values(records, gate):
for record in records:
gate.step()
yield record
def _validate_arguments(data, limits, cancel):
if type(data) is not bytes or type(limits) is not ParseLimits:
raise InvalidArgumentError("expected bytes and ParseLimits")
for value in (limits.max_input_bytes, limits.max_records,
limits.max_counter_values, limits.max_name_bytes):
if type(value) is not int or value <= 0 or value > sys.maxsize:
raise InvalidArgumentError("limits must be positive bounded integers")
if cancel is not None and not callable(cancel):
raise InvalidArgumentError("cancel must be callable or None")
def parse_profile_text(
data: bytes,
limits: ParseLimits,
*,
cancel: Callable[[object], bool] | None = None,
cancel_context: object = None,
) -> tuple[ProfileRecord, ...]:
"""Parse all records or raise a specific error; never return a partial result."""
_validate_arguments(data, limits, cancel)
gate = _Gate(cancel, cancel_context)
gate.poll()
if len(data) > limits.max_input_bytes:
raise LimitExceededError("input bytes limit exceeded")
parser = _Parser(data, limits, gate)
records = _sorted_unique(parser.records(), gate)
gate.poll()
result = tuple(_record_values(records, gate))
gate.poll()
return result
+148
View File
@@ -0,0 +1,148 @@
#!/usr/bin/env bash
# replay-selector.sh — replay every September 2026 PR push through the lane
# selector (scripts/ci/select-lanes.sh) and hold it to the measured history.
#
# The rule set was chosen by replaying this history offline; this keeps the
# committed selector honest against the same data, as a contract step:
# 1. every push (767) gets exactly the tier recorded in fixtures/2026-09-
# pushes.tsv. Where that differs from the analysis tier the row must name
# its documented deviation, and a deviation may only ever raise the tier
# (the fail-safe direction) -- never quietly select less than was measured;
# 2. every real failure of the month (22, fixtures/2026-09-real-failures.tsv)
# had its catching lane selected for its push;
# 3. every push runs the static contract steps (the contracts lane or any
# test leg) -- they police docs, test-infrastructure/ and lint config.
# Pure data, one selector process (--batch over the distinct file sets).
#
# Usage: scripts/test-impact/replay-selector.sh [repo-root]
set -euo pipefail
case "${1:-}" in
-h | --help)
sed -n '2,17p' "$0" | sed 's/^# \{0,1\}//'
exit 0
;;
-*)
echo "replay-selector.sh: unknown option '$1'. Please consult --help." >&2
exit 2
;;
esac
ROOT="${1:-$(cd "$(dirname "$0")/../.." && pwd)}"
FIXTURES="$ROOT/scripts/test-impact/fixtures"
WORK=$(mktemp -d "${TMPDIR:-/tmp}/cbm-replay-selector.XXXXXX")
trap 'rm -rf "$WORK"' EXIT
python3 - "$FIXTURES" "$WORK/batch.jsonl" <<'PY'
import json
import sys
fixtures, batch = sys.argv[1:3]
def rows(name):
with open(f"{fixtures}/{name}", encoding="utf-8") as fh:
for line in fh:
line = line.rstrip("\r\n")
if line and not line.startswith("#"):
yield line.split("\t")
with open(batch, "w", encoding="utf-8") as out:
for fileset, paths in rows("2026-09-filesets.tsv"):
out.write(json.dumps({"id": fileset, "files": paths.split(" ")}) + "\n")
PY
bash "$ROOT/scripts/ci/select-lanes.sh" --batch "$WORK/batch.jsonl" >"$WORK/decisions.jsonl"
python3 - "$FIXTURES" "$WORK/decisions.jsonl" <<'PY'
import collections
import json
import sys
fixtures, decisions_path = sys.argv[1:3]
TIERS = ["T0a-docs", "T0b-nonproduct", "T1-tests", "T2-extract", "T3-mid",
"T4-platform", "T5-core"]
DEVIATIONS = {"makefile-by-name"}
def rows(name, width):
with open(f"{fixtures}/{name}", encoding="utf-8") as fh:
for number, line in enumerate(fh, 1):
line = line.rstrip("\r\n")
if not line or line.startswith("#"):
continue
cols = line.split("\t")
if len(cols) != width:
sys.exit(f"FAIL: {name}:{number}: expected {width} columns, got {len(cols)}")
yield cols
decisions = {}
with open(decisions_path, encoding="utf-8") as fh:
for line in fh:
row = json.loads(line)
decisions[row["id"]] = row
failures = []
pushes = {}
matched = deviated = 0
tiers = collections.Counter()
for run_id, pr, sha, fileset, analysis, expected, note in rows("2026-09-pushes.tsv", 7):
decision = decisions.get(fileset)
if decision is None:
failures.append(f"run {run_id}: fileset {fileset} missing")
continue
pushes[run_id] = decision
tiers[decision["tier"]] += 1
if decision["tier"] != expected:
failures.append(f"run {run_id} (PR #{pr}, {sha[:10]}): selector tier "
f"{decision['tier']}, expected {expected} -- reasons: "
f"{'; '.join(decision['reasons'])}")
continue
if analysis == expected:
if note:
failures.append(f"run {run_id}: note '{note}' on a row with no deviation")
matched += 1
elif note not in DEVIATIONS:
failures.append(f"run {run_id}: tier differs from the analysis ({analysis}) "
f"without a documented deviation")
elif TIERS.index(expected) < TIERS.index(analysis):
failures.append(f"run {run_id}: deviation '{note}' LOWERS the tier "
f"{analysis} -> {expected}")
else:
deviated += 1
# The static contract steps police docs, test-infrastructure/ and lint config,
# so every push runs them: in the contracts job or inside any test leg.
CONTRACT_CARRIERS = {"contracts", "unix-x86", "unix-arm64", "unix-macos14",
"unix-macos-intel", "windows"}
uncovered = collections.Counter(d["tier"] for d in pushes.values()
if not set(d["lanes"]) & CONTRACT_CARRIERS)
for tier, count in sorted(uncovered.items()):
failures.append(f"{count} {tier} push(es) run no contract step (neither the "
f"contracts lane nor a test leg)")
caught = 0
for run_id, attempt, pr, sha, lane, why in rows("2026-09-real-failures.tsv", 6):
decision = pushes.get(run_id)
if decision is None:
failures.append(f"real failure run {run_id}: no such push in the replay")
elif lane not in decision["lanes"]:
failures.append(f"real failure run {run_id} (PR #{pr}): lane {lane} NOT selected "
f"(tier {decision['tier']}) -- {why}")
else:
caught += 1
if failures:
print("SELECTOR HISTORY REPLAY FAILED:")
for failure in failures:
print(f" {failure}")
sys.exit(1)
print(f"selector history replay OK: {len(pushes)} pushes, tier as expected for all "
f"({matched} equal to the analysis, {deviated} documented fail-safe deviations); "
f"{caught} real failures, every catching lane selected; every push runs the "
f"contract steps")
print(" pushes per tier: " + ", ".join(f"{t} {tiers[t]}" for t in TIERS))
print(f" full runs: {sum(1 for d in pushes.values() if d['full'])}")
PY
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""Turn a test-impact answer (`codebase-memory-mcp test-impact select`) into
what a CI consumer runs.
selection.py suites ANSWER
One line per suite whose tests the answer selects, or a single `*` when a
lane runs everything. The team-artifact producer re-runs exactly these
suites with coverage (scripts/ci/test-impact-publish.sh).
selection.py filter ANSWER --lane LANE --out FILE [--optional-out FILE2]
exit 0 narrowed: FILE holds the lane's CBM_TEST_ONLY_FILE lines, and
FILE2 the selected suites the engine saw registered only
conditionally (CONDITIONAL): a build may legitimately lack them
(scripts/run-tests-parallel.sh, CBM_TEST_SELECTION_OPTIONAL)
exit 11 the lane selects nothing: FILE is empty
anything else: run everything (FILE and FILE2 are removed)
It fails safe by construction: a run-all answer, a lane it cannot find, an
unknown schema or filter encoding, a token that is not `suite` or
`suite:test`, and a filter that disagrees with the lane's own suite list all
mean "run everything", and so does any crash (exit 1).
"""
import argparse
import json
import os
import re
import sys
SCHEMAS = {"cbm.test_impact.v0", "cbm.test_impact.v1"}
TOKEN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*(:[A-Za-z_][A-Za-z0-9_]*)?$")
NARROWED, NOTHING, RUN_ALL = 0, 11, 10
def suites(answer):
if answer.get("decision") == "run_all":
return ["*"]
out = []
for lane in answer.get("lanes") or []:
if lane.get("decision") == "run_all" or lane.get("suites") is None:
return ["*"]
out += [suite["suite"] for suite in lane["suites"]]
return sorted(set(out))
def run_all(why):
print(f"selection: run everything: {why}", file=sys.stderr)
return RUN_ALL
def lane_filter(answer, name):
"""-> (exit code, lines)"""
if answer.get("schema") not in SCHEMAS:
return run_all(f"unknown schema {answer.get('schema')!r}"), []
if answer.get("decision") == "run_all":
return run_all("the answer runs everything"), []
lanes = [lane for lane in answer.get("lanes") or [] if lane.get("lane") == name]
if len(lanes) != 1:
return run_all(f"lane {name!r} appears {len(lanes)} times"), []
lane = lanes[0]
decision = lane.get("decision")
if decision == "skip":
if lane.get("suites") or lane.get("runner_filter") is not None:
return run_all("a skipped lane that still lists suites or a filter"), []
return NOTHING, []
if decision != "selected" or not isinstance(lane.get("suites"), list):
return run_all(f"lane decision {decision!r}"), []
flt = lane.get("runner_filter")
if not isinstance(flt, dict) or flt.get("encoding") != "cbm-test-only-lines" or \
not isinstance(flt.get("value"), str):
return run_all("no cbm-test-only-lines filter"), []
lines = [line for line in flt["value"].split("\n") if line]
bad = [line for line in lines if not TOKEN.match(line)]
if not lines or bad:
return run_all(f"filter tokens unusable ({bad[:3] or 'empty'})"), []
expected = set()
for suite in lane["suites"]:
if suite.get("mode") == "whole":
expected.add(suite["suite"])
elif suite.get("mode") == "tests" and suite.get("tests"):
expected |= {f"{suite['suite']}:{test['test']}" for test in suite["tests"]}
else:
return run_all(f"suite {suite.get('suite')!r} has mode {suite.get('mode')!r}"), []
if set(lines) != expected:
return run_all("the filter disagrees with the lane's suite list"), []
return NARROWED, lines
def conditional_suites(answer, name):
"""The lane's selected suites whose registration the engine saw as conditional."""
for lane in answer.get("lanes") or []:
if lane.get("lane") == name:
return sorted({suite["suite"] for suite in lane.get("suites") or []
if "CONDITIONAL" in (suite.get("whole_reason") or [])})
return []
def main():
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
sub = parser.add_subparsers(dest="command", required=True)
s = sub.add_parser("suites")
s.add_argument("answer")
f = sub.add_parser("filter")
f.add_argument("answer")
f.add_argument("--lane", required=True)
f.add_argument("--out", required=True)
f.add_argument("--optional-out")
args = parser.parse_args()
if args.command == "suites":
# LF on every host, like the files below: a Windows python would
# otherwise write CRLF.
sys.stdout.reconfigure(newline="\n")
with open(args.answer, encoding="utf-8") as handle:
print("\n".join(suites(json.load(handle))))
return 0
for stale in (args.out, args.optional_out):
if stale and os.path.exists(stale):
os.remove(stale)
try:
with open(args.answer, encoding="utf-8") as handle:
answer = json.load(handle)
except (OSError, ValueError) as error:
return run_all(f"unreadable answer ({error})")
if not isinstance(answer, dict):
return run_all("the answer is not an object")
code, lines = lane_filter(answer, args.lane)
if code in (NARROWED, NOTHING):
with open(args.out, "w", encoding="utf-8", newline="\n") as handle:
handle.write("".join(line + "\n" for line in lines))
if args.optional_out:
optional = conditional_suites(answer, args.lane) if code == NARROWED else []
with open(args.optional_out, "w", encoding="utf-8", newline="\n") as handle:
handle.write("".join(suite + "\n" for suite in optional))
return code
if __name__ == "__main__":
sys.exit(main())
+92 -4
View File
@@ -10,7 +10,7 @@ cd "$ROOT"
usage() {
cat <<'EOF'
Usage: scripts/test.sh [--suites LIST] [--arch ARCH] [VAR=VAL ...]
Usage: scripts/test.sh [--suites LIST | --tsan | --contracts-only] [--arch ARCH] [VAR=VAL ...]
The canonical test entry: identical in local CI, PR CI, dry run and release.
DEFAULT (no --suites) is exactly what CI runs: static contract checks
@@ -29,6 +29,10 @@ Modes:
--tsan ThreadSanitizer leg (data-race gate): builds and runs the
widened TSan runner via make test-tsan — the same leg CI's
tsan jobs and the compose test-tsan service run.
--contracts-only
Only the static contract steps (Step 0*) of the default leg:
no compiler, no build, no suites. PR CI runs this for
docs-only and non-product changes (tiers T0a, T0b).
Options:
--arch ARCH Force target arch (arm64 | x86_64), e.g. under Rosetta.
@@ -50,6 +54,11 @@ Environment:
CBM_RUN_HANG_TEST=1 Opt-in C++ index-hang regression (#410, needs prod).
CBM_NO_CCACHE=1 Disable the content-verified compiler cache.
CBM_TEST_SHARD/_LEG Set by CI's sharded legs; leave unset locally.
CBM_TEST_SELECTION_DIR PR CI's test selection (smart CI): a directory with
test-only.txt (suite / suite:test lines) and
optional-suites.txt. Narrows the parallel suite step
only; contract steps and CBM_TEST_SEQUENTIAL=1 runs
keep running everything.
Examples:
scripts/test.sh # the full venue leg (what CI runs)
@@ -64,11 +73,13 @@ EOF
# silently swallowed — agents must know exactly what a run will do.
SUITES=""
TSAN=0
CONTRACTS_ONLY=0
prev_arg=""
for arg in "$@"; do
case "$arg" in
-h|--help) usage; exit 0 ;;
--tsan) :;;
--contracts-only) :;;
--suites) :;; # next arg is the value, handled below
--suites=*) SUITES="${arg#--suites=}" ;;
--arch) :;; # next arg is the value, handled below
@@ -96,6 +107,7 @@ done
for arg in "$@"; do
case "$arg" in
--tsan) TSAN=1 ;;
--contracts-only) CONTRACTS_ONLY=1 ;;
arm64|x86_64)
if [[ "${prev_arg2:-}" == "--arch" ]]; then
export CBM_ARCH="$arg"
@@ -121,6 +133,10 @@ if [ "$TSAN" -eq 1 ] && [ -n "$SUITES" ]; then
echo "test.sh: --tsan and --suites are separate modes (the TSan leg has its own suite set). Please consult --help." >&2
exit 2
fi
if [ "$CONTRACTS_ONLY" -eq 1 ] && { [ "$TSAN" -eq 1 ] || [ -n "$SUITES" ]; }; then
echo "test.sh: --contracts-only is its own mode (no build, no suites). Please consult --help." >&2
exit 2
fi
prev_arg=""
# Also support --arch=value
@@ -152,7 +168,7 @@ for arg in "$@"; do
CC=*|CXX=*) export "${arg}" ;;
--arch|--arch=*) ;; # already handled
arm64|x86_64) ;; # already handled
--tsan) ;; # already handled
--tsan|--contracts-only) ;; # already handled
--suites|--suites=*) ;; # already handled (value skipped via prev_arg below)
BUILD_DIR=*) BUILD_DIR="${arg#BUILD_DIR=}"; MAKE_ARGS+=("$arg") ;;
SANITIZE=*)
@@ -338,9 +354,45 @@ bash "$ROOT/tests/test_version_metadata_contract.sh"
echo "=== Step 0y: VM leg verdict contract ==="
bash "$ROOT/tests/test_vm_verdict_contract.sh"
echo "=== Step 0z: setup scripts install through the installers' verified path ==="
echo "=== Step 0y2: isolated Windows venue contract ==="
bash "$ROOT/tests/test_vm_isolation_contract.sh"
echo "=== Step 0y3: setup scripts install through the installers' verified path ==="
bash "$ROOT/tests/test_setup_scripts_contract.sh"
# Step 0z: PR CI runs only the lanes scripts/ci/select-lanes.sh selects, so a
# wrong selection is a silent gate loss. The decision table, and the replay of
# every September 2026 PR push and real failure against it.
echo "=== Step 0z: PR lane selector contract ==="
bash "$ROOT/tests/test_select_lanes.sh"
echo "=== Step 0z2: lane selector history replay ==="
bash "$ROOT/scripts/test-impact/replay-selector.sh"
echo "=== Step 0z3: lane-aware aggregate gates (ci-ok, shard union) ==="
bash "$ROOT/tests/test_lane_gate_contract.sh"
echo "=== Step 0z4: lane wiring (PRs select, dry run and release run all) ==="
bash "$ROOT/tests/test_lane_wiring_contract.sh"
echo "=== Step 0z5: test-impact shadow prediction contract ==="
bash "$ROOT/tests/test_test_impact_predict.sh"
# Step 0z6: the per-test coverage map must never report a test as executing
# less than it did: every case where the builder cannot know (a killed forked
# child, an unreadable profile, a failed suite) has to come out `incomplete`.
echo "=== Step 0z6: per-test coverage map builder contract ==="
bash "$ROOT/tests/test_coverage_map.sh"
# Step 0z7: a push's incremental coverage map carries the suites it did not
# re-run onto the new image's function table; a wrong remap would hand the PR
# selection a map that claims tests ran functions they never touched.
echo "=== Step 0z7: incremental coverage map merge contract ==="
bash "$ROOT/tests/test_coverage_merge.sh"
# Step 0z8: the one place a test-impact answer becomes what PR CI runs. Only a
# well-formed, self-consistent selection may narrow; everything else runs all.
echo "=== Step 0z8: test selection (answer -> runner filter) contract ==="
bash "$ROOT/tests/test_test_impact_selection.sh"
if [ "$CONTRACTS_ONLY" -eq 1 ]; then
echo "=== test.sh: contracts-only — every contract step passed ==="
exit 0
fi
# Verify compiler supports target arch
verify_compiler "$CC"
@@ -354,10 +406,46 @@ BUILD_DIR="$BUILD_DIR" scripts/clean.sh
# CBM_TEST_SEQUENTIAL=1 restores the single-process runner.
make -j"$NPROC" -f Makefile.cbm "$BUILD_DIR/test-runner" ${MAKE_ARGS[@]+"${MAKE_ARGS[@]}"}
assert_test_runner_build_config "$BUILD_DIR/test-runner" "$EXPECTED_SANITIZED"
# Step 2b: per-test selection process regression. CBM_TEST_ONLY must run exactly
# the tests it names and fail on a token that matches nothing; a suite cannot
# assert that about the runner executing it. Runs against the runner just built
# and BEFORE the suites: it takes seconds, and a selection that silently drops
# tests is a property of the harness every later result depends on.
echo "=== Step 2b: per-test selection regression (CBM_TEST_ONLY) ==="
CBM_TEST_RUNNER="$ROOT/$BUILD_DIR/test-runner" bash "$ROOT/tests/test_harness_test_only.sh"
# Step 2c: PR CI's test selection (smart CI) through the parallel harness — the
# selected suites only, unknown suites and tests fail, conditional ones drop.
echo "=== Step 2c: parallel-harness test selection regression ==="
CBM_TEST_RUNNER="$ROOT/$BUILD_DIR/test-runner" bash "$ROOT/tests/test_harness_selection.sh"
# PR CI's test selection (smart CI, the select-tests job) narrows the parallel
# suite step and nothing else (scripts/run-tests-parallel.sh). A selection that
# was promised but is missing is a plumbing fault: fail, never run silently.
SELECTION_ENV=()
if [ -n "${CBM_TEST_SELECTION_DIR:-}" ]; then
selection_dir="$CBM_TEST_SELECTION_DIR"
if command -v cygpath > /dev/null 2>&1; then
selection_dir="$(cygpath -u "$selection_dir")"
fi
if [ ! -s "$selection_dir/test-only.txt" ]; then
echo "FAIL: CBM_TEST_SELECTION_DIR=$CBM_TEST_SELECTION_DIR holds no test-only.txt" >&2
exit 1
fi
SELECTION_ENV=(CBM_TEST_SELECTION_FILE="$selection_dir/test-only.txt")
if [ -f "$selection_dir/optional-suites.txt" ]; then
SELECTION_ENV+=(CBM_TEST_SELECTION_OPTIONAL="$selection_dir/optional-suites.txt")
fi
if [ "${CBM_TEST_SEQUENTIAL:-0}" = "1" ]; then
echo "test.sh: CBM_TEST_SEQUENTIAL=1 runs every test; the selection narrows the parallel harness only"
fi
fi
if [ "${CBM_TEST_SEQUENTIAL:-0}" = "1" ]; then
make -f Makefile.cbm test ${MAKE_ARGS[@]+"${MAKE_ARGS[@]}"}
else
make -f Makefile.cbm test-par ${MAKE_ARGS[@]+"${MAKE_ARGS[@]}"}
env ${SELECTION_ENV[@]+"${SELECTION_ENV[@]}"} \
make -f Makefile.cbm test-par ${MAKE_ARGS[@]+"${MAKE_ARGS[@]}"}
fi
# Step 3a: the runner must ignore an inherited git repository environment
+1 -1
View File
@@ -1564,7 +1564,7 @@ static const char skill_content[] =
"`check_index_coverage`, `manage_adr`, `ingest_traces`\n"
"\n"
"## Edge Types\n"
"CALLS, HTTP_CALLS, ASYNC_CALLS, DATA_FLOWS, IMPORTS, DEFINES, DEFINES_METHOD,\n"
"CALLS, HTTP_CALLS, ASYNC_CALLS, SPAWNS, DATA_FLOWS, IMPORTS, DEFINES, DEFINES_METHOD,\n"
"HANDLES, IMPLEMENTS, OVERRIDE, USAGE, CALL_REFERENCE, CONFIGURES, REFERENCES_FILE,\n"
"FILE_CHANGES_WITH, SIMILAR_TO, SEMANTICALLY_RELATED, CONTAINS_FILE, CONTAINS_FOLDER,\n"
"CONTAINS_PACKAGE\n"
+91 -1
View File
@@ -2778,6 +2778,96 @@ static bool eval_where(const cbm_where_clause_t *w, binding_t *b) {
return is_and;
}
/* Three-valued WHERE evaluation for the early (seed-row) pass in
* execute_single: only the first MATCH alias is bound there, so a leaf on
* any other alias is UNKNOWN rather than true. The two-valued evaluator
* returns true for an unbound alias, which NOT / XOR then invert -- pruning
* every seed before relationship expansion (distilled from PR #1245). The
* full evaluation reruns after expansion (Step 3), so the early pass only has
* to prune on a definite false. Allocation-free, O(expression nodes). */
typedef enum { CYP_PARTIAL_FALSE = 0, CYP_PARTIAL_TRUE, CYP_PARTIAL_UNKNOWN } cyp_partial_t;
static cyp_partial_t partial_and(cyp_partial_t l, cyp_partial_t r) {
if (l == CYP_PARTIAL_FALSE || r == CYP_PARTIAL_FALSE) {
return CYP_PARTIAL_FALSE;
}
return (l == CYP_PARTIAL_TRUE && r == CYP_PARTIAL_TRUE) ? CYP_PARTIAL_TRUE
: CYP_PARTIAL_UNKNOWN;
}
static cyp_partial_t partial_or(cyp_partial_t l, cyp_partial_t r) {
if (l == CYP_PARTIAL_TRUE || r == CYP_PARTIAL_TRUE) {
return CYP_PARTIAL_TRUE;
}
return (l == CYP_PARTIAL_FALSE && r == CYP_PARTIAL_FALSE) ? CYP_PARTIAL_FALSE
: CYP_PARTIAL_UNKNOWN;
}
/* A condition whose alias is not bound yet is UNKNOWN; a literal-only LHS
* (func condition with no variable arg) has nothing to wait for. */
static cyp_partial_t eval_condition_partial(const cbm_condition_t *c, binding_t *b) {
if (c->variable && !binding_get(b, c->variable) && !binding_get_edge(b, c->variable)) {
return CYP_PARTIAL_UNKNOWN;
}
return eval_condition(c, b) ? CYP_PARTIAL_TRUE : CYP_PARTIAL_FALSE;
}
static cyp_partial_t eval_expr_partial(const cbm_expr_t *e, // NOLINT(misc-no-recursion)
binding_t *b) {
if (!e) {
return CYP_PARTIAL_TRUE;
}
if (e->type == EXPR_CONDITION) {
return eval_condition_partial(&e->cond, b);
}
cyp_partial_t left = eval_expr_partial(e->left, b);
if (e->type == EXPR_NOT) {
if (left == CYP_PARTIAL_UNKNOWN) {
return CYP_PARTIAL_UNKNOWN;
}
return left == CYP_PARTIAL_TRUE ? CYP_PARTIAL_FALSE : CYP_PARTIAL_TRUE;
}
/* Same short-circuits as eval_expr: a definite left decides AND / OR. */
if (e->type == EXPR_AND && left == CYP_PARTIAL_FALSE) {
return CYP_PARTIAL_FALSE;
}
if (e->type == EXPR_OR && left == CYP_PARTIAL_TRUE) {
return CYP_PARTIAL_TRUE;
}
cyp_partial_t right = eval_expr_partial(e->right, b);
if (e->type == EXPR_AND) {
return partial_and(left, right);
}
if (e->type == EXPR_OR) {
return partial_or(left, right);
}
/* EXPR_XOR */
if (left == CYP_PARTIAL_UNKNOWN || right == CYP_PARTIAL_UNKNOWN) {
return CYP_PARTIAL_UNKNOWN;
}
return left != right ? CYP_PARTIAL_TRUE : CYP_PARTIAL_FALSE;
}
static cyp_partial_t eval_where_partial(const cbm_where_clause_t *w, binding_t *b) {
if (!w) {
return CYP_PARTIAL_TRUE;
}
if (w->root) {
return eval_expr_partial(w->root, b);
}
/* Legacy flat evaluation */
if (w->count == 0) {
return CYP_PARTIAL_TRUE;
}
bool is_and = (w->op && strcmp(w->op, "AND") == 0) != 0;
cyp_partial_t result = is_and ? CYP_PARTIAL_TRUE : CYP_PARTIAL_FALSE;
for (int i = 0; i < w->count; i++) {
cyp_partial_t r = eval_condition_partial(&w->conditions[i], b);
result = is_and ? partial_and(result, r) : partial_or(result, r);
}
return result;
}
/* Check if a string value looks like a regex pattern. */
static bool looks_like_regex(const char *s) {
if (!s) {
@@ -5281,7 +5371,7 @@ static int execute_single(cbm_store_t *store, cbm_query_t *q, const char *projec
binding_t b = {0};
b.store = store;
binding_set(&b, var_name, &scanned[i]);
bool pass = !q->where || eval_where(q->where, &b);
bool pass = eval_where_partial(q->where, &b) != CYP_PARTIAL_FALSE;
if (pass) {
bindings[bind_count++] = b;
} else {
+4
View File
@@ -178,6 +178,10 @@ cbm_daemon_process_role_t cbm_daemon_process_role(int argc, char *const argv[])
* project, so it needs no daemon. Listed here rather than routed through
* the daemon so enrolling a root cannot depend on daemon state. */
"allow-root",
/* test-impact runs its own frozen index of a pinned tree in process
* (CI: publish the team artifact, select tests); it never reads or
* writes the daemon's projects, so it needs no daemon. */
"test-impact",
};
/* Stop at the first top-level mode token. Tool names, flag values, and JSON
* following `cli` are opaque user input: a search query named "install"
+18
View File
@@ -7078,3 +7078,21 @@ int cbm_daemon_ipc_receive_frame(cbm_daemon_ipc_connection_t *connection, uint32
return cbm_daemon_ipc_receive_frame_bounded(connection, timeout_ms, CBM_DAEMON_MAX_FRAME_SIZE,
frame_out, payload_out);
}
int cbm_daemon_ipc_receive_frame_after_failed_send(cbm_daemon_ipc_connection_t *connection,
uint32_t timeout_ms, uint32_t max_payload_length,
cbm_daemon_frame_t *frame_out,
uint8_t **payload_out) {
if (!connection) {
return cbm_daemon_ipc_receive_frame_bounded(NULL, timeout_ms, max_payload_length, frame_out,
payload_out);
}
/* The poison came from the failed send alone (nothing was read yet), and it
* guards the OUTBOUND frame boundary; the inbound one is intact. Lift it for
* this one receive, then restore it so no later send can reuse the stream. */
atomic_store_explicit(&connection->poisoned, false, memory_order_release);
int received = cbm_daemon_ipc_receive_frame_bounded(connection, timeout_ms, max_payload_length,
frame_out, payload_out);
atomic_store_explicit(&connection->poisoned, true, memory_order_release);
return received;
}
+16
View File
@@ -75,6 +75,22 @@ int cbm_daemon_ipc_receive_frame_bounded(cbm_daemon_ipc_connection_t *connection
uint32_t timeout_ms, uint32_t max_payload_length,
cbm_daemon_frame_t *frame_out, uint8_t **payload_out);
/* Bounded receive of an answer the peer queued before it closed, on a
* connection whose send just failed. A daemon at capacity or stopping answers
* from its accept loop without reading the request and closes at once; when
* that close lands before the client's first send, the send fails (EPIPE) and
* poisons the connection although the complete answer is still queued for
* reading -- POSIX keeps it readable after the peer's close (a Windows server
* close discards it, so there this fails like any other receive). A failed
* send consumes no inbound bytes, so the inbound frame boundary is intact;
* call this ONLY before anything was read from the connection. The connection
* stays poisoned for sends afterwards. Returns like
* cbm_daemon_ipc_receive_frame_bounded. */
int cbm_daemon_ipc_receive_frame_after_failed_send(cbm_daemon_ipc_connection_t *connection,
uint32_t timeout_ms, uint32_t max_payload_length,
cbm_daemon_frame_t *frame_out,
uint8_t **payload_out);
/* Narrow crash/fault seams for publication-state and retry-state tests. They
* are inert unless a test installs a hook/failure count in its own process. */
typedef enum {
+72 -8
View File
@@ -76,8 +76,26 @@ static _Atomic uint32_t runtime_ephemeral_linger_timeout_seam = UINT32_MAX;
void cbm_daemon_runtime_service_set_ephemeral_linger_timeout_for_testing(uint32_t timeout_ms) {
atomic_store(&runtime_ephemeral_linger_timeout_seam, timeout_ms);
}
/* Connection-cap seams (flaky-ledger item 12): see runtime.h. */
static atomic_bool runtime_hold_peer_waits_seam;
void cbm_daemon_runtime_hold_peer_waits_for_testing(bool hold) {
atomic_store(&runtime_hold_peer_waits_seam, hold);
}
static _Atomic(cbm_daemon_runtime_hello_hook_t) runtime_hello_hook_seam;
void cbm_daemon_runtime_set_hello_hook_for_testing(cbm_daemon_runtime_hello_hook_t hook) {
atomic_store(&runtime_hello_hook_seam, hook);
}
static void runtime_hello_point(cbm_daemon_runtime_hello_point_t point) {
cbm_daemon_runtime_hello_hook_t hook = atomic_load(&runtime_hello_hook_seam);
if (hook) {
hook(point);
}
}
#define RUNTIME_HELLO_POINT(point) runtime_hello_point(CBM_DAEMON_RUNTIME_HELLO_POINT_##point)
#else
static void runtime_note_peer_image_hash(void) {}
#define RUNTIME_HELLO_POINT(point) ((void)0)
#endif
#ifdef _WIN32
@@ -1989,6 +2007,29 @@ static void runtime_worker_handle_activation_shutdown(cbm_daemon_runtime_worker_
runtime_worker_finish(worker);
}
/* The first frame is due within the request deadline. Under the item-12 seam a
* connection that never sends one holds its slot until it closes; stop still
* interrupts the wait like any other worker receive. */
static uint32_t runtime_first_frame_timeout_ms(const cbm_daemon_runtime_service_t *service) {
#ifdef CBM_ENABLE_TEST_SEAMS
if (atomic_load(&runtime_hold_peer_waits_seam)) {
return CBM_DAEMON_IPC_WAIT_FOREVER;
}
#endif
return service->request_timeout_ms;
}
/* Under the same seam an inline rejection's drain ends only on the peer's
* close, never on the 250 ms bound (the drain only waits on Windows). */
static uint32_t runtime_reject_drain_timeout_ms(void) {
#ifdef CBM_ENABLE_TEST_SEAMS
if (atomic_load(&runtime_hold_peer_waits_seam)) {
return CBM_DAEMON_IPC_WAIT_FOREVER;
}
#endif
return RUNTIME_REJECT_DRAIN_TIMEOUT_MS;
}
static void *runtime_connection_worker(void *opaque) {
cbm_daemon_runtime_worker_t *worker = opaque;
cbm_daemon_runtime_service_t *service = worker->service;
@@ -1998,7 +2039,7 @@ static void *runtime_connection_worker(void *opaque) {
char requested_build[CBM_DAEMON_BUILD_FINGERPRINT_SIZE];
int received = cbm_daemon_ipc_receive_frame_bounded(
worker->connection, service->request_timeout_ms,
worker->connection, runtime_first_frame_timeout_ms(service),
CBM_DAEMON_ACTIVATION_SHUTDOWN_REQUEST_SIZE, &frame, &payload);
if (received != 1 || frame.type != CBM_DAEMON_FRAME_REQUEST) {
free(payload);
@@ -2274,14 +2315,19 @@ static cbm_daemon_runtime_worker_t *runtime_find_free_worker_locked(
return NULL;
}
/* Answers WITHOUT reading the peer's HELLO, so on POSIX (where the drain is a
* no-op) the close can land before the peer has even sent it; the client reads
* this answer after its failed send (cbm_daemon_runtime_client_connect). */
static void runtime_reject_inline(cbm_daemon_ipc_connection_t *connection, const char *message) {
cbm_daemon_runtime_connect_result_t result;
runtime_result_rejected(&result, message);
RUNTIME_HELLO_POINT(DAEMON_REJECTING);
(void)runtime_send_hello_response(connection, &result);
/* Same Windows named-pipe discard hazard as runtime_worker_finish: the
* peer must get to read the rejection before the handle closes. */
cbm_daemon_ipc_connection_drain(connection, RUNTIME_REJECT_DRAIN_TIMEOUT_MS);
cbm_daemon_ipc_connection_drain(connection, runtime_reject_drain_timeout_ms());
cbm_daemon_ipc_connection_close(connection);
RUNTIME_HELLO_POINT(DAEMON_REJECTED);
}
static void runtime_accept_connection(cbm_daemon_runtime_service_t *service,
@@ -3063,21 +3109,39 @@ cbm_daemon_runtime_client_t *cbm_daemon_runtime_client_connect(
return NULL;
}
cbm_daemon_ipc_connection_t *connection = cbm_daemon_ipc_connect(endpoint, timeout_ms);
if (!connection || !cbm_daemon_ipc_send_frame(connection, CBM_DAEMON_FRAME_REQUEST,
CBM_DAEMON_RUNTIME_OP_HELLO, request,
(uint32_t)sizeof(request))) {
cbm_daemon_ipc_connection_close(connection);
if (!connection) {
return NULL;
}
RUNTIME_HELLO_POINT(CLIENT_CONNECTED);
bool hello_sent =
cbm_daemon_ipc_send_frame(connection, CBM_DAEMON_FRAME_REQUEST, CBM_DAEMON_RUNTIME_OP_HELLO,
request, (uint32_t)sizeof(request));
RUNTIME_HELLO_POINT(CLIENT_SENT);
cbm_daemon_frame_t frame = {0};
uint8_t *payload = NULL;
int received = cbm_daemon_ipc_receive_frame_bounded(
connection, timeout_ms, CBM_DAEMON_RENDEZVOUS_RESPONSE_SIZE, &frame, &payload);
/* A daemon at capacity or stopping answers from its accept loop without
* reading this HELLO and closes at once. When that close lands first, the
* send fails (EPIPE) with the whole answer already queued here: read it,
* or the caller gets a bare transport error and bootstrap classifies by
* lock state instead of by the daemon's own "capacity" / "stopping". */
int received =
hello_sent
? cbm_daemon_ipc_receive_frame_bounded(
connection, timeout_ms, CBM_DAEMON_RENDEZVOUS_RESPONSE_SIZE, &frame, &payload)
: cbm_daemon_ipc_receive_frame_after_failed_send(
connection, timeout_ms, CBM_DAEMON_RENDEZVOUS_RESPONSE_SIZE, &frame, &payload);
bool valid = received == 1 && frame.type == CBM_DAEMON_FRAME_RESPONSE &&
frame.flags == CBM_DAEMON_RUNTIME_OP_HELLO &&
frame.length == CBM_DAEMON_RENDEZVOUS_RESPONSE_SIZE &&
runtime_hello_response_decode(payload, result_out);
free(payload);
if (!hello_sent && (!valid || result_out->status != CBM_DAEMON_RUNTIME_CONNECT_REJECTED)) {
/* Only a rejection can answer a HELLO the daemon never received;
* anything else stays the plain send failure it always was. */
memset(result_out, 0, sizeof(*result_out));
cbm_daemon_ipc_connection_close(connection);
return NULL;
}
if (received != 1) {
/* The kernel completed the pipe/socket connect, so a server process
* exists, yet the HELLO went unanswered. Name that holder: a dead
+21
View File
@@ -471,6 +471,27 @@ void cbm_daemon_runtime_set_containment_hook_for_testing(
* in test time. UINT32_MAX restores the production constant; any other value
* (0 = expire immediately) overrides. Process-global; reset it after use. */
void cbm_daemon_runtime_service_set_ephemeral_linger_timeout_for_testing(uint32_t timeout_ms);
/* Connection-cap seams (flaky-ledger item 12). While held, the two bounded
* waits on a peer in the connection-cap path end only on that peer: a
* connection that has not sent its first frame keeps its worker slot until it
* closes (or the service stops) instead of until the request deadline, and an
* inline rejection's drain (Windows; a no-op on POSIX) lasts until the peer
* closes instead of 250 ms. A "slow hello" becomes a state the test releases,
* not a window it races. Process-global; reset it after use. */
void cbm_daemon_runtime_hold_peer_waits_for_testing(bool hold);
/* The four points that order a HELLO against an inline accept-loop rejection.
* The hook runs on the client thread for the CLIENT_* points and on the accept
* thread for the DAEMON_* points (never under the service mutex), so a test can
* pin either order: daemon answers after the HELLO is on the wire, or the
* daemon has answered and closed before the HELLO is sent. NULL removes it. */
typedef enum {
CBM_DAEMON_RUNTIME_HELLO_POINT_CLIENT_CONNECTED = 0, /* transport up, HELLO not sent */
CBM_DAEMON_RUNTIME_HELLO_POINT_CLIENT_SENT, /* HELLO send attempted */
CBM_DAEMON_RUNTIME_HELLO_POINT_DAEMON_REJECTING, /* inline rejection not yet written */
CBM_DAEMON_RUNTIME_HELLO_POINT_DAEMON_REJECTED, /* inline rejection written + closed */
} cbm_daemon_runtime_hello_point_t;
typedef void (*cbm_daemon_runtime_hello_hook_t)(cbm_daemon_runtime_hello_point_t point);
void cbm_daemon_runtime_set_hello_hook_for_testing(cbm_daemon_runtime_hello_hook_t hook);
#endif
#endif /* CBM_DAEMON_RUNTIME_H */
+13 -75
View File
@@ -10,6 +10,7 @@
* 5. Language detection for accepted files
*/
#include "discover/discover.h"
#include "discover/discover_policy_internal.h"
#include "cbm.h" // CBMLanguage, CBM_LANG_COUNT, CBM_LANG_JSON
#include "foundation/constants.h"
@@ -107,20 +108,6 @@ static const char *FAST_PATTERNS[] = {".d.ts", ".bundle.", ".chunk.", ".gen
"_string.go", "mock_", "_mock.", "_test_helpers.",
".stories.", ".spec.", ".test.", NULL};
/* ── Ignored JSON filenames ──────────────────────── */
static const char *IGNORED_JSON_FILES[] = {
"package.json", "package-lock.json", "tsconfig.json",
"jsconfig.json", "composer.json", "composer.lock",
"yarn.lock", "openapi.json", "swagger.json",
"jest.config.json", ".eslintrc.json", ".prettierrc.json",
".babelrc.json", "tslint.json", "angular.json",
"firebase.json", "renovate.json", "lerna.json",
"turbo.json", ".stylelintrc.json", "pnpm-lock.json",
"deno.json", "biome.json", "devcontainer.json",
".devcontainer.json", "launch.json", "settings.json",
"extensions.json", "tasks.json", NULL};
/* ── Helper: check if string is in NULL-terminated array ─────────── */
static bool str_in_list(const char *s, const char *const *list) {
@@ -134,8 +121,7 @@ static bool str_in_list(const char *s, const char *const *list) {
/* ── Helper: check if string ends with suffix ────────────── */
static bool ends_with(const char *s, const char *suffix) {
size_t slen = strlen(s);
static bool ends_with(const char *s, size_t slen, const char *suffix) {
size_t sufflen = strlen(suffix);
if (sufflen > slen) {
return false;
@@ -349,7 +335,7 @@ static bool resolve_global_excludes_path(char *out, size_t out_sz) {
}
/* True when rel_path equals a suffix rule or ends with "/" + the rule. */
static bool rel_path_has_skip_suffix(const char *rel_path) {
bool cbm_discover_path_has_skip_suffix(const char *rel_path) {
if (!rel_path) {
return false;
}
@@ -393,15 +379,16 @@ bool cbm_has_ignored_suffix(const char *filename, cbm_index_mode_t mode) {
return false;
}
size_t filename_length = strlen(filename);
for (int i = 0; ALWAYS_IGNORED_SUFFIXES[i]; i++) {
if (ends_with(filename, ALWAYS_IGNORED_SUFFIXES[i])) {
if (ends_with(filename, filename_length, ALWAYS_IGNORED_SUFFIXES[i])) {
return true;
}
}
if (mode != CBM_MODE_FULL) {
for (int i = 0; FAST_IGNORED_SUFFIXES[i]; i++) {
if (ends_with(filename, FAST_IGNORED_SUFFIXES[i])) {
if (ends_with(filename, filename_length, FAST_IGNORED_SUFFIXES[i])) {
return true;
}
}
@@ -725,7 +712,7 @@ static int gitignore_chain_result(const gitignore_link_t *link, const char *rel_
* (.worktrees / .claude-worktrees, the worktree entries in ALWAYS_SKIP_DIRS)
* contain parallel checkouts of the same repo whose indexing would duplicate
* the whole codebase (#802). */
static bool is_safety_core_dir(const char *name) {
bool cbm_discover_is_safety_core_directory(const char *name) {
static const char *const SAFETY_CORE_DIRS[] = {".git", "node_modules", ".worktrees",
".claude-worktrees", NULL};
return str_in_list(name, SAFETY_CORE_DIRS);
@@ -775,12 +762,12 @@ static bool should_skip_directory(const char *entry_name, const char *rel_path,
const cbm_gitignore_t *global_gi,
const cbm_gitignore_t *cbmignore) {
if (cbm_should_skip_dir(entry_name, opts ? opts->mode : CBM_MODE_FULL) ||
rel_path_has_skip_suffix(rel_path)) {
cbm_discover_path_has_skip_suffix(rel_path)) {
/* #500: a .cbmignore negation (e.g. "!obj/") whose rule is the last
* match for this dir un-skips a built-in skip-list dir — except the
* non-negatable safety core. Fall through so .gitignore/global/local
* rules still apply to the un-skipped dir. */
bool unskipped = cbmignore && !is_safety_core_dir(entry_name) &&
bool unskipped = cbmignore && !cbm_discover_is_safety_core_directory(entry_name) &&
cbm_gitignore_match_result(cbmignore, rel_path, true) < 0;
if (!unskipped) {
return true;
@@ -841,60 +828,11 @@ static const char *file_skip_reason(const char *entry_name, const char *rel_path
return global_ignored ? "gitignore" : NULL;
}
/* Detect language for a file, handling .m disambiguation and JSON filtering. */
/* Detect language for a file: its name, and its first bytes where the name is
* not enough (cbm_language_classify; the pinned test-impact inventory uses the
* same rule on git blobs). */
static CBMLanguage detect_file_language(const char *entry_name, const char *abs_path) {
CBMLanguage lang = cbm_language_for_filename(entry_name);
if (lang == CBM_LANG_COUNT) {
/* Filename/extension detection failed: fall back to a conservative
* shebang probe so extensionless scripts get indexed (#1199). Filename
* detection stays authoritative — this runs only when it returns
* unknown. */
return cbm_language_from_shebang(abs_path);
}
/* Special: .m files need content-based disambiguation */
const char *dot = strrchr(entry_name, '.');
if (dot && strcmp(dot, ".m") == 0) {
lang = cbm_disambiguate_m(abs_path);
}
/* Special: .cls is shared by ObjectScript UDL, Apex and VB6 class modules */
if (dot && strcmp(dot, ".cls") == 0) {
lang = cbm_disambiguate_cls(abs_path);
}
/* Special: .inc is shared by BitBake and ObjectScript include files */
if (dot && strcmp(dot, ".inc") == 0) {
lang = cbm_disambiguate_inc(abs_path);
}
/* Special: .cfc components may be script-dialect or tag-dialect (<cfcomponent>) */
if (dot && strcmp(dot, ".cfc") == 0) {
lang = cbm_disambiguate_cfc(abs_path);
}
/* Special: .frm is shared by FORM and VB6 forms (#721) */
if (dot && strcmp(dot, ".frm") == 0) {
lang = cbm_disambiguate_frm(abs_path);
}
/* Special: .res is also a binary Godot / Windows resource (#2176) */
if (dot && strcmp(dot, ".res") == 0) {
lang = cbm_disambiguate_res(abs_path);
}
/* Special: ObjectScript Studio Export XML (<Export generator="...">) is
* detected by content; otherwise .xml stays XML. */
if (lang == CBM_LANG_XML) {
FILE *xf = cbm_fopen(abs_path, "r");
if (xf) {
char xbuf[CBM_SZ_256];
size_t xn = fread(xbuf, SKIP_ONE, sizeof(xbuf) - SKIP_ONE, xf);
(void)fclose(xf);
xbuf[xn] = '\0';
if (strstr(xbuf, "<Export generator=")) {
return CBM_LANG_OBJECTSCRIPT_EXPORT;
}
}
}
/* Check ignored JSON files */
if (lang == CBM_LANG_JSON && str_in_list(entry_name, IGNORED_JSON_FILES)) {
return CBM_LANG_COUNT;
}
return lang;
return cbm_language_for_file(entry_name, abs_path);
}
/* UTF-8-safe stat: wide API on Windows, regular stat on POSIX. */
+27
View File
@@ -18,6 +18,7 @@
/* Use the existing CBMLanguage enum from extraction layer */
#include "cbm.h"
#include "foundation/index_policy.h"
#include "discover/userconfig.h"
/* ── Language detection ──────────────────────────────────────────── */
@@ -83,6 +84,32 @@ CBMLanguage cbm_language_from_shebang(const char *path);
* On read failure, defaults to CBM_LANG_CFSCRIPT. */
CBMLanguage cbm_disambiguate_cfc(const char *path);
/* The language of a file from its base name and, where the name is not enough,
* its first bytes: the one rule discovery and the pinned test-impact inventory
* share. cbm_language_probe_bytes says how many leading bytes the name needs
* (0: the name decides). cbm_language_classify takes those bytes raw, `more`
* when the file has bytes beyond them, and `readable` false when they could
* not be read (each probe then keeps the name's default, as a failed open
* always did). The name lookup consults the installed user language config. */
size_t cbm_language_probe_bytes(const char *filename);
CBMLanguage cbm_language_classify(const char *filename, const unsigned char *head, size_t head_len,
bool more, bool readable);
/* The same with the language config given explicitly (NULL: none) instead of
* the installed process-wide one, for a caller that classifies a snapshot
* under that snapshot's config while another index may have its own
* installed. */
size_t cbm_language_probe_bytes_with(const cbm_userconfig_t *config, const char *filename);
CBMLanguage cbm_language_classify_with(const cbm_userconfig_t *config, const char *filename,
const unsigned char *head, size_t head_len, bool more,
bool readable);
/* The most leading bytes any name asks cbm_language_probe_bytes for. */
#define CBM_LANGUAGE_PROBE_MAX 16384
/* cbm_language_classify on the file at `path`, read raw. */
CBMLanguage cbm_language_for_file(const char *filename, const char *path);
/* ── Gitignore pattern matching ──────────────────────────────────── */
typedef struct cbm_gitignore cbm_gitignore_t;
+9
View File
@@ -0,0 +1,9 @@
#ifndef CBM_DISCOVER_POLICY_INTERNAL_H
#define CBM_DISCOVER_POLICY_INTERNAL_H
#include <stdbool.h>
/* Pure legacy policy predicates; callers provide live terminated strings. */
bool cbm_discover_path_has_skip_suffix(const char *relative_path);
bool cbm_discover_is_safety_core_directory(const char *name);
#endif
+8 -266
View File
@@ -13,8 +13,9 @@
*/
#include "foundation/constants.h"
#include "foundation/compat_fs.h"
#include "discover/gitignore_internal.h"
enum { GI_INIT_CAP = 16, GI_CHAR_IDX1 = 1, GI_CHAR_IDX2 = 2, GI_SKIP3 = 3 };
enum { GI_INIT_CAP = 16 };
#include "discover/discover.h"
#include <ctype.h>
@@ -24,236 +25,22 @@ enum { GI_INIT_CAP = 16, GI_CHAR_IDX1 = 1, GI_CHAR_IDX2 = 2, GI_SKIP3 = 3 };
/* ── Pattern representation ──────────────────────────────────────── */
typedef struct {
char *pattern; /* the glob pattern (normalized) */
bool negated; /* starts with ! */
bool dir_only; /* ends with / */
bool rooted; /* contains / (anchored to root) */
} gi_pattern_t;
struct cbm_gitignore {
gi_pattern_t *patterns;
int count;
int capacity;
};
/* ── Pattern matching engine ─────────────────────────────────────── */
/* Backtracking budget. `**` retries the remainder at every position, and
* consecutive literal + `**` groups multiply, so cost is exponential in the
* number of groups. Patterns come from a committed .gitignore / .cbmignore and
* every discovered path is matched against every pattern, so a small ignore file
* can make discovery take unbounded time. Cap the match steps per
* (pattern, path) pair.
*
* Exhausting the budget reports "no match", so a pathological pattern fails to
* ignore rather than ignoring the wrong files: the file gets indexed, which is
* the recoverable direction. The cap is far above any real pattern — ordinary
* globs finish in tens of steps. */
enum { GI_MATCH_MAX_STEPS = 20000 };
/* Forward declaration for recursive calls. */
static bool glob_match(const char *pat, const char *str,
int *budget); // NOLINT(misc-no-recursion)
/* Match a ** (doublestar-slash) pattern: try rest at every / boundary. */
static bool glob_match_doublestar_slash(const char *pat, // NOLINT(misc-no-recursion)
const char *str, int *budget) {
if (glob_match(pat, str, budget)) {
return true;
}
for (const char *s = str; *s; s++) {
if (*s == '/' && glob_match(pat, s + SKIP_ONE, budget)) {
return true;
}
}
return false;
}
/* Match a ** (doublestar) followed by non-slash: try at every position. */
static bool glob_match_doublestar_any(const char *pat, // NOLINT(misc-no-recursion)
const char *str, int *budget) {
for (const char *s = str;; s++) {
if (glob_match(pat, s, budget)) {
return true;
}
if (!*s) {
return false;
}
}
}
/* Match a * (single star): match any sequence not containing /. */
static bool glob_match_star(const char *pat, const char *str,
int *budget) { // NOLINT(misc-no-recursion)
for (const char *s = str;; s++) {
if (glob_match(pat, s, budget)) {
return true;
}
if (!*s || *s == '/') {
return false;
}
}
}
/* Match a [...] character class at current position.
* Returns true if matched. Advances *pat_out past the closing ']'. */
static bool glob_match_charclass(const char *pat, char ch, const char **pat_out) {
bool negate_class = false;
if (*pat == '!' || *pat == '^') {
negate_class = true;
pat++;
}
bool matched = false;
char prev = 0;
while (*pat && *pat != ']') {
if (*pat == '-' && prev && pat[GI_CHAR_IDX1] && pat[GI_CHAR_IDX1] != ']') {
pat++;
if (ch >= prev && ch <= *pat) {
matched = true;
}
prev = *pat;
pat++;
} else {
if (ch == *pat) {
matched = true;
}
prev = *pat;
pat++;
}
}
if (*pat == ']') {
pat++;
}
*pat_out = pat;
return negate_class ? !matched : matched;
}
/*
* Match a glob pattern against a string.
* Handles: * (non-slash), ** (any path), ? (single non-slash), [class]
*/
/* Handle ** at current position. Returns match result. */
static bool glob_match_doublestar(const char *pat, const char *str,
int *budget) { // NOLINT(misc-no-recursion)
if (pat[GI_CHAR_IDX2] == '/') {
return glob_match_doublestar_slash(pat + GI_SKIP3, str, budget);
}
if (pat[GI_CHAR_IDX2] == '\0') {
return true;
}
return glob_match_doublestar_any(pat + GI_CHAR_IDX2, str, budget);
}
static bool glob_match(const char *pat, const char *str,
int *budget) { // NOLINT(misc-no-recursion)
if (--*budget <= 0) {
return false; /* budget exhausted — see GI_MATCH_MAX_STEPS */
}
while (*pat && *str) {
if (pat[0] == '*' && pat[GI_CHAR_IDX1] == '*') {
return glob_match_doublestar(pat, str, budget);
}
if (*pat == '*') {
return glob_match_star(pat + SKIP_ONE, str, budget);
}
if (*pat == '?') {
if (*str == '/') {
return false;
}
pat++;
str++;
continue;
}
if (*pat == '[') {
const char *new_pat = NULL;
if (!glob_match_charclass(pat + SKIP_ONE, *str, &new_pat)) {
return false;
}
pat = new_pat;
str++;
continue;
}
if (*pat != *str) {
return false;
}
pat++;
str++;
}
while (*pat == '*') {
pat++;
}
return *pat == '\0' && *str == '\0';
}
/* Match one pattern against one path with a fresh backtracking budget. Not
* recursive itself — it seeds the budget and hands off to the engine. */
static bool glob_match_bounded(const char *pat, const char *str) {
int budget = GI_MATCH_MAX_STEPS;
return glob_match(pat, str, &budget);
}
/* ── Pattern parsing ─────────────────────────────────────────────── */
static void gi_add_pattern(cbm_gitignore_t *gi, const char *line, int len) {
/* Trim trailing whitespace */
while (len > 0 && (line[len - SKIP_ONE] == ' ' || line[len - SKIP_ONE] == '\t' ||
line[len - SKIP_ONE] == '\r')) {
len--;
}
if (len == 0) {
gi_view_t view;
if (!gi_normalize(line, (size_t)len, &view, NULL) || !view.length) {
return;
}
gi_pattern_t p = {0};
/* Check for negation */
const char *start = line;
if (*start == '!') {
p.negated = true;
start++;
len--;
}
if (len == 0) {
return;
}
/* Check for trailing / (directory-only) */
if (start[len - SKIP_ONE] == '/') {
p.dir_only = true;
len--;
}
if (len == 0) {
return;
}
/* Check for leading / (rooted) */
if (*start == '/') {
p.rooted = true;
start++;
len--;
}
if (len == 0) {
return;
}
/* Check if pattern contains / anywhere (makes it rooted) */
if (!p.rooted) {
for (int i = 0; i < len; i++) {
if (start[i] == '/') {
p.rooted = true;
break;
}
}
}
gi_pattern_t p = {.negated = view.negated, .dir_only = view.dir_only, .rooted = view.rooted};
const char *start = view.text;
len = (int)view.length;
/* Copy pattern */
p.pattern = malloc(len + SKIP_ONE);
@@ -345,56 +132,11 @@ cbm_gitignore_t *cbm_gitignore_load(const char *path) {
return gi;
}
/* Match a non-rooted pattern against basename and path suffixes. */
static bool match_unrooted(const char *pattern, const char *rel_path, const char *basename) {
if (glob_match_bounded(pattern, basename)) {
return true;
}
if (!strchr(rel_path, '/')) {
return false;
}
/* Try matching at every / boundary */
const char *s = rel_path;
while (*s) {
if (glob_match_bounded(pattern, s)) {
return true;
}
const char *next = strchr(s, '/');
if (!next) {
break;
}
s = next + SKIP_ONE;
}
return false;
}
int cbm_gitignore_match_result(const cbm_gitignore_t *gi, const char *rel_path, bool is_dir) {
if (!gi || !rel_path) {
return 0;
}
/* Extract the basename for non-rooted pattern matching */
const char *basename = strrchr(rel_path, '/');
basename = basename ? basename + SKIP_ONE : rel_path;
int matched = 0;
for (int i = 0; i < gi->count; i++) {
const gi_pattern_t *p = &gi->patterns[i];
if (p->dir_only && !is_dir) {
continue;
}
bool this_match = p->rooted ? glob_match_bounded(p->pattern, rel_path)
: match_unrooted(p->pattern, rel_path, basename);
if (this_match) {
matched = p->negated ? -1 : 1;
}
}
return matched;
return gi_core_match(gi->patterns, (size_t)gi->count, rel_path, is_dir, NULL);
}
bool cbm_gitignore_matches(const cbm_gitignore_t *gi, const char *rel_path, bool is_dir) {
+114
View File
@@ -0,0 +1,114 @@
#include "discover/gitignore_internal.h"
#include <limits.h>
#include <string.h>
static bool gi_limits_valid(const cbm_ignore_checked_limits_t *limits) {
return limits && limits->max_bytes && limits->max_work && limits->max_patterns &&
limits->max_patterns <= INT_MAX && limits->max_arena_bytes && limits->max_depth &&
limits->max_depth <= CBM_IGNORE_CHECKED_DEPTH_MAX;
}
static cbm_ignore_checked_status_t gi_open(const cbm_ignore_checked_limits_t *limits,
cbm_ignore_checked_t **out,
cbm_ignore_checked_error_t *error, bool fault) {
if (out) {
*out = NULL;
}
gi_error(error, CBM_IGNORE_CHECKED_OK, CBM_IGNORE_CAP_NONE, SIZE_MAX);
if (!out || !gi_limits_valid(limits)) {
gi_error(error, CBM_IGNORE_CHECKED_INVALID, CBM_IGNORE_CAP_NONE, SIZE_MAX);
return CBM_IGNORE_CHECKED_INVALID;
}
if (limits->max_arena_bytes < sizeof(cbm_ignore_checked_t)) {
gi_error(error, CBM_IGNORE_CHECKED_LIMIT, CBM_IGNORE_CAP_ARENA, SIZE_MAX);
return CBM_IGNORE_CHECKED_LIMIT;
}
CBMArena arena;
cbm_arena_init_lazy(&arena, CBM_ARENA_DEFAULT_BLOCK_SIZE);
cbm_ignore_checked_t *owner = fault ? NULL : cbm_arena_alloc(&arena, sizeof(*owner));
if (!owner) {
cbm_arena_destroy(&arena);
gi_error(error, CBM_IGNORE_CHECKED_OOM, CBM_IGNORE_CAP_NONE, SIZE_MAX);
return CBM_IGNORE_CHECKED_OOM;
}
memset(owner, 0, sizeof(*owner));
owner->arena = arena;
owner->limits = *limits;
owner->usage.arena_requested_bytes = sizeof(*owner);
gi_error(&owner->error, CBM_IGNORE_CHECKED_OK, CBM_IGNORE_CAP_NONE, SIZE_MAX);
*out = owner;
return CBM_IGNORE_CHECKED_OK;
}
cbm_ignore_checked_status_t cbm_ignore_checked_open(const cbm_ignore_checked_limits_t *limits,
cbm_ignore_checked_t **out,
cbm_ignore_checked_error_t *error) {
return gi_open(limits, out, error, false);
}
static bool gi_path_valid(gi_guard_t *guard, const char *path, size_t length) {
for (size_t i = 0; i <= length; i++) {
guard->offset = i;
if (!gi_work(guard, 1)) {
return false;
}
if ((path[i] == '\0') != (i == length)) {
return gi_fail(guard, CBM_IGNORE_CHECKED_UNSUPPORTED, CBM_IGNORE_CAP_NONE);
}
}
guard->offset = SIZE_MAX;
return true;
}
cbm_ignore_checked_status_t cbm_ignore_checked_match(
cbm_ignore_checked_t *owner, const cbm_ignore_program_t *program, const char *path,
size_t length, bool is_directory, const cbm_ignore_checked_control_t *control,
cbm_ignore_decision_t *decision, cbm_ignore_checked_error_t *error) {
if (decision) {
*decision = CBM_IGNORE_UNAVAILABLE;
}
gi_error(error, CBM_IGNORE_CHECKED_OK, CBM_IGNORE_CAP_NONE, SIZE_MAX);
if (!owner || !program || !path || length == SIZE_MAX || !control || !control->deadline_ms ||
!decision || program->owner != owner) {
gi_error(error, CBM_IGNORE_CHECKED_INVALID, CBM_IGNORE_CAP_NONE, SIZE_MAX);
return CBM_IGNORE_CHECKED_INVALID;
}
gi_guard_t guard;
if (!gi_begin(owner, control, &guard) || !gi_bytes(&guard, length + 1) ||
!gi_path_valid(&guard, path, length)) {
return gi_finish(owner, error);
}
int result = gi_core_match(program->rows, program->count, path, is_directory, &guard);
if (gi_poll(&guard)) {
*decision = (cbm_ignore_decision_t)result;
}
return gi_finish(owner, error);
}
bool cbm_ignore_checked_usage(const cbm_ignore_checked_t *owner, cbm_ignore_checked_usage_t *out) {
if (!out) {
return false;
}
memset(out, 0, sizeof(*out));
if (!owner) {
return false;
}
*out = owner->usage;
return true;
}
void cbm_ignore_checked_free(cbm_ignore_checked_t *owner) {
if (owner) {
/* The owner itself lives in the arena being destroyed. */
CBMArena arena = owner->arena;
cbm_arena_destroy(&arena);
}
}
#if defined(CBM_ENABLE_TEST_SEAMS) && CBM_ENABLE_TEST_SEAMS
cbm_ignore_checked_status_t cbm_ignore_checked_open_oom_for_tests(
const cbm_ignore_checked_limits_t *limits, cbm_ignore_checked_t **out,
cbm_ignore_checked_error_t *error) {
return gi_open(limits, out, error, true);
}
#endif
+137
View File
@@ -0,0 +1,137 @@
/* Checked in-memory ignore parsing and matching; no filesystem/MCP dependency. */
#ifndef CBM_GITIGNORE_CHECKED_H
#define CBM_GITIGNORE_CHECKED_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct cbm_ignore_checked cbm_ignore_checked_t;
typedef struct cbm_ignore_program cbm_ignore_program_t;
typedef enum {
CBM_IGNORE_CHECKED_OK = 0,
CBM_IGNORE_CHECKED_INVALID,
CBM_IGNORE_CHECKED_UNSUPPORTED,
CBM_IGNORE_CHECKED_LIMIT,
CBM_IGNORE_CHECKED_OOM,
CBM_IGNORE_CHECKED_CANCELLED,
CBM_IGNORE_CHECKED_DEADLINE
} cbm_ignore_checked_status_t;
typedef enum {
CBM_IGNORE_CAP_NONE = 0,
CBM_IGNORE_CAP_BYTES,
CBM_IGNORE_CAP_PATTERNS,
CBM_IGNORE_CAP_WORK,
CBM_IGNORE_CAP_ARENA,
CBM_IGNORE_CAP_DEPTH,
CBM_IGNORE_CAP_ATTEMPT,
CBM_IGNORE_CAP_REPRESENTATION
} cbm_ignore_cap_t;
typedef enum {
CBM_IGNORE_REINCLUDED = -1,
CBM_IGNORE_NO_OPINION = 0,
CBM_IGNORE_IGNORED = 1,
CBM_IGNORE_UNAVAILABLE = 2 /* every failure clears the decision to this */
} cbm_ignore_decision_t;
enum { CBM_IGNORE_CHECKED_DEPTH_MAX = 32 };
typedef struct {
uint64_t max_bytes; /* aggregate parse lengths and match path lengths + NUL */
uint64_t max_work; /* aggregate charged work, including parsing */
size_t max_patterns; /* cumulative reservations; positive, <= INT_MAX */
size_t max_arena_bytes; /* cumulative logical requests, not resident memory */
unsigned max_depth; /* 1..CBM_IGNORE_CHECKED_DEPTH_MAX active glob entries */
} cbm_ignore_checked_limits_t;
typedef struct {
uint64_t deadline_ms; /* positive absolute cbm_now_ms deadline */
bool (*cancelled)(void *context); /* optional prompt, pure, non-reentrant */
void *context; /* borrowed for this synchronous call only */
} cbm_ignore_checked_control_t;
typedef struct {
cbm_ignore_checked_status_t status;
cbm_ignore_cap_t cap; /* LIMIT only; otherwise NONE */
size_t byte_offset; /* input offset when known, otherwise SIZE_MAX */
char diagnostic[128]; /* bounded owned text, never input contents */
} cbm_ignore_checked_error_t;
typedef struct {
uint64_t bytes_reserved;
uint64_t work_used;
size_t patterns_reserved;
size_t arena_requested_bytes;
cbm_ignore_checked_status_t terminal_status;
} cbm_ignore_checked_usage_t;
typedef struct {
const cbm_ignore_program_t *program; /* borrowed until owner free */
size_t pattern_count; /* zero is successful empty/comment-only normalization */
} cbm_ignore_checked_parse_result_t;
/* All caps positive; copies limits. Initializes optional error and clears *out.
* No callbacks/IO. Failure publishes no owner. Uses a CBMArena, including owner.
*/
cbm_ignore_checked_status_t cbm_ignore_checked_open(const cbm_ignore_checked_limits_t *limits,
cbm_ignore_checked_t **out,
cbm_ignore_checked_error_t *error);
/* Exact span: NULL permitted only for length==0; terminator not required.
* Embedded NUL => UNSUPPORTED, never truncation. Result is required/cleared.
* Copies normalized patterns; caller may discard bytes after return.
* Every started failure is terminal for owner; earlier programs remain allocated
* but no further parse/match can succeed. No partial program is published.
*/
cbm_ignore_checked_status_t cbm_ignore_checked_parse(cbm_ignore_checked_t *owner, const void *bytes,
size_t length,
const cbm_ignore_checked_control_t *control,
cbm_ignore_checked_parse_result_t *out,
cbm_ignore_checked_error_t *error);
/* program must belong to this live owner. path is readable for length+1 bytes,
* with path[length]==0 and no earlier NUL. Empty path is permitted. No decoding
* or path normalization. Inputs borrowed for this call; no match allocation.
* Required decision is initialized to UNAVAILABLE and set only on full success.
*/
cbm_ignore_checked_status_t cbm_ignore_checked_match(
cbm_ignore_checked_t *owner, const cbm_ignore_program_t *program, const char *path,
size_t length, bool is_directory, const cbm_ignore_checked_control_t *control,
cbm_ignore_decision_t *decision, cbm_ignore_checked_error_t *error);
/* Read-only; clears output, false for NULL owner/output. Does not consume quotas,
* poll or clear a terminal failure. No operation may overlap any other call.
*/
bool cbm_ignore_checked_usage(const cbm_ignore_checked_t *owner, cbm_ignore_checked_usage_t *out);
/* NULL-safe; releases all programs/arena at once. No callback/IO or cancellation. */
void cbm_ignore_checked_free(cbm_ignore_checked_t *owner);
/* Test-only per-call allocation failure seams.
* No persistent/global fault, raw allocator replacement or production option.
*/
#if defined(CBM_ENABLE_TEST_SEAMS) && CBM_ENABLE_TEST_SEAMS
typedef enum {
CBM_IGNORE_PARSE_FAULT_NONE = 0,
CBM_IGNORE_PARSE_FAULT_PROGRAM,
CBM_IGNORE_PARSE_FAULT_PATTERN
} cbm_ignore_parse_fault_t;
/* Simulate NULL at the owner's first arena allocation, after normal quota checks. */
cbm_ignore_checked_status_t cbm_ignore_checked_open_oom_for_tests(
const cbm_ignore_checked_limits_t *limits, cbm_ignore_checked_t **out,
cbm_ignore_checked_error_t *error);
/* PROGRAM: program/header+row-storage allocation. PATTERN: first effective
* pattern-string allocation. Unreached boundary has no effect. Same normal
* implementation/error path; no extra allocations or changed matching logic.
*/
cbm_ignore_checked_status_t cbm_ignore_checked_parse_fault_for_tests(
cbm_ignore_checked_t *owner, const void *bytes, size_t length,
const cbm_ignore_checked_control_t *control, cbm_ignore_parse_fault_t fault,
cbm_ignore_checked_parse_result_t *out, cbm_ignore_checked_error_t *error);
#endif
#endif
+143
View File
@@ -0,0 +1,143 @@
#include "discover/gitignore_internal.h"
#include <limits.h>
typedef struct {
gi_guard_t *guard;
const char *bytes;
size_t length;
cbm_ignore_program_t *program;
size_t count;
unsigned fault;
} gi_parse_t;
static bool gi_line(gi_parse_t *parse, size_t *cursor, gi_view_t *view) {
size_t start = *cursor;
while (*cursor < parse->length) {
parse->guard->offset = *cursor;
/* Both exact-byte validation and line splitting visit this byte. */
if (!gi_work(parse->guard, 2)) {
return false;
}
char byte = parse->bytes[*cursor];
if (!byte) {
return gi_fail(parse->guard, CBM_IGNORE_CHECKED_UNSUPPORTED, CBM_IGNORE_CAP_NONE);
}
if (byte == '\n') {
break;
}
(*cursor)++;
if (*cursor - start > INT_MAX) {
return gi_fail(parse->guard, CBM_IGNORE_CHECKED_LIMIT, CBM_IGNORE_CAP_REPRESENTATION);
}
}
size_t length = *cursor - start;
if (*cursor < parse->length) {
(*cursor)++;
}
parse->guard->offset = start;
return gi_normalize(parse->bytes + start, length, view, parse->guard);
}
static bool gi_store_pattern(gi_parse_t *parse, const gi_view_t *view) {
bool fault = parse->fault == GI_FAULT_PATTERN && parse->count == 0;
char *text = gi_allocate(parse->guard, view->length + 1, fault);
if (!text) {
return false;
}
for (size_t i = 0; i <= view->length; i++) {
if (!gi_work(parse->guard, 1)) {
return false;
}
text[i] = i == view->length ? '\0' : view->text[i];
}
gi_pattern_t row = {.pattern = text,
.negated = view->negated,
.dir_only = view->dir_only,
.rooted = view->rooted};
parse->program->rows[parse->count++] = row;
return true;
}
static bool gi_parse_pass(gi_parse_t *parse) {
size_t cursor = 0;
while (cursor < parse->length) {
gi_view_t view;
if (!gi_line(parse, &cursor, &view)) {
return false;
}
if (!view.length) {
continue;
}
if (parse->program) {
if (!gi_store_pattern(parse, &view)) {
return false;
}
} else {
if (!gi_pattern_reserve(parse->guard)) {
return false;
}
parse->count++;
}
}
return true;
}
static bool gi_parse_storage(gi_parse_t *parse) {
size_t count = parse->count;
if (count > (SIZE_MAX - sizeof(cbm_ignore_program_t)) / sizeof(gi_pattern_t)) {
return gi_fail(parse->guard, CBM_IGNORE_CHECKED_LIMIT, CBM_IGNORE_CAP_ARENA);
}
size_t bytes = sizeof(cbm_ignore_program_t) + count * sizeof(gi_pattern_t);
parse->program = gi_allocate(parse->guard, bytes, parse->fault == GI_FAULT_PROGRAM);
if (!parse->program || !gi_zero(parse->guard, parse->program, bytes)) {
return false;
}
parse->program->owner = parse->guard->owner;
parse->program->count = count;
parse->count = 0;
return true;
}
static cbm_ignore_checked_status_t gi_parse(cbm_ignore_checked_t *owner, const void *bytes,
size_t length,
const cbm_ignore_checked_control_t *control,
unsigned fault, cbm_ignore_checked_parse_result_t *out,
cbm_ignore_checked_error_t *error) {
if (out) {
*out = (cbm_ignore_checked_parse_result_t){0};
}
gi_error(error, CBM_IGNORE_CHECKED_OK, CBM_IGNORE_CAP_NONE, SIZE_MAX);
if (!owner || (!bytes && length) || !control || !control->deadline_ms || !out ||
fault > GI_FAULT_PATTERN) {
gi_error(error, CBM_IGNORE_CHECKED_INVALID, CBM_IGNORE_CAP_NONE, SIZE_MAX);
return CBM_IGNORE_CHECKED_INVALID;
}
gi_guard_t guard;
if (!gi_begin(owner, control, &guard) || !gi_bytes(&guard, length)) {
return gi_finish(owner, error);
}
gi_parse_t parse = {.guard = &guard, .bytes = bytes, .length = length, .fault = fault};
if (gi_parse_pass(&parse) && gi_parse_storage(&parse) && gi_parse_pass(&parse) &&
gi_poll(&guard)) {
out->program = parse.program;
out->pattern_count = parse.count;
}
return gi_finish(owner, error);
}
cbm_ignore_checked_status_t cbm_ignore_checked_parse(cbm_ignore_checked_t *owner, const void *bytes,
size_t length,
const cbm_ignore_checked_control_t *control,
cbm_ignore_checked_parse_result_t *out,
cbm_ignore_checked_error_t *error) {
return gi_parse(owner, bytes, length, control, GI_FAULT_NONE, out, error);
}
#if defined(CBM_ENABLE_TEST_SEAMS) && CBM_ENABLE_TEST_SEAMS
cbm_ignore_checked_status_t cbm_ignore_checked_parse_fault_for_tests(
cbm_ignore_checked_t *owner, const void *bytes, size_t length,
const cbm_ignore_checked_control_t *control, cbm_ignore_parse_fault_t fault,
cbm_ignore_checked_parse_result_t *out, cbm_ignore_checked_error_t *error) {
return gi_parse(owner, bytes, length, control, (unsigned)fault, out, error);
}
#endif
+243
View File
@@ -0,0 +1,243 @@
#include "discover/gitignore_internal.h"
/* These recursive functions and annotations move unchanged in identity from
* gitignore.c. A NULL guard preserves the legacy budget/fallback behavior. */
static bool glob_match(const char *pat, const char *str, int *budget,
gi_guard_t *guard); // NOLINT(misc-no-recursion)
static bool glob_match_doublestar_slash(const char *pat, const char *str, int *budget,
gi_guard_t *guard) { // NOLINT(misc-no-recursion)
if (glob_match(pat, str, budget, guard)) {
return true;
}
for (const char *s = str;; s++) {
if (!gi_work(guard, 1) || !*s) {
return false;
}
if (*s == '/' && glob_match(pat, s + 1, budget, guard)) {
return true;
}
}
}
static bool glob_match_doublestar_any(const char *pat, const char *str, int *budget,
gi_guard_t *guard) { // NOLINT(misc-no-recursion)
for (const char *s = str;; s++) {
if (glob_match(pat, s, budget, guard)) {
return true;
}
if (!gi_work(guard, 1) || !*s) {
return false;
}
}
}
static bool glob_match_star(const char *pat, const char *str, int *budget,
gi_guard_t *guard) { // NOLINT(misc-no-recursion)
for (const char *s = str;; s++) {
if (glob_match(pat, s, budget, guard)) {
return true;
}
if (!gi_work(guard, 1) || !*s || *s == '/') {
return false;
}
}
}
static bool glob_match_charclass(const char *pat, char ch, const char **pat_out,
gi_guard_t *guard) {
bool negate_class = false;
if (*pat == '!' || *pat == '^') {
negate_class = true;
pat++;
}
bool matched = false;
char prev = 0;
while (*pat && *pat != ']') {
if (!gi_work(guard, 1)) {
return false;
}
if (*pat == '-' && prev && pat[1] && pat[1] != ']') {
pat++;
if (!gi_work(guard, 1)) {
return false;
}
if (ch >= prev && ch <= *pat) {
matched = true;
}
prev = *pat;
pat++;
} else {
if (ch == *pat) {
matched = true;
}
prev = *pat;
pat++;
}
}
if (*pat == ']') {
pat++;
}
*pat_out = pat;
return negate_class ? !matched : matched;
}
static bool glob_match_doublestar(const char *pat, const char *str, int *budget,
gi_guard_t *guard) { // NOLINT(misc-no-recursion)
if (pat[2] == '/') {
return glob_match_doublestar_slash(pat + 3, str, budget, guard);
}
if (pat[2] == '\0') {
return true;
}
return glob_match_doublestar_any(pat + 2, str, budget, guard);
}
static bool glob_match(const char *pat, const char *str, int *budget,
gi_guard_t *guard) { // NOLINT(misc-no-recursion)
if (!gi_work(guard, 1)) {
return false;
}
if (--*budget <= 0) {
return gi_fail(guard, CBM_IGNORE_CHECKED_LIMIT, CBM_IGNORE_CAP_ATTEMPT);
}
if (guard) {
if (guard->depth == guard->owner->limits.max_depth) {
return gi_fail(guard, CBM_IGNORE_CHECKED_LIMIT, CBM_IGNORE_CAP_DEPTH);
}
guard->depth++;
}
bool matched = false;
while (*pat && *str) {
if (!gi_work(guard, 1)) {
goto done;
}
if (pat[0] == '*' && pat[1] == '*') {
matched = glob_match_doublestar(pat, str, budget, guard);
goto done;
}
if (*pat == '*') {
matched = glob_match_star(pat + 1, str, budget, guard);
goto done;
}
if (*pat == '?') {
if (*str == '/') {
goto done;
}
pat++;
str++;
continue;
}
if (*pat == '[') {
const char *next = NULL;
if (*str == '/' || !glob_match_charclass(pat + 1, *str, &next, guard)) {
goto done;
}
pat = next;
str++;
continue;
}
if (*pat != *str) {
goto done;
}
pat++;
str++;
}
while (*pat == '*') {
if (!gi_work(guard, 1)) {
goto done;
}
pat++;
}
matched = *pat == '\0' && *str == '\0';
done:
if (guard) {
guard->depth--;
}
return matched;
}
static bool glob_match_bounded(const char *pat, const char *str, gi_guard_t *guard) {
int budget = GI_MATCH_MAX_STEPS;
return gi_work(guard, 1) && glob_match(pat, str, &budget, guard);
}
static const char *gi_slash(const char *path, gi_guard_t *guard) {
for (;;) {
if (!gi_work(guard, 1)) {
return NULL;
}
if (*path == '/') {
return path;
}
if (!*path) {
return NULL;
}
path++;
}
}
static const char *gi_basename(const char *path, gi_guard_t *guard) {
const char *basename = path;
for (;;) {
if (!gi_work(guard, 1)) {
return NULL;
}
if (!*path) {
return basename;
}
if (*path == '/') {
basename = path + 1;
}
path++;
}
}
static bool match_unrooted(const char *pattern, const char *path, const char *basename,
gi_guard_t *guard) {
if (glob_match_bounded(pattern, basename, guard)) {
return true;
}
if (!gi_slash(path, guard)) {
return false;
}
const char *suffix = path;
while (*suffix) {
if (glob_match_bounded(pattern, suffix, guard)) {
return true;
}
const char *next = gi_slash(suffix, guard);
if (!next) {
break;
}
suffix = next + 1;
}
return false;
}
int gi_core_match(const gi_pattern_t *rows, size_t count, const char *path, bool is_directory,
gi_guard_t *guard) {
const char *basename = gi_basename(path, guard);
if (!basename) {
return 0;
}
int matched = 0;
for (size_t i = 0; i < count; i++) {
if (!gi_work(guard, 1)) {
return 0;
}
const gi_pattern_t *pattern = &rows[i];
if (pattern->dir_only && !is_directory) {
continue;
}
bool this_match = pattern->rooted ? glob_match_bounded(pattern->pattern, path, guard)
: match_unrooted(pattern->pattern, path, basename, guard);
if (!gi_active(guard)) {
return 0;
}
if (this_match) {
matched = pattern->negated ? -1 : 1;
}
}
return matched;
}
+209
View File
@@ -0,0 +1,209 @@
#include "discover/gitignore_internal.h"
#include "foundation/platform.h"
#include <string.h>
void gi_error(cbm_ignore_checked_error_t *error, cbm_ignore_checked_status_t status,
cbm_ignore_cap_t cap, size_t offset) {
if (!error) {
return;
}
memset(error, 0, sizeof(*error));
error->status = status;
error->cap = cap;
error->byte_offset = offset;
if (status != CBM_IGNORE_CHECKED_OK) {
static const char message[] = "checked ignore operation failed";
memcpy(error->diagnostic, message, sizeof(message));
}
}
bool gi_active(const gi_guard_t *guard) {
return !guard || guard->owner->usage.terminal_status == CBM_IGNORE_CHECKED_OK;
}
bool gi_fail(gi_guard_t *guard, cbm_ignore_checked_status_t status, cbm_ignore_cap_t cap) {
if (guard && gi_active(guard)) {
guard->owner->usage.terminal_status = status;
gi_error(&guard->owner->error, status, cap, guard->offset);
}
return false;
}
bool gi_poll(gi_guard_t *guard) {
if (!gi_active(guard)) {
return false;
}
if (!guard) {
return true;
}
const cbm_ignore_checked_control_t *control = guard->control;
if (control->cancelled && control->cancelled(control->context)) {
return gi_fail(guard, CBM_IGNORE_CHECKED_CANCELLED, CBM_IGNORE_CAP_NONE);
}
if (cbm_now_ms() >= control->deadline_ms) {
return gi_fail(guard, CBM_IGNORE_CHECKED_DEADLINE, CBM_IGNORE_CAP_NONE);
}
guard->ticks = 0;
return true;
}
bool gi_work(gi_guard_t *guard, size_t units) {
if (!gi_active(guard)) {
return false;
}
if (!guard) {
return true;
}
cbm_ignore_checked_t *owner = guard->owner;
if (units > owner->limits.max_work - owner->usage.work_used) {
return gi_fail(guard, CBM_IGNORE_CHECKED_LIMIT, CBM_IGNORE_CAP_WORK);
}
/* All callers submit a constant-size event or a bounded memory chunk. */
while (units) {
size_t part = GI_POLL_UNITS - guard->ticks;
if (part > units) {
part = units;
}
owner->usage.work_used += part;
guard->ticks += (unsigned)part;
units -= part;
if (guard->ticks == GI_POLL_UNITS && !gi_poll(guard)) {
return false;
}
}
return true;
}
bool gi_bytes(gi_guard_t *guard, size_t bytes) {
cbm_ignore_checked_t *owner = guard->owner;
if (bytes > owner->limits.max_bytes - owner->usage.bytes_reserved) {
return gi_fail(guard, CBM_IGNORE_CHECKED_LIMIT, CBM_IGNORE_CAP_BYTES);
}
owner->usage.bytes_reserved += bytes;
return true;
}
bool gi_pattern_reserve(gi_guard_t *guard) {
cbm_ignore_checked_t *owner = guard->owner;
if (!gi_work(guard, 1)) {
return false;
}
if (owner->usage.patterns_reserved == owner->limits.max_patterns) {
return gi_fail(guard, CBM_IGNORE_CHECKED_LIMIT, CBM_IGNORE_CAP_PATTERNS);
}
owner->usage.patterns_reserved++;
return true;
}
bool gi_begin(cbm_ignore_checked_t *owner, const cbm_ignore_checked_control_t *control,
gi_guard_t *guard) {
*guard = (gi_guard_t){.owner = owner, .control = control, .offset = SIZE_MAX};
return gi_poll(guard) && gi_work(guard, 1);
}
cbm_ignore_checked_status_t gi_finish(const cbm_ignore_checked_t *owner,
cbm_ignore_checked_error_t *error) {
if (error) {
*error = owner->error;
}
return owner->usage.terminal_status;
}
void *gi_allocate(gi_guard_t *guard, size_t bytes, bool simulate_oom) {
cbm_ignore_checked_t *owner = guard->owner;
CBMArena *arena = &owner->arena;
if (!gi_poll(guard)) {
return NULL;
}
if (bytes > owner->limits.max_arena_bytes - owner->usage.arena_requested_bytes ||
bytes > (SIZE_MAX / 2) - GI_ARENA_ALIGN) {
gi_fail(guard, CBM_IGNORE_CHECKED_LIMIT, CBM_IGNORE_CAP_ARENA);
return NULL;
}
size_t aligned = (bytes + GI_ARENA_ALIGN) & ~(size_t)GI_ARENA_ALIGN;
if (aligned > SIZE_MAX - arena->used || aligned > SIZE_MAX - arena->total_alloc) {
gi_fail(guard, CBM_IGNORE_CHECKED_LIMIT, CBM_IGNORE_CAP_ARENA);
return NULL;
}
owner->usage.arena_requested_bytes += bytes;
/* Keep the arena's doubling arithmetic representable; no allocator changes. */
if (arena->grow_size > SIZE_MAX / 2) {
arena->grow_size = CBM_ARENA_DEFAULT_BLOCK_SIZE;
}
void *memory = simulate_oom ? NULL : cbm_arena_alloc(arena, bytes);
if (!memory) {
gi_fail(guard, CBM_IGNORE_CHECKED_OOM, CBM_IGNORE_CAP_NONE);
}
return memory;
}
bool gi_zero(gi_guard_t *guard, void *storage, size_t bytes) {
unsigned char *output = storage;
enum { ZERO_CHUNK = 256 };
while (bytes) {
size_t chunk = bytes < ZERO_CHUNK ? bytes : ZERO_CHUNK;
if (!gi_work(guard, chunk)) {
return false;
}
memset(output, 0, chunk);
output += chunk;
bytes -= chunk;
}
return true;
}
static bool gi_trim(const char *line, size_t *length, gi_guard_t *guard) {
while (*length) {
if (!gi_work(guard, 1)) {
return false;
}
char last = line[*length - 1];
if (last != ' ' && last != '\t' && last != '\r') {
break;
}
(*length)--;
}
return true;
}
bool gi_normalize(const char *line, size_t length, gi_view_t *view, gi_guard_t *guard) {
*view = (gi_view_t){0};
if (!length || !gi_work(guard, 1) || line[0] == '#') {
return gi_active(guard);
}
if (!gi_trim(line, &length, guard) || !length) {
return gi_active(guard);
}
if (!gi_work(guard, 1)) {
return false;
}
if (*line == '!') {
view->negated = true;
line++;
length--;
}
if (!length || !gi_work(guard, 1)) {
return gi_active(guard);
}
if (line[length - 1] == '/') {
view->dir_only = true;
length--;
}
if (!length || !gi_work(guard, 1)) {
return gi_active(guard);
}
if (*line == '/') {
view->rooted = true;
line++;
length--;
}
for (size_t i = 0; !view->rooted && i < length; i++) {
if (!gi_work(guard, 1)) {
return false;
}
view->rooted = line[i] == '/';
}
view->text = line;
view->length = length;
return true;
}
+62
View File
@@ -0,0 +1,62 @@
#ifndef CBM_GITIGNORE_INTERNAL_H
#define CBM_GITIGNORE_INTERNAL_H
#include "discover/gitignore_checked.h"
#include "foundation/arena.h"
typedef struct {
char *pattern;
bool negated;
bool dir_only;
bool rooted;
} gi_pattern_t;
typedef struct {
const char *text;
size_t length;
bool negated;
bool dir_only;
bool rooted;
} gi_view_t;
struct cbm_ignore_checked {
CBMArena arena;
cbm_ignore_checked_limits_t limits;
cbm_ignore_checked_usage_t usage;
cbm_ignore_checked_error_t error;
};
struct cbm_ignore_program {
const cbm_ignore_checked_t *owner;
size_t count;
gi_pattern_t rows[];
};
typedef struct {
cbm_ignore_checked_t *owner;
const cbm_ignore_checked_control_t *control;
size_t offset;
unsigned ticks;
unsigned depth;
} gi_guard_t;
enum { GI_POLL_UNITS = 4096, GI_MATCH_MAX_STEPS = 20000, GI_ARENA_ALIGN = 7 };
enum { GI_FAULT_NONE, GI_FAULT_PROGRAM, GI_FAULT_PATTERN };
void gi_error(cbm_ignore_checked_error_t *error, cbm_ignore_checked_status_t status,
cbm_ignore_cap_t cap, size_t offset);
bool gi_fail(gi_guard_t *guard, cbm_ignore_checked_status_t status, cbm_ignore_cap_t cap);
bool gi_active(const gi_guard_t *guard);
bool gi_poll(gi_guard_t *guard);
bool gi_work(gi_guard_t *guard, size_t units);
bool gi_bytes(gi_guard_t *guard, size_t bytes);
bool gi_pattern_reserve(gi_guard_t *guard);
bool gi_begin(cbm_ignore_checked_t *owner, const cbm_ignore_checked_control_t *control,
gi_guard_t *guard);
cbm_ignore_checked_status_t gi_finish(const cbm_ignore_checked_t *owner,
cbm_ignore_checked_error_t *error);
void *gi_allocate(gi_guard_t *guard, size_t bytes, bool simulate_oom);
bool gi_zero(gi_guard_t *guard, void *storage, size_t bytes);
bool gi_normalize(const char *line, size_t length, gi_view_t *view, gi_guard_t *guard);
int gi_core_match(const gi_pattern_t *rows, size_t count, const char *path, bool is_directory,
gi_guard_t *guard);
#endif
+137
View File
@@ -0,0 +1,137 @@
#include "discover/inventory_internal.h"
#include "discover/inventory_limits_internal.h"
#include <limits.h>
#include <string.h>
bool cif_limits_valid(const cbm_inventory_limits_t *l) {
return l && l->max_files && l->max_files <= INT_MAX && l->max_directories &&
l->max_directories <= INT_MAX && l->max_arena_bytes && l->max_ignore_arena_bytes &&
l->max_ignore_arena_bytes <= l->max_arena_bytes && l->max_control_file_bytes &&
l->max_control_total_bytes && l->max_ignore_patterns &&
l->max_ignore_patterns <= INT_MAX && l->max_probe_prefix_bytes && l->max_ignore_work &&
l->max_verified_file_reads && l->max_verified_content_bytes;
}
static bool cif_open_ignore(cif_context *c) {
cbm_inventory_limits_t *l = &c->owner->limits;
cbm_ignore_checked_limits_t limits = {.max_bytes =
l->max_control_total_bytes + l->max_ignore_work,
.max_work = l->max_ignore_work,
.max_patterns = l->max_ignore_patterns,
.max_arena_bytes = l->max_ignore_arena_bytes,
.max_depth = CBM_IGNORE_CHECKED_DEPTH_MAX};
c->file_index = SIZE_MAX;
if (!cif_poll(c)) {
return false;
}
cbm_ignore_checked_status_t status = cbm_ignore_checked_open(&limits, &c->owner->ignore, NULL);
return cif_checked(c, status) && cif_poll(c);
}
static bool cif_publish(cif_context *c) {
cbm_inventory_filter_t *o = c->owner;
cbm_ignore_checked_usage_t checked;
c->file_index = SIZE_MAX;
if (!cbm_ignore_checked_usage(o->ignore, &checked) ||
checked.terminal_status != CBM_IGNORE_CHECKED_OK) {
return cif_fail(c, CBM_INVENTORY_STATE);
}
o->usage.ignore_bytes_reserved = checked.bytes_reserved;
o->usage.ignore_work_used = checked.work_used;
o->usage.ignore_patterns_reserved = checked.patterns_reserved;
o->usage.ignore_arena_requested_bytes = checked.arena_requested_bytes;
size_t d = o->usage.non_ignore_arena_requested_bytes;
size_t i = o->limits.max_ignore_arena_bytes;
if (checked.arena_requested_bytes > i || d > o->limits.max_arena_bytes - i) {
return cif_fail(c, CBM_INVENTORY_STATE);
}
o->usage.arena_requested_bytes = d + checked.arena_requested_bytes;
o->usage.arena_budget_used_bytes = d + i;
o->view.files = o->files;
o->view.rows = o->rows;
o->view.controls = o->controls;
o->view.excluded_directories = o->exclusions;
return cif_poll(c);
}
cbm_inventory_status_t cbm_inventory_filter_prepare(const cbm_inventory_source_t *source,
const cbm_inventory_limits_t *limits,
const cbm_inventory_control_t *control,
cbm_inventory_filter_t **out,
cbm_inventory_error_t *error) {
cbm_inventory_error_t initial = {.status = CBM_INVENTORY_OK, .file_index = SIZE_MAX};
if (out) {
*out = NULL;
}
if (error) {
*error = initial;
}
cbm_inventory_filter_t bootstrap = {0};
cif_context c = {.owner = &bootstrap,
.source = source,
.control = control,
.error = initial,
.file_index = SIZE_MAX};
if (!out || !source || !cif_limits_valid(limits) || !control || !control->deadline_ms ||
!source->native_root || !source->read || (source->file_count && !source->files)) {
cif_fail(&c, CBM_INVENTORY_INVALID);
} else if (source->file_count > limits->max_files || source->file_count > INT_MAX ||
limits->max_control_total_bytes > UINT64_MAX - limits->max_ignore_work) {
cif_fail(&c, CBM_INVENTORY_LIMIT);
} else if (cif_poll(&c)) {
bootstrap.limits = *limits;
bootstrap.deadline_ms = control->deadline_ms;
cbm_arena_init_lazy(&bootstrap.arena, CBM_ARENA_DEFAULT_BLOCK_SIZE);
cbm_inventory_filter_t *owner = cif_alloc(&c, 1, sizeof(*owner));
if (owner && cif_copy(&c, owner, &bootstrap, sizeof(*owner))) {
c.owner = owner;
if (cif_metadata(&c) && cif_directories(&c) && cif_open_ignore(&c) && cif_filter(&c) &&
cif_publish(&c)) {
*out = owner;
return CBM_INVENTORY_OK;
}
}
}
cbm_inventory_filter_free(c.owner);
if (error) {
*error = c.error;
}
return c.error.status;
}
const cbm_inventory_filter_view_t *cbm_inventory_filter_view(const cbm_inventory_filter_t *owner) {
return owner ? &owner->view : NULL;
}
bool cbm_inventory_filter_usage(const cbm_inventory_filter_t *owner,
cbm_inventory_filter_usage_t *out) {
if (!out) {
return false;
}
memset(out, 0, sizeof(*out));
if (!owner) {
return false;
}
*out = owner->usage;
return true;
}
bool cbm_inventory_filter_limits(const cbm_inventory_filter_t *owner, cbm_inventory_limits_t *out) {
if (!out) {
return false;
}
memset(out, 0, sizeof(*out));
if (!owner) {
return false;
}
*out = owner->limits;
return true;
}
void cbm_inventory_filter_free(cbm_inventory_filter_t *owner) {
if (owner) {
cbm_ignore_checked_free(owner->ignore);
CBMArena arena = owner->arena;
cbm_arena_destroy(&arena);
}
}
+177
View File
@@ -0,0 +1,177 @@
#include "discover/inventory_internal.h"
#include "foundation/sha256.h"
#include <string.h>
static bool cif_control_index(cif_context *c, size_t directory, const char *name,
size_t name_length, size_t *index) {
cbm_inventory_path_t path = c->owner->directories[directory].path;
size_t separator = path.length ? 1 : 0;
*index = SIZE_MAX;
/* A longer candidate cannot occur in this fully validated inventory. */
if (path.length > CIF_PATH_MAX - name_length - separator) {
return true;
}
unsigned char buffer[CIF_PATH_MAX + 1];
if (!cif_copy(c, buffer, path.data, path.length) || !cif_bytes(c, 1)) {
return false;
}
if (separator) {
buffer[path.length] = '/';
}
if (!cif_copy(c, buffer + path.length + separator, name, name_length + 1)) {
return false;
}
cbm_inventory_path_t candidate = {buffer, path.length + separator + name_length};
if (!cif_find_file(c, candidate, index)) {
return false;
}
if (*index == SIZE_MAX) {
size_t collision = SIZE_MAX;
if (!cif_find_directory(c, candidate, &collision)) {
return false;
}
if (collision != SIZE_MAX) {
return cif_fail(c, CBM_INVENTORY_UNSUPPORTED);
}
}
return true;
}
static bool cif_reserve_control(cif_context *c, uint64_t length) {
cbm_inventory_filter_t *o = c->owner;
cbm_inventory_limits_t *l = &o->limits;
cbm_inventory_filter_usage_t *u = &o->usage;
if ((uint64_t)(size_t)length != length || length > l->max_control_file_bytes ||
u->verified_file_reads_reserved >= l->max_verified_file_reads ||
length > l->max_verified_content_bytes - u->verified_content_bytes_reserved ||
length > l->max_control_total_bytes - u->control_bytes_reserved) {
return cif_fail(c, CBM_INVENTORY_LIMIT);
}
u->verified_file_reads_reserved++;
u->verified_content_bytes_reserved += length;
u->control_bytes_reserved += length;
return true;
}
static bool cif_read_control(cif_context *c, size_t index, size_t length) {
cbm_inventory_filter_t *o = c->owner;
if (length > o->scratch_capacity) {
unsigned char *buffer = cif_alloc(c, length, 1);
if (!buffer) {
return false;
}
o->scratch = buffer;
o->scratch_capacity = length;
}
if (!cif_poll(c)) {
return false;
}
size_t copied = 0;
cbm_inventory_error_t error = {.status = CBM_INVENTORY_OK, .file_index = SIZE_MAX};
cbm_inventory_status_t status =
c->source->read(c->source->read_context, index, length ? o->scratch : NULL, length, &copied,
c->control, &error);
c->error.cleanup_required |= error.cleanup_required;
if ((unsigned)status > (unsigned)CBM_INVENTORY_DEADLINE || error.status != status) {
return cif_fail(c, CBM_INVENTORY_INVALID);
}
if (status != CBM_INVENTORY_OK) {
return cif_fail(c, copied ? CBM_INVENTORY_INVALID : status);
}
if (copied != length || error.cleanup_required) {
return cif_fail(c, CBM_INVENTORY_INVALID);
}
return cif_poll(c);
}
static bool cif_hash_control(cif_context *c, size_t index, size_t length) {
cbm_sha256_ctx hash;
unsigned char digest[CBM_SHA256_DIGEST_LEN];
if (!cif_poll(c)) {
return false;
}
cbm_sha256_init(&hash);
for (size_t at = 0; at < length;) {
size_t n = length - at < 512 ? length - at : 512;
if (!cif_poll(c) || !cif_bytes(c, n)) {
return false;
}
cbm_sha256_update(&hash, c->owner->scratch + at, n);
if (!cif_poll(c)) {
return false;
}
at += n;
}
cbm_sha256_final(&hash, digest);
if (!cif_poll(c)) {
return false;
}
if (memcmp(digest, c->owner->files[index].content_sha256, sizeof(digest))) {
return cif_fail(c, CBM_INVENTORY_CHANGED);
}
return true;
}
static bool cif_load_control(cif_context *c, size_t directory, cbm_inventory_control_kind_t kind,
cbm_inventory_control_row_t *row,
const cbm_ignore_program_t **program) {
cbm_inventory_filter_t *o = c->owner;
c->file_index = SIZE_MAX;
if (!cif_event(c) || !cif_bytes(c, 256)) {
return false;
}
*row = (cbm_inventory_control_row_t){
.kind = kind, .directory = o->directories[directory].path, .file_index = SIZE_MAX};
const char *name = kind == CBM_INVENTORY_CONTROL_CBMIGNORE ? ".cbmignore" : ".gitignore";
size_t index = SIZE_MAX;
if (!cif_control_index(c, directory, name, 10, &index)) {
return false;
}
if (index == SIZE_MAX) {
return true;
}
c->file_index = index;
uint64_t size = o->files[index].content_length;
if (!cif_reserve_control(c, size)) {
return false;
}
size_t length = (size_t)size;
if (!cif_read_control(c, index, length) || !cif_hash_control(c, index, length) ||
!cif_poll(c)) {
return false;
}
cbm_ignore_checked_control_t control = cif_ignore_control(c);
cbm_ignore_checked_parse_result_t parsed = {0};
cbm_ignore_checked_status_t status = cbm_ignore_checked_parse(
o->ignore, length ? o->scratch : NULL, length, &control, &parsed, NULL);
if (!cif_checked(c, status)) {
return false;
}
if (!parsed.program) {
return cif_fail(c, CBM_INVENTORY_STATE);
}
*program = parsed.program;
row->file_index = index;
row->effective_patterns = parsed.pattern_count;
row->outcome =
parsed.pattern_count ? CBM_INVENTORY_CONTROL_APPLIED : CBM_INVENTORY_CONTROL_APPLIED_EMPTY;
o->rows[index].roles |= kind == CBM_INVENTORY_CONTROL_CBMIGNORE
? CBM_INVENTORY_FILTER_ROLE_CBMIGNORE
: CBM_INVENTORY_FILTER_ROLE_GITIGNORE;
return cif_poll(c);
}
bool cif_load_controls(cif_context *c, size_t directory) {
cbm_inventory_filter_t *o = c->owner;
if (!directory && !cif_load_control(c, 0, CBM_INVENTORY_CONTROL_CBMIGNORE, &o->cbm_control,
&o->cbm_program)) {
return false;
}
cbm_inventory_control_row_t *row = &o->controls[o->view.control_count];
if (!cif_load_control(c, directory, CBM_INVENTORY_CONTROL_GITIGNORE, row,
&o->directories[directory].program)) {
return false;
}
o->view.control_count++;
return true;
}
+239
View File
@@ -0,0 +1,239 @@
#include "discover/inventory_internal.h"
static bool cif_common(cif_context *c, cbm_inventory_path_t a, cbm_inventory_path_t b,
size_t *length) {
*length = 0;
size_t n = a.length < b.length ? a.length : b.length;
while (*length < n) {
if (!cif_bytes(c, 2)) {
return false;
}
if (a.data[*length] != b.data[*length]) {
break;
}
(*length)++;
}
return true;
}
static bool cif_directory_init(cif_context *c, cif_directory *d, cbm_inventory_path_t path) {
cif_directory value = {.parent = SIZE_MAX,
.next = SIZE_MAX,
.first_directory = SIZE_MAX,
.last_directory = SIZE_MAX,
.first_file = SIZE_MAX,
.last_file = SIZE_MAX,
.excluded = SIZE_MAX};
value.path.data = cif_string(c, path.data, path.length);
value.path.length = path.length;
return value.path.data && cif_copy(c, d, &value, sizeof(value));
}
/* Equal slash-terminated prefixes occupy one contiguous run of sorted files.
* Their first occurrence is unique; the generated directory order still needs
* sorting (a-/x precedes a/x although directory a precedes directory a-). */
static bool cif_prefixes(cif_context *c, bool fill, size_t *total) {
cbm_inventory_filter_t *o = c->owner;
size_t count = 1;
for (size_t i = 0; i < o->view.file_count; i++) {
c->file_index = i;
cbm_inventory_path_t path = o->files[i].path;
size_t common = 0;
if (!cif_event(c) || !cif_bytes(c, 128) ||
(i && !cif_common(c, o->files[i - 1].path, path, &common))) {
return false;
}
for (size_t j = 0; j < path.length; j++) {
if (!cif_bytes(c, 1)) {
return false;
}
if (path.data[j] != '/' || j < common) {
continue;
}
if (count >= o->limits.max_directories) {
return cif_fail(c, CBM_INVENTORY_LIMIT);
}
if (fill && !cif_directory_init(c, &o->directories[count],
(cbm_inventory_path_t){path.data, j})) {
return false;
}
count++;
}
}
*total = count;
return true;
}
static bool cif_merge(cif_context *c, const cif_directory *from, cif_directory *to, size_t start,
size_t middle, size_t end) {
size_t left = start, right = middle;
for (size_t at = start; at < end; at++) {
int order = 0;
if (!cif_event(c)) {
return false;
}
if (left < middle && right < end &&
!cif_compare(c, from[left].path, from[right].path, &order)) {
return false;
}
size_t selected = right == end || (left < middle && order <= 0) ? left++ : right++;
if (!cif_copy(c, &to[at], &from[selected], sizeof(*to))) {
return false;
}
}
return true;
}
static bool cif_sort_directories(cif_context *c) {
cbm_inventory_filter_t *o = c->owner;
size_t n = o->view.directory_count;
if (n < 2) {
return true;
}
cif_directory *temporary = cif_alloc(c, n, sizeof(*temporary));
if (!temporary) {
return false;
}
cif_directory *from = o->directories, *to = temporary;
for (size_t width = 1; width < n;) {
for (size_t start = 0; start < n;) {
size_t middle = start + (width < n - start ? width : n - start);
size_t end = middle + (width < n - middle ? width : n - middle);
if (!cif_merge(c, from, to, start, middle, end)) {
return false;
}
start = end;
}
cif_directory *swap = from;
from = to;
to = swap;
if (width >= n - width) {
break;
}
width *= 2;
}
return from == o->directories || cif_copy(c, o->directories, from, n * sizeof(*from));
}
static bool cif_find(cif_context *c, cbm_inventory_path_t path, bool directory, size_t *index) {
cbm_inventory_filter_t *o = c->owner;
size_t low = 0, high = directory ? o->view.directory_count : o->view.file_count;
*index = SIZE_MAX;
while (low < high) {
size_t mid = low + (high - low) / 2;
cbm_inventory_path_t candidate = directory ? o->directories[mid].path : o->files[mid].path;
int order = 0;
if (!cif_compare(c, candidate, path, &order)) {
return false;
}
if (!order) {
*index = mid;
return true;
}
if (order < 0) {
low = mid + 1;
} else {
high = mid;
}
}
return true;
}
bool cif_find_file(cif_context *c, cbm_inventory_path_t path, size_t *index) {
return cif_find(c, path, false, index);
}
bool cif_find_directory(cif_context *c, cbm_inventory_path_t path, size_t *index) {
return cif_find(c, path, true, index);
}
static bool cif_parent(cif_context *c, cbm_inventory_path_t path, size_t *parent) {
size_t length = path.length;
while (length) {
if (!cif_bytes(c, 1)) {
return false;
}
if (path.data[--length] == '/') {
break;
}
}
if (!cif_find_directory(c, (cbm_inventory_path_t){path.data, length}, parent)) {
return false;
}
return *parent != SIZE_MAX || cif_fail(c, CBM_INVENTORY_STATE);
}
static bool cif_link_directories(cif_context *c) {
cbm_inventory_filter_t *o = c->owner;
for (size_t i = 1; i < o->view.directory_count; i++) {
c->file_index = SIZE_MAX;
size_t collision = SIZE_MAX;
cif_directory *d = &o->directories[i];
if (!cif_event(c) || !cif_bytes(c, 256) || !cif_find_file(c, d->path, &collision)) {
return false;
}
if (collision != SIZE_MAX) {
c->file_index = collision;
return cif_fail(c, CBM_INVENTORY_INVALID);
}
if (!cif_parent(c, d->path, &d->parent)) {
return false;
}
if (d->parent >= i) {
return cif_fail(c, CBM_INVENTORY_STATE);
}
if (!cif_bytes(c, 128)) {
return false;
}
cif_directory *parent = &o->directories[d->parent];
if (parent->last_directory != SIZE_MAX) {
o->directories[parent->last_directory].next = i;
} else {
parent->first_directory = i;
}
parent->last_directory = i;
}
return true;
}
static bool cif_link_files(cif_context *c) {
cbm_inventory_filter_t *o = c->owner;
for (size_t i = 0; i < o->view.file_count; i++) {
c->file_index = i;
size_t at = SIZE_MAX;
if (!cif_event(c) || !cif_bytes(c, 256) || !cif_parent(c, o->files[i].path, &at)) {
return false;
}
if (!cif_bytes(c, 128)) {
return false;
}
cif_directory *parent = &o->directories[at];
if (parent->last_file != SIZE_MAX) {
o->file_next[parent->last_file] = i;
} else {
parent->first_file = i;
}
parent->last_file = i;
}
return true;
}
bool cif_directories(cif_context *c) {
cbm_inventory_filter_t *o = c->owner;
size_t count = 0;
if (!cif_prefixes(c, false, &count)) {
return false;
}
c->file_index = SIZE_MAX;
o->view.directory_count = count;
o->directories = cif_alloc(c, count, sizeof(*o->directories));
o->controls = cif_alloc(c, count + 1, sizeof(*o->controls));
o->exclusions = cif_alloc(c, count, sizeof(*o->exclusions));
if (!o->directories || !o->controls || !o->exclusions ||
!cif_directory_init(c, &o->directories[0], (cbm_inventory_path_t){NULL, 0}) ||
!cif_prefixes(c, true, &count)) {
return false;
}
c->file_index = SIZE_MAX;
return cif_sort_directories(c) && cif_link_directories(c) && cif_link_files(c);
}
+239
View File
@@ -0,0 +1,239 @@
#include "discover/inventory_internal.h"
#include "discover/discover.h"
#include "discover/discover_policy_internal.h"
static bool cif_match(cif_context *c, const cbm_ignore_program_t *program,
cbm_inventory_path_t path, bool directory, cbm_ignore_decision_t *decision) {
*decision = CBM_IGNORE_NO_OPINION;
if (!program) {
return true;
}
if (!cif_poll(c)) {
return false;
}
cbm_ignore_checked_control_t control = cif_ignore_control(c);
cbm_ignore_checked_status_t status =
cbm_ignore_checked_match(c->owner->ignore, program, (const char *)path.data, path.length,
directory, &control, decision, NULL);
if (!cif_checked(c, status)) {
return false;
}
if (*decision != CBM_IGNORE_REINCLUDED && *decision != CBM_IGNORE_NO_OPINION &&
*decision != CBM_IGNORE_IGNORED) {
return cif_fail(c, CBM_INVENTORY_STATE);
}
return cif_poll(c);
}
static bool cif_git_opinion(cif_context *c, size_t parent, cbm_inventory_path_t path,
bool directory, cbm_ignore_decision_t *decision) {
*decision = CBM_IGNORE_NO_OPINION;
while (parent != SIZE_MAX) {
if (!cif_event(c) || !cif_bytes(c, 128)) {
return false;
}
const cif_directory *d = &c->owner->directories[parent];
size_t offset = d->path.length ? d->path.length + 1 : 0;
if (offset > path.length) {
return cif_fail(c, CBM_INVENTORY_STATE);
}
cbm_inventory_path_t local = {path.data + offset, path.length - offset};
if (!cif_match(c, d->program, local, directory, decision)) {
return false;
}
if (*decision != CBM_IGNORE_NO_OPINION) {
return true;
}
parent = d->parent;
}
return true;
}
static bool cif_ignore_reason(cif_context *c, size_t parent, cbm_inventory_path_t path,
bool directory, cbm_inventory_filter_reason_t *reason) {
cbm_ignore_decision_t decision;
if (!cif_git_opinion(c, parent, path, directory, &decision)) {
return false;
}
if (decision == CBM_IGNORE_IGNORED) {
*reason = CBM_INVENTORY_FILTER_REASON_GITIGNORE;
return true;
}
if (!cif_match(c, c->owner->cbm_program, path, directory, &decision)) {
return false;
}
if (decision == CBM_IGNORE_IGNORED) {
*reason = CBM_INVENTORY_FILTER_REASON_CBMIGNORE;
}
return true;
}
static const char *cif_basename(const cbm_inventory_filter_t *o, size_t parent,
cbm_inventory_path_t path) {
size_t length = o->directories[parent].path.length;
return (const char *)path.data + (length ? length + 1 : 0);
}
static bool cif_file_reason(cif_context *c, size_t parent, cbm_inventory_path_t path,
cbm_inventory_filter_reason_t *reason) {
const char *name = cif_basename(c->owner, parent, path);
if (!cif_poll(c)) {
return false;
}
/* Shared FULL helpers have bounded literal tables; suffix length is cached
* in discover.c so a 4095-byte name cannot cause repeated full scans. */
if (cbm_has_ignored_suffix(name, CBM_MODE_FULL)) {
*reason = CBM_INVENTORY_FILTER_REASON_IGNORED_SUFFIX;
} else if (cbm_should_skip_filename(name, CBM_MODE_FULL)) {
*reason = CBM_INVENTORY_FILTER_REASON_SKIP_LIST;
} else if (cbm_matches_fast_pattern(name, CBM_MODE_FULL)) {
*reason = CBM_INVENTORY_FILTER_REASON_FAST_PATTERN;
}
if (!cif_poll(c)) {
return false;
}
return *reason != CBM_INVENTORY_FILTER_REASON_NONE ||
cif_ignore_reason(c, parent, path, false, reason);
}
static bool cif_directory_reason(cif_context *c, size_t parent, cbm_inventory_path_t path,
cbm_inventory_filter_reason_t *reason) {
const char *name = cif_basename(c->owner, parent, path);
if (!cif_poll(c)) {
return false;
}
bool builtin = cbm_should_skip_dir(name, CBM_MODE_FULL);
bool suffix = !builtin && cbm_discover_path_has_skip_suffix((const char *)path.data);
bool safety =
(builtin || suffix) && c->owner->cbm_program && cbm_discover_is_safety_core_directory(name);
if (!cif_poll(c)) {
return false;
}
if (builtin || suffix) {
cbm_ignore_decision_t decision = CBM_IGNORE_NO_OPINION;
if (!safety && !cif_match(c, c->owner->cbm_program, path, true, &decision)) {
return false;
}
if (decision != CBM_IGNORE_REINCLUDED) {
*reason = builtin ? CBM_INVENTORY_FILTER_REASON_DIRECTORY_BUILTIN
: CBM_INVENTORY_FILTER_REASON_DIRECTORY_SUFFIX;
return true;
}
}
return cif_ignore_reason(c, parent, path, true, reason);
}
static bool cif_file_entry(cif_context *c, size_t parent, size_t file) {
cbm_inventory_filter_t *o = c->owner;
c->file_index = file;
cbm_inventory_filter_reason_t reason = CBM_INVENTORY_FILTER_REASON_NONE;
if (!cif_file_reason(c, parent, o->files[file].path, &reason) || !cif_bytes(c, 128)) {
return false;
}
o->rows[file].reason = reason;
if (reason != CBM_INVENTORY_FILTER_REASON_NONE) {
o->rows[file].disposition = CBM_INVENTORY_FILTER_IGNORED_FILE;
}
return true;
}
static bool cif_directory_entry(cif_context *c, size_t parent, size_t directory) {
cbm_inventory_filter_t *o = c->owner;
c->file_index = SIZE_MAX;
cbm_inventory_filter_reason_t reason = CBM_INVENTORY_FILTER_REASON_NONE;
if (!cif_directory_reason(c, parent, o->directories[directory].path, &reason) ||
!cif_bytes(c, 128)) {
return false;
}
o->directories[directory].reason = reason;
if (reason != CBM_INVENTORY_FILTER_REASON_NONE) {
o->directories[directory].excluded = directory;
}
return true;
}
static bool cif_entries(cif_context *c, size_t parent) {
cbm_inventory_filter_t *o = c->owner;
size_t file = o->directories[parent].first_file;
size_t directory = o->directories[parent].first_directory;
while (file != SIZE_MAX || directory != SIZE_MAX) {
if (!cif_event(c) || !cif_bytes(c, 128)) {
return false;
}
int order = directory == SIZE_MAX ? -1 : 1;
if (file != SIZE_MAX && directory != SIZE_MAX &&
!cif_compare(c, o->files[file].path, o->directories[directory].path, &order)) {
return false;
}
if (order < 0) {
if (!cif_file_entry(c, parent, file)) {
return false;
}
file = o->file_next[file];
} else {
if (!cif_directory_entry(c, parent, directory)) {
return false;
}
directory = o->directories[directory].next;
}
}
return true;
}
static bool cif_excluded_files(cif_context *c, size_t directory) {
cbm_inventory_filter_t *o = c->owner;
const cif_directory *d = &o->directories[directory];
const cif_directory *excluded = &o->directories[d->excluded];
for (size_t file = d->first_file; file != SIZE_MAX; file = o->file_next[file]) {
c->file_index = file;
if (!cif_event(c) || !cif_bytes(c, 256)) {
return false;
}
o->rows[file].disposition = CBM_INVENTORY_FILTER_EXCLUDED_SUBTREE;
o->rows[file].reason = excluded->reason;
o->rows[file].excluded_ancestor = excluded->path;
}
return true;
}
static bool cif_final_ledgers(cif_context *c) {
cbm_inventory_filter_t *o = c->owner;
c->file_index = SIZE_MAX;
/* Git rows were appended in canonical directory order; CBM kind follows. */
if (!cif_copy(c, &o->controls[o->view.control_count], &o->cbm_control,
sizeof(o->cbm_control))) {
return false;
}
o->view.control_count++;
for (size_t i = 1; i < o->view.directory_count; i++) {
if (!cif_event(c) || !cif_bytes(c, 128)) {
return false;
}
if (o->directories[i].excluded == i) {
o->exclusions[o->view.excluded_count++] = o->directories[i].path;
}
}
return true;
}
bool cif_filter(cif_context *c) {
cbm_inventory_filter_t *o = c->owner;
for (size_t i = 0; i < o->view.directory_count; i++) {
c->file_index = SIZE_MAX;
if (!cif_event(c) || !cif_bytes(c, 256)) {
return false;
}
cif_directory *d = &o->directories[i];
if (d->parent != SIZE_MAX && o->directories[d->parent].excluded != SIZE_MAX) {
d->excluded = o->directories[d->parent].excluded;
}
if (d->excluded != SIZE_MAX) {
if (!cif_excluded_files(c, i)) {
return false;
}
} else if (!cif_load_controls(c, i) || !cif_entries(c, i)) {
return false;
}
}
return cif_final_ledgers(c);
}
+192
View File
@@ -0,0 +1,192 @@
/* DRAFT internal pre-language inventory filter; parent review required.
* Proposed destination: src/discover/inventory_filter.h.
* No language classification, full-discovery success, pipeline or admission API.
* Shared inventory data types can be included by the later inventory.h rather
* than redeclared. Normative behavior is in contract.txt.
*/
#ifndef CBM_DISCOVER_INVENTORY_FILTER_H
#define CBM_DISCOVER_INVENTORY_FILTER_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef enum {
CBM_INVENTORY_OK = 0,
CBM_INVENTORY_INVALID,
CBM_INVENTORY_STATE,
CBM_INVENTORY_CHANGED,
CBM_INVENTORY_UNSUPPORTED,
CBM_INVENTORY_LIMIT,
CBM_INVENTORY_OOM,
CBM_INVENTORY_IO,
CBM_INVENTORY_CANCELLED,
CBM_INVENTORY_DEADLINE
} cbm_inventory_status_t;
typedef struct {
cbm_inventory_status_t status;
size_t file_index; /* SIZE_MAX unless a particular input row is known. */
bool cleanup_required; /* Independent provider cleanup obligation. */
char diagnostic[256]; /* Owned bounded generic text, no input contents. */
} cbm_inventory_error_t;
typedef struct {
const unsigned char *data;
size_t length;
} cbm_inventory_path_t;
typedef struct {
cbm_inventory_path_t path; /* data[length]==0; no earlier NUL. */
uint32_t git_mode; /* 0100644 or 0100755. */
char oid[65]; /* First NUL at 40 or 64; preceding lowercase hex only. */
uint64_t content_length;
unsigned char content_sha256[32];
} cbm_inventory_file_t;
typedef struct {
uint64_t deadline_ms; /* Positive absolute cbm_now_ms deadline. */
bool (*cancelled)(void *context); /* Optional prompt, pure, non-reentrant. */
void *context; /* Borrowed only until prepare returns. */
} cbm_inventory_control_t;
typedef struct {
size_t max_files; /* Positive, <=INT_MAX; complete inventory, not survivors. */
size_t max_directories; /* Positive, <=INT_MAX; root plus proper prefixes. */
size_t max_arena_bytes; /* Total cumulative logical requests/reservations. */
size_t max_ignore_arena_bytes; /* Positive reserved slice <= total. */
size_t max_control_file_bytes;
uint64_t max_control_total_bytes;
size_t max_ignore_patterns; /* Positive, <=INT_MAX. */
size_t max_probe_prefix_bytes; /* Copied/validated; unused before language. */
uint64_t max_ignore_work; /* Exclusive checked parse+match allowance. */
uint64_t max_verified_file_reads;
uint64_t max_verified_content_bytes;
} cbm_inventory_limits_t;
/* Contractual full-file verification followed by min(capacity,length) exact
* prefix bytes. OK publishes copied; error publishes copied==0. No terminator.
* Serial; all inputs borrowed. An error may require caller-owned native cleanup.
* This callback type cannot authenticate a synthetic provider.
*/
typedef cbm_inventory_status_t (*cbm_inventory_read_fn)(void *context, size_t file_index,
unsigned char *prefix, size_t capacity,
size_t *copied,
const cbm_inventory_control_t *control,
cbm_inventory_error_t *error);
typedef struct {
const char *native_root;
const cbm_inventory_file_t *files;
size_t file_count;
unsigned char manifest_sha256[32]; /* Copied opaque binding, not authenticated. */
cbm_inventory_read_fn read;
void *read_context;
} cbm_inventory_source_t;
/* Separate internal type: never cast to the future final disposition enum. */
typedef enum {
CBM_INVENTORY_FILTER_NEEDS_LANGUAGE = 0,
CBM_INVENTORY_FILTER_IGNORED_FILE,
CBM_INVENTORY_FILTER_EXCLUDED_SUBTREE
} cbm_inventory_filter_disposition_t;
typedef enum {
CBM_INVENTORY_FILTER_REASON_NONE = 0,
CBM_INVENTORY_FILTER_REASON_DIRECTORY_BUILTIN,
CBM_INVENTORY_FILTER_REASON_DIRECTORY_SUFFIX,
CBM_INVENTORY_FILTER_REASON_GITIGNORE,
CBM_INVENTORY_FILTER_REASON_CBMIGNORE,
CBM_INVENTORY_FILTER_REASON_IGNORED_SUFFIX,
CBM_INVENTORY_FILTER_REASON_SKIP_LIST,
CBM_INVENTORY_FILTER_REASON_FAST_PATTERN
} cbm_inventory_filter_reason_t;
enum {
CBM_INVENTORY_FILTER_ROLE_GITIGNORE = 1u << 0,
CBM_INVENTORY_FILTER_ROLE_CBMIGNORE = 1u << 1,
CBM_INVENTORY_FILTER_ROLE_PHYSICAL_PROJECT_CONFIG = 1u << 2
};
typedef struct {
size_t file_index;
cbm_inventory_filter_disposition_t disposition;
cbm_inventory_filter_reason_t reason;
unsigned roles;
cbm_inventory_path_t excluded_ancestor; /* Otherwise {NULL,0}. */
} cbm_inventory_filter_row_t;
typedef enum {
CBM_INVENTORY_CONTROL_GITIGNORE = 0,
CBM_INVENTORY_CONTROL_CBMIGNORE
} cbm_inventory_control_kind_t;
typedef enum {
CBM_INVENTORY_CONTROL_ABSENT = 0,
CBM_INVENTORY_CONTROL_APPLIED_EMPTY,
CBM_INVENTORY_CONTROL_APPLIED
} cbm_inventory_control_outcome_t;
typedef struct {
cbm_inventory_control_kind_t kind;
cbm_inventory_path_t directory; /* Empty root or reached directory. */
size_t file_index; /* SIZE_MAX iff ABSENT. */
cbm_inventory_control_outcome_t outcome;
size_t effective_patterns;
} cbm_inventory_control_row_t;
typedef struct {
uint64_t verified_file_reads_reserved;
uint64_t verified_content_bytes_reserved;
uint64_t control_bytes_reserved;
uint64_t ignore_bytes_reserved;
uint64_t ignore_work_used;
size_t ignore_patterns_reserved;
size_t ignore_arena_requested_bytes;
size_t non_ignore_arena_requested_bytes;
size_t arena_requested_bytes; /* D+C actual logical requests. */
size_t arena_budget_used_bytes; /* D+I unavailable quota. */
} cbm_inventory_filter_usage_t;
typedef struct cbm_inventory_filter cbm_inventory_filter_t;
typedef struct {
const char *native_root;
const cbm_inventory_file_t *files;
const cbm_inventory_filter_row_t *rows;
size_t file_count; /* Exactly the complete input count. */
size_t directory_count; /* Includes excluded directories and root. */
const cbm_inventory_control_row_t *controls;
size_t control_count;
const cbm_inventory_path_t *excluded_directories; /* Minimal roots only. */
size_t excluded_count;
unsigned char manifest_sha256[32];
} cbm_inventory_filter_view_t;
/* Synchronous FULL/no-size-skip pre-language pass. Clears out/error first.
* Required source/limits/control/out; read callback required even for empty input.
* No config/global/Git/directory enumeration/stat/non-control content read.
* Success owns all returned metadata/rows/strings and one checked-ignore owner;
* retains neither callbacks nor caller contexts. Failure: out==NULL, no partial
* view. Native cleanup remains the provider caller's responsibility.
*/
cbm_inventory_status_t cbm_inventory_filter_prepare(const cbm_inventory_source_t *source,
const cbm_inventory_limits_t *limits,
const cbm_inventory_control_t *control,
cbm_inventory_filter_t **out,
cbm_inventory_error_t *error);
/* Borrowed read-only view until free; NULL for NULL. No IO/poll/allocation. */
const cbm_inventory_filter_view_t *cbm_inventory_filter_view(const cbm_inventory_filter_t *owner);
/* Clears out; false for NULL owner/output. Copies usage or original limits.
* These are diagnostics, never a capability to replenish/reset a live owner.
*/
bool cbm_inventory_filter_usage(const cbm_inventory_filter_t *owner,
cbm_inventory_filter_usage_t *out);
bool cbm_inventory_filter_limits(const cbm_inventory_filter_t *owner, cbm_inventory_limits_t *out);
/* NULL-safe; frees checked owner before the containing arena; no callback/IO. */
void cbm_inventory_filter_free(cbm_inventory_filter_t *owner);
#endif
+172
View File
@@ -0,0 +1,172 @@
#include "discover/inventory_internal.h"
#include "foundation/platform.h"
#include <string.h>
bool cif_fail(cif_context *c, cbm_inventory_status_t status) {
if (c->error.status == CBM_INVENTORY_OK) {
c->error.status = status;
c->error.file_index = c->file_index;
strcpy(c->error.diagnostic, "inventory filter could not complete");
}
return false;
}
bool cif_poll(cif_context *c) {
if (c->error.status != CBM_INVENTORY_OK) {
return false;
}
if (c->control->cancelled && c->control->cancelled(c->control->context)) {
return cif_fail(c, CBM_INVENTORY_CANCELLED);
}
if (cbm_now_ms() >= c->control->deadline_ms) {
return cif_fail(c, CBM_INVENTORY_DEADLINE);
}
c->byte_gap = CIF_BYTE_GAP;
c->event_gap = CIF_EVENT_GAP;
return true;
}
bool cif_bytes(cif_context *c, size_t count) {
if (c->error.status != CBM_INVENTORY_OK) {
return false;
}
if (count > CIF_BYTE_GAP) {
return cif_fail(c, CBM_INVENTORY_STATE);
}
if (count > c->byte_gap && !cif_poll(c)) {
return false;
}
c->byte_gap -= count;
return true;
}
bool cif_event(cif_context *c) {
if (c->error.status != CBM_INVENTORY_OK || (!c->event_gap && !cif_poll(c))) {
return false;
}
c->event_gap--;
return true;
}
bool cif_copy(cif_context *c, void *destination, const void *source, size_t length) {
unsigned char *out = destination;
const unsigned char *in = source;
while (length) {
size_t n = length < CIF_COPY_CHUNK ? length : CIF_COPY_CHUNK;
if (!cif_bytes(c, n * 2)) {
return false;
}
if (in) {
memcpy(out, in, n);
in += n;
} else {
memset(out, 0, n);
}
out += n;
length -= n;
}
return true;
}
/* Guard arithmetic also used internally by the existing arena allocator. */
static bool cif_arena_room(cif_context *c, size_t n) {
CBMArena *a = &c->owner->arena;
if (n > SIZE_MAX - 7) {
return cif_fail(c, CBM_INVENTORY_LIMIT);
}
size_t aligned = (n + 7) & ~(size_t)7;
if (a->used > SIZE_MAX - aligned || a->total_alloc > SIZE_MAX - aligned) {
return cif_fail(c, CBM_INVENTORY_LIMIT);
}
if (!a->nblocks || a->used + aligned > a->block_size) {
size_t next = a->grow_size > aligned ? a->grow_size : aligned;
size_t tail = a->block_size > a->used ? a->block_size - a->used : 0;
if (next > SIZE_MAX / 2 || a->waste_tail > SIZE_MAX - tail) {
return cif_fail(c, CBM_INVENTORY_LIMIT);
}
}
return true;
}
void *cif_alloc(cif_context *c, size_t count, size_t width) {
if (!count || !width) {
return NULL;
}
if (count > SIZE_MAX / width) {
cif_fail(c, CBM_INVENTORY_LIMIT);
return NULL;
}
size_t n = count * width;
cbm_inventory_filter_t *o = c->owner;
size_t available = o->limits.max_arena_bytes - o->limits.max_ignore_arena_bytes;
if (n > available - o->usage.non_ignore_arena_requested_bytes) {
cif_fail(c, CBM_INVENTORY_LIMIT);
return NULL;
}
if (!cif_poll(c) || !cif_arena_room(c, n)) {
return NULL;
}
o->usage.non_ignore_arena_requested_bytes += n;
void *result = cbm_arena_alloc(&o->arena, n);
if (!result) {
cif_fail(c, CBM_INVENTORY_OOM);
return NULL;
}
if (!cif_poll(c) || !cif_copy(c, result, NULL, n)) {
return NULL;
}
return result;
}
unsigned char *cif_string(cif_context *c, const unsigned char *source, size_t length) {
if (length == SIZE_MAX) {
cif_fail(c, CBM_INVENTORY_LIMIT);
return NULL;
}
unsigned char *result = cif_alloc(c, length + 1, 1);
return result && cif_copy(c, result, source, length) ? result : NULL;
}
bool cif_compare(cif_context *c, cbm_inventory_path_t a, cbm_inventory_path_t b, int *result) {
if (!cif_event(c)) {
return false;
}
size_t n = a.length < b.length ? a.length : b.length;
for (size_t i = 0; i < n; i++) {
if (!cif_bytes(c, 2)) {
return false;
}
unsigned char left = a.data[i], right = b.data[i];
if (left != right) {
*result = left < right ? -1 : 1;
return true;
}
}
*result = a.length < b.length ? -1 : a.length > b.length;
return true;
}
bool cif_checked(cif_context *c, cbm_ignore_checked_status_t status) {
switch (status) {
case CBM_IGNORE_CHECKED_OK:
return true;
case CBM_IGNORE_CHECKED_INVALID:
return cif_fail(c, CBM_INVENTORY_INVALID);
case CBM_IGNORE_CHECKED_UNSUPPORTED:
return cif_fail(c, CBM_INVENTORY_UNSUPPORTED);
case CBM_IGNORE_CHECKED_LIMIT:
return cif_fail(c, CBM_INVENTORY_LIMIT);
case CBM_IGNORE_CHECKED_OOM:
return cif_fail(c, CBM_INVENTORY_OOM);
case CBM_IGNORE_CHECKED_CANCELLED:
return cif_fail(c, CBM_INVENTORY_CANCELLED);
case CBM_IGNORE_CHECKED_DEADLINE:
return cif_fail(c, CBM_INVENTORY_DEADLINE);
}
return cif_fail(c, CBM_INVENTORY_INVALID);
}
cbm_ignore_checked_control_t cif_ignore_control(const cif_context *c) {
return (cbm_ignore_checked_control_t){c->control->deadline_ms, c->control->cancelled,
c->control->context};
}
+63
View File
@@ -0,0 +1,63 @@
#ifndef CBM_INVENTORY_INTERNAL_H
#define CBM_INVENTORY_INTERNAL_H
#include "discover/inventory_filter.h"
#include "discover/gitignore_checked.h"
#include "foundation/arena.h"
enum { CIF_PATH_MAX = 4095, CIF_BYTE_GAP = 65536, CIF_EVENT_GAP = 4096, CIF_COPY_CHUNK = 4096 };
typedef struct {
cbm_inventory_path_t path;
size_t parent, next, first_directory, last_directory, first_file, last_file;
size_t excluded;
cbm_inventory_filter_reason_t reason;
const cbm_ignore_program_t *program;
} cif_directory;
struct cbm_inventory_filter {
CBMArena arena;
cbm_inventory_limits_t limits;
uint64_t deadline_ms;
cbm_inventory_filter_usage_t usage;
cbm_inventory_filter_view_t view;
cbm_inventory_file_t *files;
cbm_inventory_filter_row_t *rows;
cif_directory *directories;
size_t *file_next;
cbm_inventory_control_row_t *controls;
cbm_inventory_control_row_t cbm_control;
cbm_inventory_path_t *exclusions;
cbm_ignore_checked_t *ignore;
const cbm_ignore_program_t *cbm_program;
unsigned char *scratch;
size_t scratch_capacity;
};
/* Only this synchronous stack context borrows caller functions and contexts. */
typedef struct {
cbm_inventory_filter_t *owner;
const cbm_inventory_source_t *source;
const cbm_inventory_control_t *control;
cbm_inventory_error_t error;
size_t file_index, byte_gap, event_gap;
} cif_context;
bool cif_fail(cif_context *c, cbm_inventory_status_t status);
bool cif_poll(cif_context *c);
bool cif_bytes(cif_context *c, size_t count);
bool cif_event(cif_context *c);
bool cif_copy(cif_context *c, void *destination, const void *source, size_t length);
void *cif_alloc(cif_context *c, size_t count, size_t width);
unsigned char *cif_string(cif_context *c, const unsigned char *source, size_t length);
bool cif_compare(cif_context *c, cbm_inventory_path_t a, cbm_inventory_path_t b, int *result);
bool cif_checked(cif_context *c, cbm_ignore_checked_status_t status);
cbm_ignore_checked_control_t cif_ignore_control(const cif_context *c);
bool cif_metadata(cif_context *c);
bool cif_directories(cif_context *c);
bool cif_find_file(cif_context *c, cbm_inventory_path_t path, size_t *index);
bool cif_find_directory(cif_context *c, cbm_inventory_path_t path, size_t *index);
bool cif_load_controls(cif_context *c, size_t directory);
bool cif_filter(cif_context *c);
#endif
+9
View File
@@ -0,0 +1,9 @@
#ifndef CBM_INVENTORY_LIMITS_INTERNAL_H
#define CBM_INVENTORY_LIMITS_INTERNAL_H
#include "discover/inventory_filter.h"
/* Shared Boolean configuration check; arithmetic exhaustion is checked separately. */
bool cif_limits_valid(const cbm_inventory_limits_t *limits);
#endif
+229
View File
@@ -0,0 +1,229 @@
#include "discover/inventory_internal.h"
#include "foundation/path_syntax_internal.h"
#include <limits.h>
static bool cif_separator(unsigned char ch) {
#ifdef _WIN32
return ch == '/' || ch == '\\';
#else
return ch == '/';
#endif
}
#ifdef _WIN32
static bool cif_windows_encoding(cif_context *c, const char *path, size_t length) {
wchar_t wide[CIF_PATH_MAX + 1];
char roundtrip[CIF_PATH_MAX + 1];
if (!cif_poll(c)) {
return false;
}
cbm_path_syntax_result_t s = cbm_path_windows_roundtrip(path, length, wide, CIF_PATH_MAX + 1,
roundtrip, sizeof(roundtrip));
if (s != CBM_PATH_SYNTAX_OK) {
return cif_fail(c, CBM_INVENTORY_UNSUPPORTED);
}
return cif_poll(c);
}
static bool cif_device_root(const unsigned char *root, size_t n) {
if (n < 4) {
return false;
}
return (cif_separator(root[0]) && cif_separator(root[1]) &&
(root[2] == '?' || root[2] == '.') && cif_separator(root[3])) ||
(root[0] == '\\' && root[1] == '?' && root[2] == '?' && root[3] == '\\');
}
#endif
static bool cif_root(cif_context *c, size_t *length) {
const unsigned char *root = (const unsigned char *)c->source->native_root;
size_t n = 0;
for (; n <= CIF_PATH_MAX; n++) {
if (!cif_bytes(c, 1)) {
return false;
}
if (!root[n]) {
break;
}
}
if (!n) {
return cif_fail(c, CBM_INVENTORY_INVALID);
}
if (n > CIF_PATH_MAX) {
return cif_fail(c, CBM_INVENTORY_LIMIT);
}
#ifdef _WIN32
if (cif_device_root(root, n)) {
return cif_fail(c, CBM_INVENTORY_UNSUPPORTED);
}
if (!cif_poll(c)) {
return false;
}
if (cbm_path_windows_absolute((const char *)root, n) != CBM_PATH_SYNTAX_OK) {
return cif_fail(c, CBM_INVENTORY_INVALID);
}
if (!cif_poll(c) || !cif_windows_encoding(c, (const char *)root, n)) {
return false;
}
#else
if (root[0] != '/') {
return cif_fail(c, CBM_INVENTORY_INVALID);
}
#endif
*length = n;
c->owner->view.native_root = (const char *)cif_string(c, root, n);
return c->owner->view.native_root != NULL;
}
static bool cif_component(cif_context *c, const unsigned char *p, size_t n) {
if (!n || (n == 1 && p[0] == '.') || (n == 2 && p[0] == '.' && p[1] == '.')) {
return cif_fail(c, CBM_INVENTORY_INVALID);
}
#ifdef _WIN32
if (!cif_poll(c)) {
return false;
}
if (cbm_path_windows_component(p, n) != CBM_PATH_SYNTAX_OK) {
return cif_fail(c, CBM_INVENTORY_UNSUPPORTED);
}
return cif_poll(c);
#else
return true;
#endif
}
static bool cif_file_path(cif_context *c, cbm_inventory_path_t path, size_t root_length) {
if (path.length > CIF_PATH_MAX) {
return cif_fail(c, CBM_INVENTORY_LIMIT);
}
if (!path.data || !path.length) {
return cif_fail(c, CBM_INVENTORY_INVALID);
}
size_t separator =
cif_separator((unsigned char)c->owner->view.native_root[root_length - 1]) ? 0 : 1;
if (path.length > CIF_PATH_MAX - root_length ||
separator > CIF_PATH_MAX - root_length - path.length) {
return cif_fail(c, CBM_INVENTORY_LIMIT);
}
size_t start = 0;
for (size_t i = 0; i <= path.length; i++) {
if (!cif_bytes(c, 1)) {
return false;
}
unsigned char ch = path.data[i];
if ((ch == 0) != (i == path.length)) {
return cif_fail(c, CBM_INVENTORY_INVALID);
}
if (!ch || ch == '/') {
if (!cif_component(c, path.data + start, i - start)) {
return false;
}
start = i + 1;
}
}
#ifdef _WIN32
return cif_windows_encoding(c, (const char *)path.data, path.length);
#else
return true;
#endif
}
static bool cif_oid(cif_context *c, const char oid[65], size_t *width) {
size_t n = 0;
while (n < 65) {
if (!cif_bytes(c, 1)) {
return false;
}
unsigned char ch = (unsigned char)oid[n];
if (!ch) {
break;
}
if (!((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f'))) {
return cif_fail(c, CBM_INVENTORY_INVALID);
}
n++;
}
if ((n != 40 && n != 64) || (*width && *width != n)) {
return cif_fail(c, CBM_INVENTORY_INVALID);
}
*width = n;
return true;
}
static bool cif_file(cif_context *c, size_t i, size_t root_length, size_t *width) {
const cbm_inventory_file_t *in = &c->source->files[i];
cbm_inventory_file_t *out = &c->owner->files[i];
c->file_index = i;
if (!cif_event(c) || !cif_bytes(c, 256) || !cif_file_path(c, in->path, root_length)) {
return false;
}
if (i) {
int order = 0;
if (!cif_compare(c, c->owner->files[i - 1].path, in->path, &order)) {
return false;
}
if (order >= 0) {
return cif_fail(c, CBM_INVENTORY_INVALID);
}
}
if (in->git_mode != 0100644 && in->git_mode != 0100755) {
return cif_fail(c, CBM_INVENTORY_UNSUPPORTED);
}
if (in->content_length > INT64_MAX) {
return cif_fail(c, CBM_INVENTORY_LIMIT);
}
if (!cif_oid(c, in->oid, width)) {
return false;
}
out->path.data = cif_string(c, in->path.data, in->path.length);
if (!out->path.data || !cif_bytes(c, 128)) {
return false;
}
out->path.length = in->path.length;
out->git_mode = in->git_mode;
out->content_length = in->content_length;
if (!out->path.data || !cif_copy(c, out->oid, in->oid, *width + 1) ||
!cif_copy(c, out->content_sha256, in->content_sha256, 32)) {
return false;
}
cbm_inventory_filter_row_t *row = &c->owner->rows[i];
row->file_index = i;
row->disposition = CBM_INVENTORY_FILTER_NEEDS_LANGUAGE;
static const unsigned char config[] = ".codebase-memory.json";
int order = 0;
if (!cif_compare(c, out->path, (cbm_inventory_path_t){config, sizeof(config) - 1}, &order)) {
return false;
}
if (!cif_bytes(c, 128)) {
return false;
}
if (!order) {
row->roles = CBM_INVENTORY_FILTER_ROLE_PHYSICAL_PROJECT_CONFIG;
}
c->owner->file_next[i] = SIZE_MAX;
return true;
}
bool cif_metadata(cif_context *c) {
cbm_inventory_filter_t *o = c->owner;
size_t root_length = 0, width = 0;
if (!cif_root(c, &root_length)) {
return false;
}
size_t n = c->source->file_count;
o->view.file_count = n;
o->files = cif_alloc(c, n, sizeof(*o->files));
o->rows = cif_alloc(c, n, sizeof(*o->rows));
o->file_next = cif_alloc(c, n, sizeof(*o->file_next));
if ((n && (!o->files || !o->rows || !o->file_next)) ||
!cif_copy(c, o->view.manifest_sha256, c->source->manifest_sha256, 32)) {
return false;
}
for (size_t i = 0; i < n; i++) {
if (!cif_file(c, i, root_length, &width)) {
return false;
}
}
c->file_index = SIZE_MAX;
return true;
}
+291 -126
View File
@@ -122,11 +122,16 @@ static const ext_entry_t EXT_TABLE[] = {
{".frm", CBM_LANG_FORM},
{".prc", CBM_LANG_FORM},
/* Fortran */
/* Fortran. Upper-case suffixes are the C-preprocessed spellings of the
* same free-form sources (#ifdef blocks); lookup is case-sensitive. */
{".f03", CBM_LANG_FORTRAN},
{".f08", CBM_LANG_FORTRAN},
{".f90", CBM_LANG_FORTRAN},
{".f95", CBM_LANG_FORTRAN},
{".F03", CBM_LANG_FORTRAN},
{".F08", CBM_LANG_FORTRAN},
{".F90", CBM_LANG_FORTRAN},
{".F95", CBM_LANG_FORTRAN},
/* GLSL */
{".frag", CBM_LANG_GLSL},
@@ -918,13 +923,12 @@ static const char *LANG_NAMES[CBM_LANG_COUNT] = {
/* ── Public API ──────────────────────────────────────────────────── */
CBMLanguage cbm_language_for_extension(const char *ext) {
static CBMLanguage lang_for_extension(const cbm_userconfig_t *ucfg, const char *ext) {
if (!ext || !ext[0]) {
return CBM_LANG_COUNT;
}
/* Check user-defined overrides first */
const cbm_userconfig_t *ucfg = cbm_get_user_lang_config();
if (ucfg) {
CBMLanguage ulang = cbm_userconfig_lookup(ucfg, ext);
if (ulang != CBM_LANG_COUNT) {
@@ -940,7 +944,11 @@ CBMLanguage cbm_language_for_extension(const char *ext) {
return CBM_LANG_COUNT;
}
CBMLanguage cbm_language_for_filename(const char *filename) {
CBMLanguage cbm_language_for_extension(const char *ext) {
return lang_for_extension(cbm_get_user_lang_config(), ext);
}
static CBMLanguage lang_for_filename(const cbm_userconfig_t *ucfg, const char *filename) {
if (!filename || !filename[0]) {
return CBM_LANG_COUNT;
}
@@ -980,7 +988,6 @@ CBMLanguage cbm_language_for_filename(const char *filename) {
} COMPOUND_EXT_TABLE[] = {
{".blade.php", CBM_LANG_BLADE},
};
const cbm_userconfig_t *ucfg = cbm_get_user_lang_config();
const char *p = strchr(filename, '.');
while (p && p < last_dot) {
for (size_t i = 0; i < sizeof(COMPOUND_EXT_TABLE) / sizeof(COMPOUND_EXT_TABLE[0]); i++) {
@@ -998,7 +1005,11 @@ CBMLanguage cbm_language_for_filename(const char *filename) {
}
/* Standard single-extension lookup (built-ins + user overrides). */
return cbm_language_for_extension(last_dot);
return lang_for_extension(ucfg, last_dot);
}
CBMLanguage cbm_language_for_filename(const char *filename) {
return lang_for_filename(cbm_get_user_lang_config(), filename);
}
const char *cbm_language_name(CBMLanguage lang) {
@@ -1089,45 +1100,61 @@ static char *shebang_next_token(char **cursor) {
return start;
}
CBMLanguage cbm_language_from_shebang(const char *path) {
if (!path) {
return CBM_LANG_COUNT;
}
/* ── Content probes ────────────────────────────────────────────────
* A few names do not decide a language alone; the file's first bytes do. Every
* probe is a function of those bytes only, so discovery (which reads files)
* and the pinned test-impact inventory (which reads git blobs) give a file the
* same language. The bytes are raw on every platform: a Windows text-mode read
* dropped carriage returns and stopped at a Ctrl-Z, which made the answer
* depend on the host. */
enum {
LANG_PROBE_LINE = 255, /* a shebang first line; the ObjectScript export marker */
LANG_PROBE_HEAD = CBM_SZ_4K, /* .m .cls .inc .frm .res */
LANG_PROBE_CFC = CBM_SZ_16K, /* .cfc: a long license block may precede the first tag */
};
FILE *f = cbm_fopen(path, "rb");
/* The first min(n, cap) bytes as a C string in dst[cap + 1]. The probes are
* string scans, so an embedded NUL ends what they see, as it always did. */
static void lang_head_text(char *dst, size_t cap, const unsigned char *head, size_t n) {
size_t len = n < cap ? n : cap;
if (len > 0) {
memcpy(dst, head, len);
}
dst[len] = '\0';
}
/* The first `cap` bytes of a file, raw. false when the file cannot be opened.
* A read error keeps the bytes read before it, as the probes always did. */
static bool lang_read_head(const char *path, unsigned char *buf, size_t cap, size_t *n) {
*n = 0;
FILE *f = path ? cbm_fopen(path, "rb") : NULL;
if (!f) {
return CBM_LANG_COUNT; /* fail closed on read error */
return false;
}
*n = fread(buf, SKIP_ONE, cap, f);
(void)fclose(f);
return true;
}
/* Read only a bounded first line. */
char buf[CBM_SZ_256];
size_t n = fread(buf, SKIP_ONE, sizeof(buf) - SKIP_ONE, f);
/* Fail closed on any read error rather than parsing a partial buffer. */
if (ferror(f)) {
(void)fclose(f);
/* `more`: the file has bytes beyond head[0..n). A first line that fills the
* probe and goes on is cut, and a cut line names no interpreter. */
static CBMLanguage lang_shebang(const unsigned char *head, size_t n, bool more) {
if (n > LANG_PROBE_LINE) {
n = LANG_PROBE_LINE;
more = true;
}
if (n < PAIR_LEN) {
return CBM_LANG_COUNT; /* too short to begin with "#!" */
}
char buf[LANG_PROBE_LINE + SKIP_ONE];
memcpy(buf, head, n);
bool have_newline = (memchr(buf, '\n', n) != NULL);
if (!have_newline && n == LANG_PROBE_LINE && more) {
return CBM_LANG_COUNT;
}
/* If the bounded buffer filled without containing a newline, the first
* line may extend past our bound. Probe a single extra byte to tell an
* exact EOF (the whole file is <= 255 bytes) from a truncated longer
* line: any surviving byte -- including a newline just beyond the bound --
* means the first line was cut off, so fail closed. A probe read error
* fails closed too. This keeps the read bounded (no unbounded line read
* or allocation). */
bool have_newline = (memchr(buf, '\n', n) != NULL);
if (!have_newline && n == sizeof(buf) - SKIP_ONE) {
int probe = fgetc(f);
if (probe != EOF || ferror(f)) {
(void)fclose(f);
return CBM_LANG_COUNT;
}
}
(void)fclose(f);
/* Must begin with "#!". */
if (n < PAIR_LEN || buf[0] != '#' || buf[1] != '!') {
if (buf[0] != '#' || buf[1] != '!') {
return CBM_LANG_COUNT;
}
@@ -1174,6 +1201,29 @@ CBMLanguage cbm_language_from_shebang(const char *path) {
return lang_for_interpreter(base);
}
CBMLanguage cbm_language_from_shebang(const char *path) {
if (!path) {
return CBM_LANG_COUNT;
}
FILE *f = cbm_fopen(path, "rb");
if (!f) {
return CBM_LANG_COUNT; /* fail closed on read error */
}
/* Read only a bounded first line, plus one byte to tell an exact end of
* file from a longer first line. A read error fails closed rather than
* parsing a partial buffer. */
unsigned char buf[LANG_PROBE_LINE + SKIP_ONE];
size_t n = fread(buf, SKIP_ONE, sizeof(buf), f);
bool failed = ferror(f) != 0;
(void)fclose(f);
if (failed) {
return CBM_LANG_COUNT;
}
return lang_shebang(buf, n > LANG_PROBE_LINE ? LANG_PROBE_LINE : n, n > LANG_PROBE_LINE);
}
/* ── .m file disambiguation ──────────────────────────────────────── */
/* Simple substring search helper */
@@ -1238,22 +1288,7 @@ static bool has_matlab_line_markers(const char *buf) {
return false;
}
CBMLanguage cbm_disambiguate_m(const char *path) {
if (!path) {
return CBM_LANG_MATLAB;
}
FILE *f = cbm_fopen(path, "r");
if (!f) {
return CBM_LANG_MATLAB;
}
/* Read first 4KB */
char buf[CBM_SZ_4K + SKIP_ONE];
size_t n = fread(buf, SKIP_ONE, CBM_SZ_4K, f);
buf[n] = '\0';
(void)fclose(f);
static CBMLanguage lang_m_text(const char *buf) {
if (has_objc_markers(buf)) {
return CBM_LANG_OBJC;
}
@@ -1271,6 +1306,17 @@ CBMLanguage cbm_disambiguate_m(const char *path) {
return CBM_LANG_MATLAB;
}
CBMLanguage cbm_disambiguate_m(const char *path) {
unsigned char head[LANG_PROBE_HEAD];
size_t n = 0;
if (!lang_read_head(path, head, sizeof(head), &n)) {
return CBM_LANG_MATLAB;
}
char buf[LANG_PROBE_HEAD + SKIP_ONE];
lang_head_text(buf, LANG_PROBE_HEAD, head, n);
return lang_m_text(buf);
}
/* Visual Basic 6 / VBA source exports are recognisable from their header (#721):
* every module carries `Attribute VB_Name = "..."`, class modules open with
* `VERSION 1.0 CLASS`, forms/controls with `VERSION 5.00` + `Begin VB.Form` /
@@ -1286,21 +1332,7 @@ static bool has_vb6_markers(const char *buf) {
* VB6 form is reported as unsupported (CBM_LANG_COUNT) rather than handed to
* the FORM grammar, which yields no defs and stray junk nodes. Defaults to
* FORM on any doubt (preserves existing behaviour). */
CBMLanguage cbm_disambiguate_frm(const char *path) {
if (!path) {
return CBM_LANG_FORM;
}
FILE *f = cbm_fopen(path, "r");
if (!f) {
return CBM_LANG_FORM;
}
char buf[CBM_SZ_4K + SKIP_ONE];
size_t n = fread(buf, SKIP_ONE, CBM_SZ_4K, f);
buf[n] = '\0';
(void)fclose(f);
static CBMLanguage lang_frm_text(const char *buf) {
/* VB6 form files open with "VERSION x.yy" on line 1. */
if (strncmp(buf, "VERSION ", SLEN("VERSION ")) == 0 &&
isdigit((unsigned char)buf[SLEN("VERSION ")])) {
@@ -1309,25 +1341,34 @@ CBMLanguage cbm_disambiguate_frm(const char *path) {
return has_vb6_markers(buf) ? CBM_LANG_COUNT : CBM_LANG_FORM;
}
CBMLanguage cbm_disambiguate_frm(const char *path) {
unsigned char head[LANG_PROBE_HEAD];
size_t n = 0;
if (!lang_read_head(path, head, sizeof(head), &n)) {
return CBM_LANG_FORM;
}
char buf[LANG_PROBE_HEAD + SKIP_ONE];
lang_head_text(buf, LANG_PROBE_HEAD, head, n);
return lang_frm_text(buf);
}
/* Disambiguate .res files (#2176): ReScript source shares the extension with
* two binary formats -- Godot resources (RSRC/RSCC magic, the default save
* format for imported meshes) and Windows compiled resource files. Binary
* content has no ReScript meaning, and a NUL byte never occurs in ReScript
* text while both binary formats carry NULs in their first bytes. */
static CBMLanguage lang_res_bytes(const unsigned char *head, size_t n) {
size_t len = n < LANG_PROBE_HEAD ? n : LANG_PROBE_HEAD;
return (len > 0 && memchr(head, '\0', len)) ? CBM_LANG_COUNT : CBM_LANG_RESCRIPT;
}
CBMLanguage cbm_disambiguate_res(const char *path) {
if (!path) {
unsigned char head[LANG_PROBE_HEAD];
size_t n = 0;
if (!lang_read_head(path, head, sizeof(head), &n)) {
return CBM_LANG_RESCRIPT;
}
FILE *f = cbm_fopen(path, "rb");
if (!f) {
return CBM_LANG_RESCRIPT;
}
char buf[CBM_SZ_4K];
size_t n = fread(buf, SKIP_ONE, sizeof(buf), f);
(void)fclose(f);
return memchr(buf, '\0', n) ? CBM_LANG_COUNT : CBM_LANG_RESCRIPT;
return lang_res_bytes(head, n);
}
/* Disambiguate .cls files: shared by InterSystems ObjectScript UDL, Salesforce
@@ -1335,21 +1376,7 @@ CBMLanguage cbm_disambiguate_res(const char *path) {
* with a line of the form "Class <UppercasePackage>..."; VB6 class modules
* carry the VB6 header markers and are reported as unsupported (CBM_LANG_COUNT)
* until a Visual Basic grammar exists. Defaults to Apex on any doubt. */
CBMLanguage cbm_disambiguate_cls(const char *path) {
if (!path) {
return CBM_LANG_APEX;
}
FILE *f = cbm_fopen(path, "r");
if (!f) {
return CBM_LANG_APEX;
}
char buf[CBM_SZ_4K + SKIP_ONE];
size_t n = fread(buf, SKIP_ONE, CBM_SZ_4K, f);
buf[n] = '\0';
(void)fclose(f);
static CBMLanguage lang_cls_text(const char *buf) {
if (has_vb6_markers(buf)) {
return CBM_LANG_COUNT;
}
@@ -1369,6 +1396,17 @@ CBMLanguage cbm_disambiguate_cls(const char *path) {
return CBM_LANG_APEX;
}
CBMLanguage cbm_disambiguate_cls(const char *path) {
unsigned char head[LANG_PROBE_HEAD];
size_t n = 0;
if (!lang_read_head(path, head, sizeof(head), &n)) {
return CBM_LANG_APEX;
}
char buf[LANG_PROBE_HEAD + SKIP_ONE];
lang_head_text(buf, LANG_PROBE_HEAD, head, n);
return lang_cls_text(buf);
}
/* Disambiguate .inc files: shared by BitBake include fragments and
* InterSystems ObjectScript include (macro) files. ObjectScript .inc files are
* predominantly macro definitions ("#define NAME ..." / "#def1arg NAME ...");
@@ -1378,21 +1416,7 @@ CBMLanguage cbm_disambiguate_cls(const char *path) {
* We therefore match ObjectScript preprocessor directives ('#' immediately
* followed by 'def'/';'), which BitBake never produces. Defaults to BitBake on
* any doubt (preserves existing behaviour). */
CBMLanguage cbm_disambiguate_inc(const char *path) {
if (!path) {
return CBM_LANG_BITBAKE;
}
FILE *f = cbm_fopen(path, "r");
if (!f) {
return CBM_LANG_BITBAKE;
}
char buf[CBM_SZ_4K + SKIP_ONE];
size_t n = fread(buf, SKIP_ONE, CBM_SZ_4K, f);
buf[n] = '\0';
(void)fclose(f);
static CBMLanguage lang_inc_text(const char *buf) {
const char *line = buf;
while (*line) {
/* ObjectScript include header: a line beginning "ROUTINE <Uppercase>". */
@@ -1417,6 +1441,17 @@ CBMLanguage cbm_disambiguate_inc(const char *path) {
return CBM_LANG_BITBAKE;
}
CBMLanguage cbm_disambiguate_inc(const char *path) {
unsigned char head[LANG_PROBE_HEAD];
size_t n = 0;
if (!lang_read_head(path, head, sizeof(head), &n)) {
return CBM_LANG_BITBAKE;
}
char buf[LANG_PROBE_HEAD + SKIP_ONE];
lang_head_text(buf, LANG_PROBE_HEAD, head, n);
return lang_inc_text(buf);
}
/* Case-insensitive prefix match (portable — no strncasecmp dependency). */
static bool starts_with_ci(const char *s, const char *prefix) {
for (; *prefix; s++, prefix++) {
@@ -1442,23 +1477,7 @@ static bool starts_with_ci(const char *s, const char *prefix) {
* - a different leading tag (e.g. <cfquery> in a bare-tag file) ⇒ cfml;
* - anything else ("component { ... }") ⇒ cfscript.
* Defaults to CBM_LANG_CFSCRIPT on any doubt (preserves table behaviour). */
CBMLanguage cbm_disambiguate_cfc(const char *path) {
if (!path) {
return CBM_LANG_CFSCRIPT;
}
FILE *f = cbm_fopen(path, "r");
if (!f) {
return CBM_LANG_CFSCRIPT;
}
/* Read a generous head: tag components can carry a large license/revision
* comment block before the <cfcomponent> opener. */
char buf[CBM_SZ_16K + SKIP_ONE];
size_t n = fread(buf, SKIP_ONE, CBM_SZ_16K, f);
buf[n] = '\0';
(void)fclose(f);
static CBMLanguage lang_cfc_text(const char *buf) {
/* Rule 1: explicit tag-component markers ⇒ tag dialect. */
if (cbm_strcasestr(buf, "<cfcomponent") != NULL || cbm_strcasestr(buf, "<cffunction") != NULL) {
return CBM_LANG_CFML;
@@ -1487,3 +1506,149 @@ CBMLanguage cbm_disambiguate_cfc(const char *path) {
}
return CBM_LANG_CFSCRIPT;
}
CBMLanguage cbm_disambiguate_cfc(const char *path) {
unsigned char head[LANG_PROBE_CFC];
size_t n = 0;
if (!lang_read_head(path, head, sizeof(head), &n)) {
return CBM_LANG_CFSCRIPT;
}
char buf[LANG_PROBE_CFC + SKIP_ONE];
lang_head_text(buf, LANG_PROBE_CFC, head, n);
return lang_cfc_text(buf);
}
/* ── One classifier for a name plus its first bytes ──────────────── */
/* JSON files that are configuration, not source worth indexing. */
static const char *const IGNORED_JSON_FILES[] = {
"package.json", "package-lock.json", "tsconfig.json",
"jsconfig.json", "composer.json", "composer.lock",
"yarn.lock", "openapi.json", "swagger.json",
"jest.config.json", ".eslintrc.json", ".prettierrc.json",
".babelrc.json", "tslint.json", "angular.json",
"firebase.json", "renovate.json", "lerna.json",
"turbo.json", ".stylelintrc.json", "pnpm-lock.json",
"deno.json", "biome.json", "devcontainer.json",
".devcontainer.json", "launch.json", "settings.json",
"extensions.json", "tasks.json", NULL};
static bool lang_name_in(const char *name, const char *const *list) {
for (int i = 0; list[i]; i++) {
if (strcmp(name, list[i]) == 0) {
return true;
}
}
return false;
}
/* The content rule a name needs, or NULL. */
static const char *lang_probe_extension(const char *filename) {
static const char *const PROBED[] = {".m", ".cls", ".inc", ".cfc", ".frm", ".res", NULL};
const char *dot = strrchr(filename, '.');
return (dot && lang_name_in(dot, PROBED)) ? dot : NULL;
}
/* "<Export generator=" in the first line-probe bytes: an ObjectScript Studio
* export, which is XML by name. */
static bool lang_objectscript_export(const unsigned char *head, size_t n) {
char buf[LANG_PROBE_LINE + SKIP_ONE];
lang_head_text(buf, LANG_PROBE_LINE, head, n);
return strstr(buf, "<Export generator=") != NULL;
}
_Static_assert(LANG_PROBE_CFC == CBM_LANGUAGE_PROBE_MAX, "the longest probe is the public bound");
size_t cbm_language_probe_bytes_with(const cbm_userconfig_t *config, const char *filename) {
if (!filename) {
return 0;
}
CBMLanguage lang = lang_for_filename(config, filename);
if (lang == CBM_LANG_COUNT) {
return LANG_PROBE_LINE; /* the shebang fallback */
}
const char *ext = lang_probe_extension(filename);
if (ext) {
return strcmp(ext, ".cfc") == 0 ? LANG_PROBE_CFC : LANG_PROBE_HEAD;
}
return lang == CBM_LANG_XML ? LANG_PROBE_LINE : 0;
}
CBMLanguage cbm_language_classify_with(const cbm_userconfig_t *config, const char *filename,
const unsigned char *head, size_t head_len, bool more,
bool readable) {
if (!filename) {
return CBM_LANG_COUNT;
}
if (!readable || !head) {
head = NULL;
head_len = 0;
}
CBMLanguage lang = lang_for_filename(config, filename);
if (lang == CBM_LANG_COUNT) {
/* The name says nothing: a conservative shebang probe, so extensionless
* scripts get indexed (#1199). The name stays authoritative otherwise. */
return readable ? lang_shebang(head, head_len, more) : CBM_LANG_COUNT;
}
const char *ext = lang_probe_extension(filename);
if (ext) {
char buf[LANG_PROBE_CFC + SKIP_ONE];
bool cfc = strcmp(ext, ".cfc") == 0;
lang_head_text(buf, cfc ? LANG_PROBE_CFC : LANG_PROBE_HEAD, head, head_len);
if (strcmp(ext, ".m") == 0) {
/* Objective-C, Magma or MATLAB */
lang = readable ? lang_m_text(buf) : CBM_LANG_MATLAB;
} else if (strcmp(ext, ".cls") == 0) {
/* ObjectScript UDL, Apex or a Visual Basic 6 class module */
lang = readable ? lang_cls_text(buf) : CBM_LANG_APEX;
} else if (strcmp(ext, ".inc") == 0) {
/* BitBake or an ObjectScript include */
lang = readable ? lang_inc_text(buf) : CBM_LANG_BITBAKE;
} else if (cfc) {
/* script or tag dialect */
lang = readable ? lang_cfc_text(buf) : CBM_LANG_CFSCRIPT;
} else if (strcmp(ext, ".frm") == 0) {
/* FORM or a Visual Basic 6 form (#721) */
lang = readable ? lang_frm_text(buf) : CBM_LANG_FORM;
} else {
/* .res: ReScript, or a binary Godot / Windows resource (#2176) */
lang = readable ? lang_res_bytes(head, head_len) : CBM_LANG_RESCRIPT;
}
}
if (lang == CBM_LANG_XML && readable && lang_objectscript_export(head, head_len)) {
return CBM_LANG_OBJECTSCRIPT_EXPORT;
}
if (lang == CBM_LANG_JSON && lang_name_in(filename, IGNORED_JSON_FILES)) {
return CBM_LANG_COUNT;
}
return lang;
}
size_t cbm_language_probe_bytes(const char *filename) {
return cbm_language_probe_bytes_with(cbm_get_user_lang_config(), filename);
}
CBMLanguage cbm_language_classify(const char *filename, const unsigned char *head, size_t head_len,
bool more, bool readable) {
return cbm_language_classify_with(cbm_get_user_lang_config(), filename, head, head_len, more,
readable);
}
CBMLanguage cbm_language_for_file(const char *filename, const char *path) {
size_t probe = cbm_language_probe_bytes(filename);
if (probe == 0) {
return cbm_language_classify(filename, NULL, 0, false, true);
}
/* One byte past the probe tells a file that ends there from a longer one. */
unsigned char head[LANG_PROBE_CFC + SKIP_ONE];
size_t n = 0;
FILE *f = path ? cbm_fopen(path, "rb") : NULL;
if (!f) {
return cbm_language_classify(filename, NULL, 0, false, false);
}
n = fread(head, SKIP_ONE, probe + SKIP_ONE, f);
bool failed = ferror(f) != 0;
(void)fclose(f);
bool more = n > probe;
return cbm_language_classify(filename, head, more ? probe : n, more, !failed);
}
+302
View File
@@ -0,0 +1,302 @@
/* Immutable test-convention declarations from one project JSON snapshot. */
#include "discover/test_conventions.h"
#include "foundation/arena.h"
#include "foundation/sha256.h"
#include <limits.h>
#include <stdint.h>
#include <string.h>
#include <yyjson/yyjson.h>
enum { TD_MAX_BYTES = 65536, TD_MAX_NAME_ARGS = 64 };
struct cbm_test_declarations {
CBMArena arena;
cbm_test_declaration_t *items;
int count;
bool configured;
bool presets[CBM_TEST_PRESET_COUNT];
char digest[CBM_SHA256_HEX_LEN + 1];
};
static yyjson_val *td_field(yyjson_val *object, const char *name, bool *valid) {
if (!yyjson_is_obj(object)) {
*valid = false;
return NULL;
}
yyjson_val *found = NULL, *key, *value;
size_t index, maximum, len = strlen(name);
yyjson_obj_foreach(object, index, maximum, key, value) {
if (yyjson_get_len(key) == len && memcmp(yyjson_get_str(key), name, len) == 0) {
if (found) {
*valid = false;
return NULL;
}
found = value;
}
}
return found;
}
static const char *td_string(yyjson_val *value, size_t *len) {
const char *text = yyjson_get_str(value);
size_t size = yyjson_get_len(value);
if (!text || !size || memchr(text, '\0', size) || memchr(text, '\n', size) ||
memchr(text, '\r', size))
return NULL;
if (len)
*len = size;
return text;
}
static bool td_identifier_first(unsigned char ch) {
return ch == '_' || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
}
static bool td_identifier(const char *text, size_t len) {
if (!len || !td_identifier_first((unsigned char)text[0]))
return false;
for (size_t i = 1; i < len; i++) {
unsigned char ch = (unsigned char)text[i];
if (!td_identifier_first(ch) && !(ch >= '0' && ch <= '9'))
return false;
}
return true;
}
static bool td_copy_string(cbm_test_declarations_t *declarations, yyjson_val *value,
bool identifier, const char **result) {
size_t len = 0;
const char *text = td_string(value, &len);
if (!text || (identifier && !td_identifier(text, len)))
return false;
*result = cbm_arena_strndup(&declarations->arena, text, len);
return *result != NULL;
}
static bool td_argument(yyjson_val *value, int *result) {
if (!yyjson_is_uint(value) || yyjson_get_uint(value) > INT_MAX)
return false;
*result = (int)yyjson_get_uint(value);
return true;
}
static bool td_name_args(cbm_test_declarations_t *declarations, yyjson_val *value,
cbm_test_declaration_t *item) {
if (!yyjson_is_arr(value))
return false;
size_t count = yyjson_arr_size(value);
if (!count || count > TD_MAX_NAME_ARGS)
return false;
int *args = cbm_arena_alloc(&declarations->arena, count * sizeof(*args));
if (!args)
return false;
size_t index, maximum;
yyjson_val *argument;
yyjson_arr_foreach(value, index, maximum, argument) {
if (!td_argument(argument, &args[index]))
return false;
}
/* Ordered duplicates carry meaning for an adapter, so retain them. */
item->name_args = args;
item->name_arg_count = (int)count;
return true;
}
static bool td_record(cbm_test_declarations_t *declarations, yyjson_val *record,
cbm_test_declaration_t *item) {
bool valid = true;
yyjson_val *language = td_field(record, "language", &valid);
yyjson_val *role = td_field(record, "role", &valid);
yyjson_val *define_macro = td_field(record, "define_macro", &valid);
yyjson_val *name_args = td_field(record, "name_args", &valid);
yyjson_val *runner_id = td_field(record, "runner_id", &valid);
yyjson_val *macro = td_field(record, "macro", &valid);
yyjson_val *test_arg = td_field(record, "test_arg", &valid);
yyjson_val *suite_arg = td_field(record, "suite_arg", &valid);
yyjson_val *perf_macro = td_field(record, "perf_macro", &valid);
const char *role_text = td_string(role, NULL);
if (!valid || !role_text || !td_copy_string(declarations, language, false, &item->language))
return false;
item->test_arg = -1;
item->suite_arg = -1;
if (strcmp(role_text, "case") == 0 || strcmp(role_text, "suite") == 0) {
bool is_case = strcmp(role_text, "case") == 0;
item->role = is_case ? CBM_TEST_DECL_CASE : CBM_TEST_DECL_SUITE;
if (macro || test_arg || suite_arg || perf_macro || (!is_case && runner_id))
return false;
return td_copy_string(declarations, define_macro, true, &item->define_macro) &&
td_name_args(declarations, name_args, item) &&
(!runner_id || td_copy_string(declarations, runner_id, false, &item->runner_id));
}
if (strcmp(role_text, "registration") == 0) {
item->role = CBM_TEST_DECL_REGISTRATION;
if (define_macro || name_args || runner_id || suite_arg || perf_macro)
return false;
return td_copy_string(declarations, macro, true, &item->macro) &&
td_argument(test_arg, &item->test_arg);
}
if (strcmp(role_text, "suite_registration") == 0) {
item->role = CBM_TEST_DECL_SUITE_REGISTRATION;
if (define_macro || name_args || runner_id || test_arg)
return false;
return td_copy_string(declarations, macro, true, &item->macro) &&
td_argument(suite_arg, &item->suite_arg) &&
(!perf_macro || td_copy_string(declarations, perf_macro, true, &item->perf_macro));
}
return false;
}
static bool td_conventions(cbm_test_declarations_t *declarations, yyjson_val *value) {
if (!yyjson_is_arr(value))
return false;
size_t count = yyjson_arr_size(value);
if (count > INT_MAX || count > SIZE_MAX / sizeof(*declarations->items))
return false;
if (!count)
return true;
declarations->items =
cbm_arena_calloc(&declarations->arena, count * sizeof(*declarations->items));
if (!declarations->items)
return false;
size_t index, maximum;
yyjson_val *record;
yyjson_arr_foreach(value, index, maximum, record) {
if (!td_record(declarations, record, &declarations->items[index]))
return false;
}
declarations->count = (int)count;
return true;
}
static bool td_presets(cbm_test_declarations_t *declarations, yyjson_val *value) {
static const char *const names[CBM_TEST_PRESET_COUNT] = {
[CBM_TEST_PRESET_C_CBM] = "c-cbm", [CBM_TEST_PRESET_PYTEST] = "pytest",
[CBM_TEST_PRESET_GO] = "go", [CBM_TEST_PRESET_JUNIT] = "junit",
[CBM_TEST_PRESET_GTEST] = "gtest", [CBM_TEST_PRESET_RUST] = "rust",
[CBM_TEST_PRESET_JEST] = "jest"};
if (!yyjson_is_obj(value))
return false;
bool seen[CBM_TEST_PRESET_COUNT] = {false};
size_t index, maximum;
yyjson_val *key, *enabled;
yyjson_obj_foreach(value, index, maximum, key, enabled) {
int preset = -1;
size_t len = yyjson_get_len(key);
const char *text = yyjson_get_str(key);
for (int candidate = 0; candidate < CBM_TEST_PRESET_COUNT; candidate++) {
if (len == strlen(names[candidate]) && memcmp(text, names[candidate], len) == 0) {
preset = candidate;
break;
}
}
/* An unknown switch is an unsupported semantic request, not an
* unrelated future field whose intent can safely remain unconsumed. */
if (preset < 0 || seen[preset] || !yyjson_is_bool(enabled))
return false;
seen[preset] = true;
declarations->presets[preset] = yyjson_get_bool(enabled);
}
return true;
}
static bool td_project(cbm_test_declarations_t *declarations, yyjson_val *root) {
bool valid = true;
yyjson_val *impact = td_field(root, "test_impact", &valid);
if (!valid || !impact)
return valid;
yyjson_val *version = td_field(impact, "version", &valid);
yyjson_val *tests = td_field(impact, "tests", &valid);
if (!valid || !yyjson_is_uint(version) || yyjson_get_uint(version) != 1)
return false;
declarations->configured = true;
if (!tests)
return true;
yyjson_val *conventions = td_field(tests, "conventions", &valid);
yyjson_val *presets = td_field(tests, "presets", &valid);
if (!valid)
return false;
return (!conventions || td_conventions(declarations, conventions)) &&
(!presets || td_presets(declarations, presets));
}
static void td_digest(cbm_test_declarations_t *declarations, const char *bytes, size_t len,
bool absent) {
cbm_sha256_ctx hash;
cbm_sha256_init(&hash);
const char *domain = absent ? "cbm-test-config-absent-v1" : "cbm-test-config-bytes-v1";
cbm_sha256_update(&hash, domain, strlen(domain) + 1);
if (len)
cbm_sha256_update(&hash, bytes, len);
uint8_t digest[CBM_SHA256_DIGEST_LEN];
cbm_sha256_final(&hash, digest);
static const char hex[] = "0123456789abcdef";
for (int i = 0; i < CBM_SHA256_DIGEST_LEN; i++) {
declarations->digest[i * 2] = hex[digest[i] >> 4];
declarations->digest[i * 2 + 1] = hex[digest[i] & 15];
}
declarations->digest[CBM_SHA256_HEX_LEN] = '\0';
}
cbm_test_declarations_t *cbm_test_declarations_parse(const char *bytes, size_t len, bool absent) {
if (len > TD_MAX_BYTES || (absent && len) || (!absent && (!bytes || !len)) ||
(len && memchr(bytes, '\0', len)))
return NULL;
CBMArena arena;
cbm_arena_init_lazy(&arena, 4096);
cbm_test_declarations_t *declarations = cbm_arena_calloc(&arena, sizeof(*declarations));
if (!declarations) {
cbm_arena_destroy(&arena);
return NULL;
}
declarations->arena = arena;
for (int preset = 0; preset < CBM_TEST_PRESET_COUNT; preset++)
declarations->presets[preset] = preset != CBM_TEST_PRESET_C_CBM;
bool valid = true;
if (!absent) {
yyjson_doc *doc = yyjson_read(bytes, len, 0);
if (!doc) {
valid = false;
} else {
valid = td_project(declarations, yyjson_doc_get_root(doc));
yyjson_doc_free(doc);
}
}
if (!valid) {
cbm_test_declarations_free(declarations);
return NULL;
}
td_digest(declarations, bytes, len, absent);
return declarations;
}
void cbm_test_declarations_free(cbm_test_declarations_t *declarations) {
if (!declarations)
return;
CBMArena arena = declarations->arena;
cbm_arena_destroy(&arena);
}
bool cbm_test_declarations_configured(const cbm_test_declarations_t *declarations) {
return declarations && declarations->configured;
}
const cbm_test_declaration_t *cbm_test_declarations_items(
const cbm_test_declarations_t *declarations, int *count) {
if (count)
*count = declarations ? declarations->count : 0;
return declarations ? declarations->items : NULL;
}
bool cbm_test_declarations_preset(const cbm_test_declarations_t *declarations,
cbm_test_preset_t preset, bool *enabled) {
if (enabled)
*enabled = false;
if (!declarations || !enabled || (unsigned)preset >= CBM_TEST_PRESET_COUNT)
return false;
*enabled = declarations->presets[preset];
return true;
}
const char *cbm_test_declarations_digest(const cbm_test_declarations_t *declarations) {
return declarations ? declarations->digest : "";
}
+62
View File
@@ -0,0 +1,62 @@
#ifndef CBM_TEST_CONVENTIONS_H
#define CBM_TEST_CONVENTIONS_H
#include <stdbool.h>
#include <stddef.h>
/* Internal immutable descriptor snapshot for the approved tests.conventions
* and tests.presets schema. This parser classifies no source and creates no
* TESTS/macro edges. Consumers must separately verify that they can implement
* every applicable declaration; an unsupported record is never ignored to
* justify narrowing. */
typedef enum {
CBM_TEST_DECL_CASE,
CBM_TEST_DECL_SUITE,
CBM_TEST_DECL_REGISTRATION,
CBM_TEST_DECL_SUITE_REGISTRATION
} cbm_test_decl_role_t;
typedef struct {
const char *language;
cbm_test_decl_role_t role;
const char *define_macro; /* definition roles only */
const int *name_args; /* definition roles; preserve order */
int name_arg_count;
const char *runner_id; /* optional case template; NULL != guessed default */
const char *macro; /* registration roles only */
int test_arg; /* registration; -1 otherwise */
int suite_arg; /* suite_registration; -1 otherwise */
const char *perf_macro; /* optional suite_registration alternate */
} cbm_test_declaration_t;
typedef enum {
CBM_TEST_PRESET_C_CBM,
CBM_TEST_PRESET_PYTEST,
CBM_TEST_PRESET_GO,
CBM_TEST_PRESET_JUNIT,
CBM_TEST_PRESET_GTEST,
CBM_TEST_PRESET_RUST,
CBM_TEST_PRESET_JEST,
CBM_TEST_PRESET_COUNT
} cbm_test_preset_t;
typedef struct cbm_test_declarations cbm_test_declarations_t;
/* Full project JSON, explicit bytes, max64KiB, independently owned. absent
* requires len=0 (NULL bytes allowed); malformed or unsupported consumed
* syntax => NULL. No path read, global state, env or shell evaluation.
* Missing tests/conventions => empty custom list. Missing preset switches
* preserve existing defaults (c-cbm false; six language presets true).
* A missing impact section is distinct from a configured one with defaults.
* runner_id is retained as opaque data; adapters must validate its template
* language and produce an error for missing/unsupported required mappings. */
cbm_test_declarations_t *cbm_test_declarations_parse(const char *bytes, size_t len, bool absent);
void cbm_test_declarations_free(cbm_test_declarations_t *declarations);
bool cbm_test_declarations_configured(const cbm_test_declarations_t *declarations);
const cbm_test_declaration_t *cbm_test_declarations_items(
const cbm_test_declarations_t *declarations, int *count);
/* false for invalid object/enum; clears enabled first. */
bool cbm_test_declarations_preset(const cbm_test_declarations_t *declarations,
cbm_test_preset_t preset, bool *enabled);
const char *cbm_test_declarations_digest(const cbm_test_declarations_t *declarations);
#endif
+280 -4
View File
@@ -1,3 +1,4 @@
#include "foundation/arena.h" /* public lazy-arena declarations before cbm.h */
/*
* userconfig.c — User-defined extension→language mappings.
*
@@ -15,6 +16,7 @@
#include "foundation/platform.h" /* cbm_safe_getenv */
#include "foundation/compat_fs.h"
#include "foundation/sha256.h"
#include "foundation/arena.h"
enum { MAX_CONFIG_SIZE = 65536 };
#include "foundation/log.h"
@@ -30,6 +32,21 @@ enum { MAX_CONFIG_SIZE = 65536 };
static const cbm_userconfig_t *g_userconfig = NULL;
struct cbm_userconfig_source {
CBMArena arena;
cbm_userconfig_source_state_t state;
const char *bytes;
size_t len;
bool owns_config_arena; /* false for both legacy loaders */
};
static void userconfig_source_free(cbm_userconfig_source_t *source) {
if (source) {
CBMArena arena = source->arena;
cbm_arena_destroy(&arena);
}
}
static void userconfig_source_digest(const char *state, const void *bytes, size_t len,
char out[CBM_SHA256_HEX_LEN + 1]) {
static const char domain[] = "cbm-userconfig-source-v1";
@@ -315,13 +332,76 @@ static int load_config_file(const char *path, cbm_userext_t **entries, int *coun
return rc;
}
/* Retain the project input before parsing it; never reopen for declarations. */
static int load_project_source(const char *path, cbm_userconfig_t *cfg, cbm_userext_t **entries,
int *count) {
cbm_userconfig_source_t *source = cfg->project_source;
source->state = CBM_USERCONFIG_SOURCE_ERROR;
userconfig_source_digest("read-error", NULL, 0, cfg->project_source_sha256);
cbm_path_info_t info = {0};
int status = cbm_path_info_utf8(path, &info);
if (status == CBM_PATH_INFO_ABSENT) {
source->state = CBM_USERCONFIG_SOURCE_ABSENT;
/* Preserve the existing digest for actual absence. */
userconfig_source_digest("missing-or-unreadable", NULL, 0, cfg->project_source_sha256);
return 0;
}
if (status != CBM_PATH_INFO_OK || !info.is_regular || info.size > MAX_CONFIG_SIZE)
return 0;
char *bytes = cbm_arena_alloc(&source->arena, MAX_CONFIG_SIZE + 1U);
if (!bytes)
return CBM_NOT_FOUND;
FILE *file = cbm_fopen(path, "rb");
if (!file)
return 0;
size_t len = fread(bytes, 1, MAX_CONFIG_SIZE + 1U, file);
bool valid = !ferror(file) && feof(file) && len <= MAX_CONFIG_SIZE;
if (fclose(file) != 0)
valid = false;
if (!valid)
return 0;
bytes[len] = '\0';
source->state = CBM_USERCONFIG_SOURCE_PRESENT;
source->bytes = bytes;
source->len = len;
userconfig_source_digest(len ? "present" : "empty", bytes, len, cfg->project_source_sha256);
if (!len)
return 0;
yyjson_read_err error = {0};
yyjson_doc *doc = yyjson_read_opts(bytes, len, 0, NULL, &error);
if (!doc) {
if (error.code == YYJSON_READ_ERROR_MEMORY_ALLOCATION)
return CBM_NOT_FOUND;
cbm_log_warn("userconfig.corrupt_json", "path", path);
return 0;
}
int rc = parse_extra_extensions(yyjson_doc_get_root(doc), entries, count, path);
yyjson_doc_free(doc);
return rc;
}
/* ── Public API ──────────────────────────────────────────────────── */
cbm_userconfig_t *cbm_userconfig_load(const char *repo_path) {
static cbm_userconfig_t *userconfig_load(const char *repo_path, bool retain_source) {
/* The retained source first: a failure here has nothing else to undo. */
cbm_userconfig_source_t *source = NULL;
if (retain_source) {
CBMArena arena;
cbm_arena_init(&arena);
source = cbm_arena_calloc(&arena, sizeof(*source));
if (!source) {
cbm_arena_destroy(&arena);
return NULL;
}
source->arena = arena;
source->state = CBM_USERCONFIG_SOURCE_ABSENT;
}
cbm_userconfig_t *cfg = calloc(CBM_ALLOC_ONE, sizeof(cbm_userconfig_t));
if (!cfg) {
userconfig_source_free(source);
return NULL;
}
cfg->project_source = source;
cbm_userext_t *entries = NULL;
int count = 0;
@@ -338,6 +418,7 @@ cbm_userconfig_t *cbm_userconfig_load(const char *repo_path) {
free(entries[i].ext);
}
free(entries);
userconfig_source_free(cfg->project_source);
free(cfg);
return NULL;
}
@@ -348,14 +429,27 @@ cbm_userconfig_t *cbm_userconfig_load(const char *repo_path) {
userconfig_source_digest("not-applicable", NULL, 0, cfg->project_source_sha256);
if (repo_path && repo_path[0]) {
char project_path[PATH_BUF_SZ];
snprintf(project_path, sizeof(project_path), "%s/.codebase-memory.json", repo_path);
if (load_config_file(project_path, &entries, &count, cfg->project_source_sha256) != 0) {
int path_len =
snprintf(project_path, sizeof(project_path), "%s/.codebase-memory.json", repo_path);
int rc;
if (retain_source) {
if (path_len < 0 || (size_t)path_len >= sizeof(project_path)) {
cfg->project_source->state = CBM_USERCONFIG_SOURCE_ERROR;
userconfig_source_digest("path-error", NULL, 0, cfg->project_source_sha256);
rc = 0;
} else {
rc = load_project_source(project_path, cfg, &entries, &count);
}
} else {
rc = load_config_file(project_path, &entries, &count, cfg->project_source_sha256);
}
if (rc != 0) {
/* Free already-allocated entries */
for (int i = 0; i < count; i++) {
free(entries[i].ext);
}
free(entries);
userconfig_source_free(cfg->project_source);
free(cfg);
return NULL;
}
@@ -398,6 +492,32 @@ cbm_userconfig_t *cbm_userconfig_load(const char *repo_path) {
return cfg;
}
cbm_userconfig_t *cbm_userconfig_load(const char *repo_path) {
return userconfig_load(repo_path, false);
}
cbm_userconfig_t *cbm_userconfig_load_with_source(const char *repo_path) {
return userconfig_load(repo_path, true);
}
cbm_userconfig_source_state_t cbm_userconfig_project_source(const cbm_userconfig_t *cfg,
const char **bytes, size_t *len) {
if (bytes)
*bytes = NULL;
if (len)
*len = 0;
if (!cfg || !cfg->project_source)
return CBM_USERCONFIG_SOURCE_ERROR;
const cbm_userconfig_source_t *source = cfg->project_source;
if (source->state == CBM_USERCONFIG_SOURCE_PRESENT) {
if (bytes)
*bytes = source->bytes;
if (len)
*len = source->len;
}
return source->state;
}
CBMLanguage cbm_userconfig_lookup(const cbm_userconfig_t *cfg, const char *ext) {
if (!cfg || !ext || !ext[0]) {
return CBM_LANG_COUNT;
@@ -414,9 +534,165 @@ void cbm_userconfig_free(cbm_userconfig_t *cfg) {
if (!cfg) {
return;
}
if (cfg->project_source && cfg->project_source->owns_config_arena) {
CBMArena arena = cfg->project_source->arena;
cbm_arena_destroy(&arena);
return;
}
for (int i = 0; i < cfg->count; i++) {
free(cfg->entries[i].ext);
}
free(cfg->entries);
userconfig_source_free(cfg->project_source);
free(cfg);
}
/* Frozen project-only input. The legacy pathname loaders remain fail-open. */
static bool snapshot_key_is(yyjson_val *key, const char *name) {
size_t len = strlen(name);
return yyjson_get_len(key) == len && memcmp(yyjson_get_str(key), name, len) == 0;
}
static cbm_userconfig_snapshot_status_t snapshot_extensions(yyjson_val *root, yyjson_val **out) {
*out = NULL;
if (!yyjson_is_obj(root))
return CBM_USERCONFIG_SNAPSHOT_INVALID;
yyjson_obj_iter iter;
yyjson_obj_iter_init(root, &iter);
yyjson_val *key;
while ((key = yyjson_obj_iter_next(&iter)) != NULL) {
if (!snapshot_key_is(key, "extra_extensions"))
continue;
if (*out)
return CBM_USERCONFIG_SNAPSHOT_INVALID;
*out = yyjson_obj_iter_get_val(key);
if (!yyjson_is_obj(*out))
return CBM_USERCONFIG_SNAPSHOT_INVALID;
}
return CBM_USERCONFIG_SNAPSHOT_OK;
}
static bool snapshot_string(yyjson_val *value) {
if (!yyjson_is_str(value) || yyjson_get_len(value) == 0)
return false;
return memchr(yyjson_get_str(value), 0, yyjson_get_len(value)) == NULL;
}
/* Frozen config matching must not depend on the process locale. */
static CBMLanguage snapshot_language(yyjson_val *value) {
const unsigned char *name = (const unsigned char *)yyjson_get_str(value);
size_t len = yyjson_get_len(value);
for (size_t i = 0; i < LANG_NAME_TABLE_SIZE; i++) {
const char *alias = LANG_NAME_TABLE[i].name;
if (strlen(alias) != len)
continue;
size_t j = 0;
for (; j < len; j++) {
unsigned char byte = name[j];
if (byte >= 'A' && byte <= 'Z')
byte = (unsigned char)(byte + ('a' - 'A'));
if (byte != (unsigned char)alias[j])
break;
}
if (j == len)
return LANG_NAME_TABLE[i].lang;
}
return CBM_LANG_COUNT;
}
static cbm_userconfig_snapshot_status_t snapshot_entry(CBMArena *arena, cbm_userconfig_t *cfg,
yyjson_val *key, yyjson_val *value) {
if (!snapshot_string(key) || !snapshot_string(value) || yyjson_get_str(key)[0] != '.')
return CBM_USERCONFIG_SNAPSHOT_INVALID;
const char *ext = yyjson_get_str(key);
for (int i = 0; i < cfg->count; i++) {
if (strcmp(cfg->entries[i].ext, ext) == 0)
return CBM_USERCONFIG_SNAPSHOT_INVALID;
}
CBMLanguage language = snapshot_language(value);
if (language == CBM_LANG_COUNT)
return CBM_USERCONFIG_SNAPSHOT_UNSUPPORTED;
char *copy = cbm_arena_strndup(arena, ext, yyjson_get_len(key));
if (!copy)
return CBM_USERCONFIG_SNAPSHOT_OOM;
cfg->entries[cfg->count++] = (cbm_userext_t){.ext = copy, .lang = language};
return CBM_USERCONFIG_SNAPSHOT_OK;
}
static cbm_userconfig_snapshot_status_t snapshot_parse(CBMArena *arena, cbm_userconfig_t *cfg) {
cbm_userconfig_source_t *source = cfg->project_source;
yyjson_read_err error = {0};
yyjson_doc *doc = yyjson_read_opts((char *)source->bytes, source->len, 0, NULL, &error);
if (!doc)
return error.code == YYJSON_READ_ERROR_MEMORY_ALLOCATION ? CBM_USERCONFIG_SNAPSHOT_OOM
: CBM_USERCONFIG_SNAPSHOT_INVALID;
yyjson_val *extra = NULL;
cbm_userconfig_snapshot_status_t status = snapshot_extensions(yyjson_doc_get_root(doc), &extra);
size_t count = extra ? yyjson_obj_size(extra) : 0;
/* The 64 KiB source cap already bounds count; keep allocation math explicit. */
if (count > MAX_CONFIG_SIZE || count > SIZE_MAX / sizeof(*cfg->entries))
status = CBM_USERCONFIG_SNAPSHOT_LIMIT;
if (status == CBM_USERCONFIG_SNAPSHOT_OK && count > 0) {
cfg->entries = cbm_arena_calloc(arena, count * sizeof(*cfg->entries));
if (!cfg->entries)
status = CBM_USERCONFIG_SNAPSHOT_OOM;
}
if (status == CBM_USERCONFIG_SNAPSHOT_OK && extra) {
yyjson_obj_iter iter;
yyjson_obj_iter_init(extra, &iter);
yyjson_val *key;
while ((key = yyjson_obj_iter_next(&iter)) != NULL) {
status = snapshot_entry(arena, cfg, key, yyjson_obj_iter_get_val(key));
if (status != CBM_USERCONFIG_SNAPSHOT_OK)
break;
}
}
yyjson_doc_free(doc);
return status;
}
static cbm_userconfig_snapshot_status_t snapshot_input(cbm_userconfig_source_state_t state,
const void *bytes, size_t len) {
if (state == CBM_USERCONFIG_SOURCE_ABSENT)
return (!bytes && len == 0) ? CBM_USERCONFIG_SNAPSHOT_OK : CBM_USERCONFIG_SNAPSHOT_INVALID;
if (state != CBM_USERCONFIG_SOURCE_PRESENT || !bytes || len == 0)
return CBM_USERCONFIG_SNAPSHOT_INVALID;
return len > MAX_CONFIG_SIZE ? CBM_USERCONFIG_SNAPSHOT_LIMIT : CBM_USERCONFIG_SNAPSHOT_OK;
}
cbm_userconfig_snapshot_status_t cbm_userconfig_from_project_bytes(
cbm_userconfig_source_state_t state, const void *bytes, size_t len, cbm_userconfig_t **out) {
if (!out)
return CBM_USERCONFIG_SNAPSHOT_INVALID;
*out = NULL;
cbm_userconfig_snapshot_status_t status = snapshot_input(state, bytes, len);
if (status != CBM_USERCONFIG_SNAPSHOT_OK)
return status;
CBMArena arena;
cbm_arena_init_lazy(&arena, CBM_ARENA_DEFAULT_BLOCK_SIZE);
cbm_userconfig_t *cfg = cbm_arena_calloc(&arena, sizeof(*cfg));
cbm_userconfig_source_t *source = cbm_arena_calloc(&arena, sizeof(*source));
if (!cfg || !source) {
cbm_arena_destroy(&arena);
return CBM_USERCONFIG_SNAPSHOT_OOM;
}
cfg->project_source = source;
source->state = state;
source->len = len;
if (state == CBM_USERCONFIG_SOURCE_PRESENT) {
source->bytes = cbm_arena_strndup(&arena, bytes, len);
status = source->bytes ? snapshot_parse(&arena, cfg) : CBM_USERCONFIG_SNAPSHOT_OOM;
}
if (status != CBM_USERCONFIG_SNAPSHOT_OK) {
cbm_arena_destroy(&arena);
return status;
}
userconfig_source_digest("disabled-frozen-v1", NULL, 0, cfg->global_source_sha256);
userconfig_source_digest(state == CBM_USERCONFIG_SOURCE_ABSENT ? "missing-or-unreadable"
: "present",
source->bytes, len, cfg->project_source_sha256);
source->owns_config_arena = true;
source->arena = arena;
*out = cfg;
return CBM_USERCONFIG_SNAPSHOT_OK;
}
+38 -2
View File
@@ -27,9 +27,17 @@ typedef struct {
CBMLanguage lang; /* resolved language enum */
} cbm_userext_t;
typedef struct cbm_userconfig_source cbm_userconfig_source_t;
typedef enum {
CBM_USERCONFIG_SOURCE_ERROR = 0,
CBM_USERCONFIG_SOURCE_ABSENT,
CBM_USERCONFIG_SOURCE_PRESENT
} cbm_userconfig_source_state_t;
typedef struct {
cbm_userext_t *entries; /* heap-allocated array */
int count; /* number of entries */
cbm_userext_t *entries; /* heap-allocated array */
int count; /* number of entries */
cbm_userconfig_source_t *project_source; /* optional owned project snapshot */
/* Digests of the exact bytes/state consumed by cbm_userconfig_load(). */
char global_source_sha256[CBM_SHA256_HEX_LEN + 1];
char project_source_sha256[CBM_SHA256_HEX_LEN + 1];
@@ -46,6 +54,18 @@ typedef struct {
*/
cbm_userconfig_t *cbm_userconfig_load(const char *repo_path);
/* Additive indexing loader: retain exactly the project bytes used for extension
* parsing and project_source_sha256. Global configuration keeps legacy behavior.
* At most 64 KiB; actual absence is distinct from nonregular/read/close/size errors.
* NULL means allocation failure; inspect the snapshot state for other failures.
* Empty or malformed JSON remains PRESENT for downstream strict validation. */
cbm_userconfig_t *cbm_userconfig_load_with_source(const char *repo_path);
/* The PRESENT byte view belongs to cfg and remains valid until free. Outputs
* are cleared for ABSENT/ERROR. A legacy loader result has no snapshot: ERROR. */
cbm_userconfig_source_state_t cbm_userconfig_project_source(const cbm_userconfig_t *cfg,
const char **bytes, size_t *len);
/*
* Look up a file extension in the user config.
* ext: extension including dot, e.g. ".blade.php"
@@ -56,6 +76,22 @@ CBMLanguage cbm_userconfig_lookup(const cbm_userconfig_t *cfg, const char *ext);
/* Free a cbm_userconfig_t returned by cbm_userconfig_load. NULL-safe. */
void cbm_userconfig_free(cbm_userconfig_t *cfg);
/* Pure project-only snapshot; never reads global/home/project paths. ABSENT
* requires NULL/0; PRESENT requires a strict object of 1..65536 bytes. Consumed
* extra_extensions are strict (duplicates/types/NUL rejected, known languages).
* Copies bytes/entries into an owned arena; release through cbm_userconfig_free.
* Clears *out before validation and never exposes a partial config. */
typedef enum {
CBM_USERCONFIG_SNAPSHOT_OK = 0,
CBM_USERCONFIG_SNAPSHOT_INVALID,
CBM_USERCONFIG_SNAPSHOT_UNSUPPORTED,
CBM_USERCONFIG_SNAPSHOT_LIMIT,
CBM_USERCONFIG_SNAPSHOT_OOM
} cbm_userconfig_snapshot_status_t;
cbm_userconfig_snapshot_status_t cbm_userconfig_from_project_bytes(
cbm_userconfig_source_state_t state, const void *bytes, size_t len, cbm_userconfig_t **out);
/* ── Integration hook ───────────────────────────────────────────── */
/*
+21 -7
View File
@@ -74,13 +74,21 @@ bool cbm_git_env_entry_is_repo_local(const char *entry) {
return git_env_name_is_repo_local(entry, name_len);
}
/* Explicit diff flags must not be overridden by the caller's environment.
* Keep the public repository-local classification and its Git-derived list
* unchanged; this extra removal belongs only to child construction. */
static bool git_env_name_should_strip(const char *name, size_t name_len) {
return git_env_name_is_repo_local(name, name_len) ||
git_env_name_equals(name, name_len, "GIT_DIFF_OPTS");
}
#ifdef _WIN32
/* Wide entry name → is it repository-local? Names we match are ASCII, so any
/* Wide entry name → should the child omit it? Names we match are ASCII, so any
* non-ASCII code unit is an immediate mismatch. Windows keeps per-drive cwd
* entries ("=C:=C:\\dir") whose name starts with '='; the search for the
* separator starts at index 1 so they are kept untouched. */
static bool git_env_wide_entry_is_repo_local(const wchar_t *entry) {
static bool git_env_wide_entry_should_strip(const wchar_t *entry) {
char name[64] = {0};
size_t n = 0;
for (size_t i = 0; entry[i] && (i == 0 || entry[i] != L'='); i++) {
@@ -89,7 +97,7 @@ static bool git_env_wide_entry_is_repo_local(const wchar_t *entry) {
}
name[n++] = (char)entry[i];
}
return git_env_name_is_repo_local(name, n);
return git_env_name_should_strip(name, n);
}
wchar_t *cbm_git_child_env_block(void) {
@@ -99,7 +107,7 @@ wchar_t *cbm_git_child_env_block(void) {
}
size_t kept = 0;
for (const wchar_t *e = current; *e; e += wcslen(e) + 1) {
if (!git_env_wide_entry_is_repo_local(e)) {
if (!git_env_wide_entry_should_strip(e)) {
kept += wcslen(e) + 1;
}
}
@@ -111,7 +119,7 @@ wchar_t *cbm_git_child_env_block(void) {
}
size_t pos = 0;
for (const wchar_t *e = current; *e; e += wcslen(e) + 1) {
if (!git_env_wide_entry_is_repo_local(e)) {
if (!git_env_wide_entry_should_strip(e)) {
size_t len = wcslen(e) + 1;
memcpy(block + pos, e, len * sizeof(wchar_t));
pos += len;
@@ -127,11 +135,17 @@ wchar_t *cbm_git_child_env_block(void) {
#else
static bool git_env_entry_should_strip(const char *entry) {
const char *eq = strchr(entry, '=');
size_t name_len = eq ? (size_t)(eq - entry) : strlen(entry);
return git_env_name_should_strip(entry, name_len);
}
char **cbm_git_child_envp(void) {
size_t count = 0;
size_t bytes = 0;
for (char **e = environ; e && *e; e++) {
if (!cbm_git_env_entry_is_repo_local(*e)) {
if (!git_env_entry_should_strip(*e)) {
count++;
bytes += strlen(*e) + 1;
}
@@ -144,7 +158,7 @@ char **cbm_git_child_envp(void) {
char *strings = (char *)envp + table;
size_t slot = 0;
for (char **e = environ; e && *e && slot < count; e++) {
if (!cbm_git_env_entry_is_repo_local(*e)) {
if (!git_env_entry_should_strip(*e)) {
size_t len = strlen(*e) + 1;
memcpy(strings, *e, len);
envp[slot++] = strings;
+7 -4
View File
@@ -7,8 +7,9 @@
* would otherwise resolve every indexed project against the CALLER's repository
* (a non-git directory reported as is_git=true, history and branch data from
* the wrong repo). Every git spawn therefore gets a child environment with
* exactly the variables `git rev-parse --local-env-vars` prints removed. The
* scrub is per spawn: cbm's own environment is never modified (unsetenv in the
* the variables `git rev-parse --local-env-vars` prints removed. GIT_DIFF_OPTS
* is also removed so it cannot override explicit unified-context flags.
* The scrub is per spawn: cbm's own environment is never modified (unsetenv in the
* parent would race every other thread reading the environment).
*/
#ifndef CBM_GIT_ENV_H
@@ -31,12 +32,14 @@ bool cbm_git_env_entry_is_repo_local(const char *entry);
#ifdef _WIN32
/* A CREATE_UNICODE_ENVIRONMENT block: the current environment minus the
* repository-local git variables. One allocation; release with cbm_git_child_env_free().
* repository-local git variables and GIT_DIFF_OPTS. One allocation; release with
* cbm_git_child_env_free().
* NULL on allocation/snapshot failure. */
wchar_t *cbm_git_child_env_block(void);
#else
/* A NULL-terminated envp: the current environment minus the repository-local
* git variables. Pointers and strings live in ONE allocation; release with
* git variables and GIT_DIFF_OPTS. Pointers and strings live in ONE allocation;
* release with
* cbm_git_child_env_free(). NULL on allocation failure. */
char **cbm_git_child_envp(void);
#endif
+96
View File
@@ -0,0 +1,96 @@
#include "foundation/path_syntax_internal.h"
#include <stdbool.h>
#include <string.h>
#ifdef _WIN32
#include <limits.h>
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#endif
static unsigned char path_upper(unsigned char c) {
return c >= 'a' && c <= 'z' ? (unsigned char)(c - 'a' + 'A') : c;
}
static bool path_device(const unsigned char *p, size_t n) {
size_t base = 0;
while (base < n && p[base] != '.')
base++;
if (base < 3)
return false;
unsigned char a = path_upper(p[0]), b = path_upper(p[1]), d = path_upper(p[2]);
if (base == 3)
return (a == 'C' && b == 'O' && d == 'N') || (a == 'P' && b == 'R' && d == 'N') ||
(a == 'A' && b == 'U' && d == 'X') || (a == 'N' && b == 'U' && d == 'L');
if (!((a == 'C' && b == 'O' && d == 'M') || (a == 'L' && b == 'P' && d == 'T')))
return false;
if (base == 4)
return p[3] >= '1' && p[3] <= '9';
return base == 5 && p[3] == 0xc2 && (p[4] == 0xb9 || p[4] == 0xb2 || p[4] == 0xb3);
}
cbm_path_syntax_result_t cbm_path_windows_component(const unsigned char *component, size_t length) {
if (!component || !length || length > CBM_PATH_SYNTAX_MAX_BYTES)
return CBM_PATH_SYNTAX_INVALID;
if (component[length - 1] == '.' || component[length - 1] == ' ' ||
path_device(component, length))
return CBM_PATH_SYNTAX_DEVICE_OR_TRIMMED;
for (size_t i = 0; i < length; i++) {
unsigned char ch = component[i];
if (ch < 32 || ch == '\\' || ch == ':' || ch == '<' || ch == '>' || ch == '"' ||
ch == '|' || ch == '?' || ch == '*')
return CBM_PATH_SYNTAX_COMPONENT_BYTE;
}
return CBM_PATH_SYNTAX_OK;
}
cbm_path_syntax_result_t cbm_path_windows_absolute(const char *path, size_t length) {
if (!path || !length || length > CBM_PATH_SYNTAX_MAX_BYTES)
return CBM_PATH_SYNTAX_INVALID;
bool drive = length >= 3 &&
((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) &&
path[1] == ':' && (path[2] == '/' || path[2] == '\\');
bool unc = length >= 5 && path[0] == '\\' && path[1] == '\\' && path[2] != '?' &&
path[2] != '.' && path[2] != '\\';
if (!drive && !unc)
return CBM_PATH_SYNTAX_NOT_ABSOLUTE;
if (unc) {
size_t i = 2;
while (i < length && path[i] != '\\')
i++;
if (i + 1 >= length || path[i + 1] == '\\')
return CBM_PATH_SYNTAX_INCOMPLETE_UNC;
}
return CBM_PATH_SYNTAX_OK;
}
#ifdef _WIN32
static bool path_roundtrip_args(const char *source, size_t length, const wchar_t *wide,
size_t wide_capacity, const char *roundtrip,
size_t roundtrip_capacity) {
if (!source || !wide || !roundtrip || !length || length > CBM_PATH_SYNTAX_MAX_BYTES ||
!wide_capacity || wide_capacity > INT_MAX || !roundtrip_capacity ||
roundtrip_capacity > INT_MAX)
return false;
return source[length] == '\0' && !memchr(source, '\0', length);
}
cbm_path_syntax_result_t cbm_path_windows_roundtrip(const char *source, size_t length,
wchar_t *wide, size_t wide_capacity,
char *roundtrip, size_t roundtrip_capacity) {
if (!path_roundtrip_args(source, length, wide, wide_capacity, roundtrip, roundtrip_capacity))
return CBM_PATH_SYNTAX_INVALID;
/* length <=4095 establishes the checked int conversion including terminal NUL. */
int source_count = (int)(length + 1);
int count = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, source, source_count, wide,
(int)wide_capacity);
if (!count)
return CBM_PATH_SYNTAX_UTF8;
int bytes = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wide, count, roundtrip,
(int)roundtrip_capacity, NULL, NULL);
if (bytes != source_count || memcmp(source, roundtrip, length + 1))
return CBM_PATH_SYNTAX_ROUNDTRIP;
return CBM_PATH_SYNTAX_OK;
}
#endif
+45
View File
@@ -0,0 +1,45 @@
/* Internal lexical checks shared by discovery and the pinned-tree adapter.
* No allocation, IO, normalization, owner, callback or retained pointers. */
#ifndef CBM_PATH_SYNTAX_INTERNAL_H
#define CBM_PATH_SYNTAX_INTERNAL_H
#include <stddef.h>
#ifdef _WIN32
#include <wchar.h>
#endif
enum { CBM_PATH_SYNTAX_MAX_BYTES = 4095 };
typedef enum {
CBM_PATH_SYNTAX_OK = 0,
CBM_PATH_SYNTAX_INVALID,
CBM_PATH_SYNTAX_DEVICE_OR_TRIMMED,
CBM_PATH_SYNTAX_COMPONENT_BYTE,
CBM_PATH_SYNTAX_UTF8,
CBM_PATH_SYNTAX_ROUNDTRIP,
CBM_PATH_SYNTAX_NOT_ABSOLUTE,
CBM_PATH_SYNTAX_INCOMPLETE_UNC
} cbm_path_syntax_result_t;
/* Byte spans have length 1..MAX_BYTES. No terminator is accessed by these two
* functions. The component caller supplies one already-separated component;
* relative-path structure (including slash/dot components) belongs to caller.
* Device/trailing-dot-or-space rejection precedes forbidden-byte rejection.
* Absolute checks preserve the ordinary drive/backslash-UNC lexical domain;
* these functions do not establish native existence, aliases or permission. */
cbm_path_syntax_result_t cbm_path_windows_component(const unsigned char *component, size_t length);
cbm_path_syntax_result_t cbm_path_windows_absolute(const char *path, size_t length);
#ifdef _WIN32
/* source has exactly length non-NUL bytes followed by an accessible NUL;
* length is 1..MAX_BYTES. Buffers are distinct, caller-owned writable spans.
* Capacities are element counts, positive and <=INT_MAX; wide counts wchar_t.
* Strict UTF8 -> wide -> UTF8 conversion preserves exact bytes including NUL.
* INVALID is a bad pointer/length/capacity/terminated-span argument; UTF8 is a
* forward conversion failure; ROUNDTRIP is reverse failure/length/byte mismatch.
* Outputs may contain partial work on non-OK; only OK certifies conversion.
* Calls are bounded, synchronous and retain neither input nor output storage. */
cbm_path_syntax_result_t cbm_path_windows_roundtrip(const char *source, size_t length,
wchar_t *wide, size_t wide_capacity,
char *roundtrip, size_t roundtrip_capacity);
#endif
#endif
+7 -7
View File
@@ -8,8 +8,8 @@
* - parse_or_expr, parse_xor_expr, parse_and_expr, parse_not_expr
* - parse_atom_expr, parse_post_where, cbm_parse
*
* Cypher expression evaluator (bounded by WHERE clause depth ~5):
* - eval_expr
* Cypher expression evaluators (bounded by WHERE clause depth ~5):
* - eval_expr, eval_expr_partial
*
* Glob pattern matcher (bounded by pattern nesting ~3):
* - glob_match, glob_match_star, glob_match_doublestar
@@ -25,9 +25,9 @@
* To add a function: add it below AND add NOLINT(misc-no-recursion) on
* the function definition line. The lint gate verifies both match.
*/
#define CBM_RECURSION_WHITELIST \
"parse_or_expr", "parse_xor_expr", "parse_and_expr", "parse_not_expr", "parse_atom_expr", \
"parse_post_where", "cbm_parse", "eval_expr", "glob_match", "glob_match_star", \
"glob_match_doublestar", "glob_match_doublestar_slash", "glob_match_doublestar_any", \
"parse_bool_expr", "parse_bool_atom", "r_collect_imports", \
#define CBM_RECURSION_WHITELIST \
"parse_or_expr", "parse_xor_expr", "parse_and_expr", "parse_not_expr", "parse_atom_expr", \
"parse_post_where", "cbm_parse", "eval_expr", "eval_expr_partial", "glob_match", \
"glob_match_star", "glob_match_doublestar", "glob_match_doublestar_slash", \
"glob_match_doublestar_any", "parse_bool_expr", "parse_bool_atom", "r_collect_imports", \
"find_first_descendant_by_kind", "find_first_descendant_of"
+284 -55
View File
@@ -5,6 +5,7 @@
*/
#include "subprocess.h"
#include "arena.h"
#include "compat.h" /* cbm_nanosleep */
#include "compat_fs.h"
#include "git_env.h" /* strip_git_repo_env: scrubbed child environment for git */
@@ -411,6 +412,9 @@ struct cbm_subprocess {
size_t argc;
char *windows_cmd_payload;
char *log_file;
CBMArena stdout_arena;
char *stdout_file;
char *stdin_file;
cbm_proc_log_cb on_log_line;
void *log_ud;
int quiet_timeout_ms;
@@ -471,6 +475,7 @@ static void cbm_subprocess_free_config(cbm_subprocess_t *process) {
free(process->bin);
free(process->windows_cmd_payload);
free(process->log_file);
cbm_arena_destroy(&process->stdout_arena);
#ifndef _WIN32
cbm_git_child_env_free(process->envp);
#endif
@@ -481,6 +486,16 @@ static cbm_subprocess_t *cbm_subprocess_copy_opts(const cbm_proc_opts_t *opts) {
if (!opts || !opts->bin || !opts->bin[0]) {
return NULL;
}
if (opts->stdout_file && (!opts->stdout_file[0] ||
(opts->log_file && strcmp(opts->stdout_file, opts->log_file) == 0))) {
return NULL;
}
if (opts->stdin_file &&
(!opts->stdin_file[0] ||
(opts->log_file && strcmp(opts->stdin_file, opts->log_file) == 0) ||
(opts->stdout_file && strcmp(opts->stdin_file, opts->stdout_file) == 0))) {
return NULL;
}
#ifdef _WIN32
if (opts->windows_cmd_payload &&
(!opts->windows_cmd_payload[0] || opts->argv || !cbm_win_cmd_path_is_absolute(opts->bin))) {
@@ -507,6 +522,7 @@ static cbm_subprocess_t *cbm_subprocess_copy_opts(const cbm_proc_opts_t *opts) {
if (!process) {
return NULL;
}
cbm_arena_init_lazy(&process->stdout_arena, 256);
process->bin = cbm_strdup(opts->bin);
process->windows_cmd_payload =
opts->windows_cmd_payload ? cbm_strdup(opts->windows_cmd_payload) : NULL;
@@ -532,6 +548,20 @@ static cbm_subprocess_t *cbm_subprocess_copy_opts(const cbm_proc_opts_t *opts) {
return NULL;
}
}
if (opts->stdout_file) {
process->stdout_file = cbm_arena_strdup(&process->stdout_arena, opts->stdout_file);
if (!process->stdout_file) {
cbm_subprocess_free_config(process);
return NULL;
}
}
if (opts->stdin_file) {
process->stdin_file = cbm_arena_strdup(&process->stdout_arena, opts->stdin_file);
if (!process->stdin_file) {
cbm_subprocess_free_config(process);
return NULL;
}
}
process->on_log_line = opts->on_log_line;
process->log_ud = opts->log_ud;
process->quiet_timeout_ms = opts->quiet_timeout_ms;
@@ -649,17 +679,80 @@ static cbm_proc_poll_t cbm_subprocess_finish_failed(cbm_subprocess_t *process,
#ifdef _WIN32
static void cbm_win_close_spawn_handles(HANDLE nul, HANDLE log, LPPROC_THREAD_ATTRIBUTE_LIST attrs,
bool attrs_init) {
/* Match cbm_path_to_wide's file-path spelling using the process arena. Existing
* option conversions retain their legacy heap ownership. */
static wchar_t *cbm_win_stdin_path(cbm_subprocess_t *process) {
int length =
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, process->stdin_file, -1, NULL, 0);
if (length <= 0 || length > 32767) {
return NULL;
}
wchar_t *wide = cbm_arena_alloc(&process->stdout_arena, (size_t)length * sizeof(*wide));
if (!wide || MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, process->stdin_file, -1, wide,
length) != length) {
return NULL;
}
size_t input_length = (size_t)length - 1U;
if (cbm_win_path_has_namespace_prefix(wide, input_length)) {
cbm_win_path_normalize_wide_separators(wide);
return wide;
}
if (input_length < 240U) {
return wide;
}
DWORD needed = GetFullPathNameW(wide, 0, NULL, NULL);
if (needed == 0U || needed > 32767U) {
return NULL;
}
wchar_t *full = cbm_arena_alloc(&process->stdout_arena, (size_t)needed * sizeof(*full));
DWORD copied = full ? GetFullPathNameW(wide, needed, full, NULL) : 0U;
if (copied == 0U || copied >= needed) {
return NULL;
}
cbm_win_path_normalize_wide_separators(full);
bool drive_absolute =
copied >= 3U &&
((full[0] >= L'A' && full[0] <= L'Z') || (full[0] >= L'a' && full[0] <= L'z')) &&
full[1] == L':' && full[2] == L'\\';
bool unc_absolute = copied >= 5U && full[0] == L'\\' && full[1] == L'\\';
if (!drive_absolute && !unc_absolute) {
return NULL;
}
const wchar_t *prefix = drive_absolute ? L"\\\\?\\" : L"\\\\?\\UNC\\";
size_t prefix_length = drive_absolute ? 4U : 8U;
size_t tail_offset = drive_absolute ? 0U : 2U;
size_t tail_length = (size_t)copied - tail_offset;
size_t extended_length = prefix_length + tail_length;
if (extended_length >= 32767U) {
return NULL;
}
wchar_t *prefixed =
cbm_arena_alloc(&process->stdout_arena, (extended_length + 1U) * sizeof(*prefixed));
if (!prefixed) {
return NULL;
}
wmemcpy(prefixed, prefix, prefix_length);
wmemcpy(prefixed + prefix_length, full + tail_offset, tail_length + 1U);
return prefixed;
}
static void cbm_win_close_spawn_handles(HANDLE nul, HANDLE input, HANDLE log, HANDLE stdout_capture,
LPPROC_THREAD_ATTRIBUTE_LIST attrs, bool attrs_init) {
if (attrs) {
if (attrs_init) {
DeleteProcThreadAttributeList(attrs);
}
free(attrs);
}
if (stdout_capture != INVALID_HANDLE_VALUE) {
CloseHandle(stdout_capture);
}
if (log != INVALID_HANDLE_VALUE) {
CloseHandle(log);
}
if (input != INVALID_HANDLE_VALUE) {
CloseHandle(input);
}
if (nul != INVALID_HANDLE_VALUE) {
CloseHandle(nul);
}
@@ -675,12 +768,19 @@ static int cbm_subprocess_spawn_win(cbm_subprocess_t *process) {
if (!built) {
return -1;
}
/* Everything a failure must release, so every failure leaves through
* the one exit at the end. */
HANDLE job = NULL;
HANDLE nul = INVALID_HANDLE_VALUE;
HANDLE input = INVALID_HANDLE_VALUE;
HANDLE log = INVALID_HANDLE_VALUE;
HANDLE stdout_capture = INVALID_HANDLE_VALUE;
LPPROC_THREAD_ATTRIBUTE_LIST attrs = NULL;
bool attrs_init = false;
wchar_t *wbin = cbm_utf8_to_wide(process->bin);
wchar_t *wcmdline = cbm_utf8_to_wide(cmdline);
if (!wbin || !wcmdline) {
free(wbin);
free(wcmdline);
return -1;
goto fail;
}
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits;
@@ -690,29 +790,39 @@ static int cbm_subprocess_spawn_win(cbm_subprocess_t *process) {
limits.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_JOB_MEMORY;
limits.JobMemoryLimit = (SIZE_T)process->memory_limit_bytes;
}
HANDLE job = CreateJobObjectW(NULL, NULL);
job = CreateJobObjectW(NULL, NULL);
if (!job ||
!SetInformationJobObject(job, JobObjectExtendedLimitInformation, &limits, sizeof(limits))) {
if (job) {
CloseHandle(job);
}
free(wbin);
free(wcmdline);
return -1;
goto fail;
}
SECURITY_ATTRIBUTES security;
ZeroMemory(&security, sizeof(security));
security.nLength = sizeof(security);
security.bInheritHandle = TRUE;
HANDLE nul = CreateFileW(L"NUL", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, &security, OPEN_EXISTING, 0, NULL);
HANDLE log = INVALID_HANDLE_VALUE;
nul = CreateFileW(L"NUL", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
&security, OPEN_EXISTING, 0, NULL);
if (nul == INVALID_HANDLE_VALUE) {
CloseHandle(job);
free(wbin);
free(wcmdline);
return -1;
goto fail;
}
if (process->stdin_file) {
wchar_t *winput = cbm_win_stdin_path(process);
/* Validate before any output can be truncated; recheck the opened
* handle rather than relying only on path attributes. */
DWORD attributes = winput ? GetFileAttributesW(winput) : INVALID_FILE_ATTRIBUTES;
if (attributes != INVALID_FILE_ATTRIBUTES &&
!(attributes &
(FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DEVICE))) {
input = CreateFileW(winput, GENERIC_READ, FILE_SHARE_READ, &security, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
}
BY_HANDLE_FILE_INFORMATION input_info;
if (input == INVALID_HANDLE_VALUE || GetFileType(input) != FILE_TYPE_DISK ||
!GetFileInformationByHandle(input, &input_info) ||
(input_info.dwFileAttributes &
(FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DEVICE))) {
goto fail;
}
}
if (process->log_file) {
wchar_t *wlog = cbm_path_to_wide(process->log_file);
@@ -727,41 +837,53 @@ static int cbm_subprocess_spawn_win(cbm_subprocess_t *process) {
free(wlog);
}
if (log == INVALID_HANDLE_VALUE) {
CloseHandle(nul);
CloseHandle(job);
free(wbin);
free(wcmdline);
return -1;
goto fail;
}
}
HANDLE inherit[2];
if (process->stdout_file) {
wchar_t *wstdout = cbm_path_to_wide(process->stdout_file);
if (wstdout) {
stdout_capture = CreateFileW(wstdout, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
&security, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
free(wstdout);
}
if (stdout_capture == INVALID_HANDLE_VALUE) {
goto fail;
}
}
HANDLE inherit[4];
SIZE_T inherit_count = 0;
inherit[inherit_count++] = nul;
if (input != INVALID_HANDLE_VALUE) {
inherit[inherit_count++] = input;
}
if (log != INVALID_HANDLE_VALUE) {
inherit[inherit_count++] = log;
}
if (stdout_capture != INVALID_HANDLE_VALUE) {
inherit[inherit_count++] = stdout_capture;
}
SIZE_T attrs_size = 0;
(void)InitializeProcThreadAttributeList(NULL, 1, 0, &attrs_size);
LPPROC_THREAD_ATTRIBUTE_LIST attrs = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(attrs_size);
bool attrs_init = attrs && InitializeProcThreadAttributeList(attrs, 1, 0, &attrs_size);
attrs = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(attrs_size);
attrs_init = attrs && InitializeProcThreadAttributeList(attrs, 1, 0, &attrs_size);
bool attrs_ready = attrs_init && UpdateProcThreadAttribute(
attrs, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inherit,
inherit_count * sizeof(inherit[0]), NULL, NULL);
if (!attrs_ready) {
cbm_win_close_spawn_handles(nul, log, attrs, attrs_init);
CloseHandle(job);
free(wbin);
free(wcmdline);
return -1;
goto fail;
}
STARTUPINFOEXW startup;
ZeroMemory(&startup, sizeof(startup));
startup.StartupInfo.cb = sizeof(startup);
startup.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
startup.StartupInfo.hStdInput = nul;
startup.StartupInfo.hStdOutput = log != INVALID_HANDLE_VALUE ? log : nul;
startup.StartupInfo.hStdInput = input != INVALID_HANDLE_VALUE ? input : nul;
startup.StartupInfo.hStdOutput = stdout_capture != INVALID_HANDLE_VALUE
? stdout_capture
: (log != INVALID_HANDLE_VALUE ? log : nul);
startup.StartupInfo.hStdError = log != INVALID_HANDLE_VALUE ? log : nul;
startup.lpAttributeList = attrs;
@@ -778,7 +900,7 @@ static int cbm_subprocess_spawn_win(cbm_subprocess_t *process) {
CreateProcessW(wbin, wcmdline, NULL, NULL, TRUE, flags, (LPVOID)env, NULL,
&startup.StartupInfo, &child);
cbm_git_child_env_free(env); /* CreateProcessW copied the block into the child */
cbm_win_close_spawn_handles(nul, log, attrs, attrs_init);
cbm_win_close_spawn_handles(nul, input, log, stdout_capture, attrs, attrs_init);
free(wbin);
free(wcmdline);
if (!created) {
@@ -810,6 +932,15 @@ static int cbm_subprocess_spawn_win(cbm_subprocess_t *process) {
process->process_id = child.dwProcessId;
process->job = job;
return 0;
fail:
cbm_win_close_spawn_handles(nul, input, log, stdout_capture, attrs, attrs_init);
if (job) {
CloseHandle(job);
}
free(wbin);
free(wcmdline);
return -1;
}
static bool cbm_win_job_active(cbm_subprocess_t *process, bool *known) {
@@ -989,6 +1120,14 @@ static int g_force_spawn_eagain = 0;
void cbm_subprocess_force_spawn_eagain_for_testing(int attempts) {
g_force_spawn_eagain = attempts > 0 ? attempts : 0;
}
static int g_hold_until_exit = 0;
void cbm_subprocess_hold_parent_until_child_exits_for_testing(int spawns) {
g_hold_until_exit = spawns > 0 ? spawns : 0;
}
static int g_observe_exiting = 0;
void cbm_subprocess_observe_exiting_child_for_testing(int spawns) {
g_observe_exiting = spawns > 0 ? spawns : 0;
}
int cbm_subprocess_pending_spawn_eagain_for_testing(void) {
return g_force_spawn_eagain;
}
@@ -1133,7 +1272,8 @@ cbm_fd_close_strategy_t cbm_subprocess_close_fds_from_for_testing(int lowfd, lon
/* fork+exec child setup. On Apple this runs ONLY for the exec-failure
* fallback (see cbm_posix_spawn_apple), which preserves the documented
* "bogus binary => child exits 127" contract across platforms. */
static void cbm_posix_child_exec(cbm_subprocess_t *process, int input, int output, long max_fd) {
static void cbm_posix_child_exec(cbm_subprocess_t *process, int input, int output, int error,
long max_fd) {
if (setpgid(0, 0) < 0) {
_exit(127);
}
@@ -1141,8 +1281,8 @@ static void cbm_posix_child_exec(cbm_subprocess_t *process, int input, int outpu
/* Never let a worker consume the MCP transport inherited as stdin. Only
* async-signal-safe calls are used between fork and exec. */
if (input < 0 || output < 0 || dup2(input, STDIN_FILENO) < 0 ||
dup2(output, STDOUT_FILENO) < 0 || dup2(output, STDERR_FILENO) < 0) {
if (input < 0 || output < 0 || error < 0 || dup2(input, STDIN_FILENO) < 0 ||
dup2(output, STDOUT_FILENO) < 0 || dup2(error, STDERR_FILENO) < 0) {
_exit(127);
}
if (input > STDERR_FILENO) {
@@ -1200,7 +1340,8 @@ static int cbm_posix_fd_at_least_three(int fd) {
* the child's close-everything loop, and the three dup2'd fds stay open
* because dup2 clears close-on-exec.
* posix_spawnp keeps execvp's PATH semantics for a bare tool name. */
static int cbm_posix_spawn_apple(cbm_subprocess_t *process, int input, int output, pid_t *pid_out) {
static int cbm_posix_spawn_apple(cbm_subprocess_t *process, int input, int output, int error,
pid_t *pid_out) {
/* posix_spawn is the PRIMARY path on macOS; fork+exec is only the
* exec-class fallback. Injection therefore has to live here too, or a test
* on macOS exercises nothing. */
@@ -1230,7 +1371,7 @@ static int cbm_posix_spawn_apple(cbm_subprocess_t *process, int input, int outpu
posix_spawnattr_setsigdefault(&attr, &all_signals) == 0 &&
posix_spawn_file_actions_adddup2(&actions, input, STDIN_FILENO) == 0 &&
posix_spawn_file_actions_adddup2(&actions, output, STDOUT_FILENO) == 0 &&
posix_spawn_file_actions_adddup2(&actions, output, STDERR_FILENO) == 0;
posix_spawn_file_actions_adddup2(&actions, error, STDERR_FILENO) == 0;
pid_t pid = -1;
int rc = configured ? posix_spawnp(&pid, process->bin, &actions, &attr, process->argv,
process->envp ? process->envp : environ)
@@ -1263,12 +1404,49 @@ static int cbm_posix_spawn_apple(cbm_subprocess_t *process, int input, int outpu
}
#endif
/* Our own unreaped child that setpgid/getpgid cannot find (ESRCH) is not
* running: it has exited, or is exiting -- macOS stops finding a child as it
* starts to exit, before waitid can report it. Wait until it is reapable;
* the wait ends with its exit, and WNOWAIT leaves it for the supervision to
* collect with its exit status. */
static bool cbm_posix_child_reapable(pid_t pid) {
for (;;) {
siginfo_t info;
memset(&info, 0, sizeof(info));
if (waitid(P_PID, (id_t)pid, &info, WEXITED | WNOWAIT) == 0) {
return info.si_pid == pid;
}
if (errno != EINTR) {
return false;
}
}
}
static int cbm_subprocess_spawn_posix(cbm_subprocess_t *process) {
int input_flags = O_RDONLY;
#ifdef O_CLOEXEC
input_flags |= O_CLOEXEC;
#endif
int input = cbm_posix_fd_at_least_three(open("/dev/null", input_flags));
if (process->stdin_file) {
/* O_NONBLOCK prevents waiting for a FIFO writer before fstat rejects
* non-regular input. No output has been opened at this point. */
input_flags |= O_NONBLOCK;
#ifdef O_NOFOLLOW
input_flags |= O_NOFOLLOW;
#endif
}
int input = cbm_posix_fd_at_least_three(
open(process->stdin_file ? process->stdin_file : "/dev/null", input_flags));
if (input < 0) {
return -1;
}
if (process->stdin_file) {
struct stat input_status;
if (fstat(input, &input_status) != 0 || !S_ISREG(input_status.st_mode)) {
(void)close(input);
return -1;
}
}
const char *target = process->log_file ? process->log_file : "/dev/null";
int output_flags = O_WRONLY | O_CREAT | O_TRUNC;
#ifdef O_CLOEXEC
@@ -1278,13 +1456,8 @@ static int cbm_subprocess_spawn_posix(cbm_subprocess_t *process) {
output_flags |= O_NOFOLLOW;
#endif
int output = cbm_posix_fd_at_least_three(open(target, output_flags, 0600));
if (input < 0 || output < 0) {
if (input >= 0) {
(void)close(input);
}
if (output >= 0) {
(void)close(output);
}
if (output < 0) {
(void)close(input);
return -1;
}
struct stat output_status;
@@ -1300,6 +1473,23 @@ static int cbm_subprocess_spawn_posix(cbm_subprocess_t *process) {
return -1;
}
int stdout_capture = -1;
if (process->stdout_file) {
stdout_capture =
cbm_posix_fd_at_least_three(open(process->stdout_file, output_flags, 0600));
struct stat stdout_status;
if (stdout_capture < 0 || fstat(stdout_capture, &stdout_status) != 0 ||
!S_ISREG(stdout_status.st_mode) || fchmod(stdout_capture, 0600) != 0) {
if (stdout_capture >= 0) {
(void)close(stdout_capture);
}
(void)close(input);
(void)close(output);
return -1;
}
}
int child_output = stdout_capture >= 0 ? stdout_capture : output;
long max_fd = sysconf(_SC_OPEN_MAX);
if (max_fd < 0 || max_fd > 1048576L) {
max_fd = 65536L;
@@ -1307,11 +1497,11 @@ static int cbm_subprocess_spawn_posix(cbm_subprocess_t *process) {
pid_t pid = -1;
#ifdef __APPLE__
int spawn_rc = cbm_posix_spawn_apple(process, input, output, &pid);
int spawn_rc = cbm_posix_spawn_apple(process, input, child_output, output, &pid);
for (int attempt = 0; spawn_rc == CBM_SPAWN_RETRY && attempt < CBM_SPAWN_RETRY_ATTEMPTS;
attempt++) {
cbm_spawn_backoff(attempt);
spawn_rc = cbm_posix_spawn_apple(process, input, output, &pid);
spawn_rc = cbm_posix_spawn_apple(process, input, child_output, output, &pid);
}
if (spawn_rc == CBM_SPAWN_RETRY) {
spawn_rc = -1; /* still exhausted after backoff: a real failure */
@@ -1319,6 +1509,9 @@ static int cbm_subprocess_spawn_posix(cbm_subprocess_t *process) {
if (spawn_rc < 0) {
(void)close(input);
(void)close(output);
if (stdout_capture >= 0) {
(void)close(stdout_capture);
}
return -1;
}
if (spawn_rc > 0) { /* exec-class failure: reproduce the fork+exec 127 */
@@ -1326,10 +1519,13 @@ static int cbm_subprocess_spawn_posix(cbm_subprocess_t *process) {
if (pid < 0) {
(void)close(input);
(void)close(output);
if (stdout_capture >= 0) {
(void)close(stdout_capture);
}
return -1;
}
if (pid == 0) {
cbm_posix_child_exec(process, input, output, max_fd);
cbm_posix_child_exec(process, input, child_output, output, max_fd);
}
}
#else
@@ -1337,21 +1533,54 @@ static int cbm_subprocess_spawn_posix(cbm_subprocess_t *process) {
if (pid < 0) {
(void)close(input);
(void)close(output);
if (stdout_capture >= 0) {
(void)close(stdout_capture);
}
return -1;
}
if (pid == 0) {
cbm_posix_child_exec(process, input, output, max_fd);
cbm_posix_child_exec(process, input, child_output, output, max_fd);
}
#endif
(void)close(input);
(void)close(output);
if (stdout_capture >= 0) {
(void)close(stdout_capture);
}
#ifdef CBM_ENABLE_TEST_SEAMS
if (g_hold_until_exit > 0) {
g_hold_until_exit--;
siginfo_t held;
(void)waitid(P_PID, (id_t)pid, &held, WEXITED | WNOWAIT);
}
#endif
/* Parent and child both establish the group, removing scheduler-order races.
* If the child won and already execed, EACCES is accepted only after proving
* that its process group is the expected isolated one. */
bool contained = setpgid(pid, pid) == 0;
* that its process group is the expected isolated one. A child that has
* EXITED, or is exiting, is a finished run, not a failed spawn: macOS no
* longer shows such an unreaped child to setpgid/getpgid (ESRCH), and on a
* loaded machine a short command (`git config`) gets there before the
* parent does. Its group was set before exec, by the spawn attributes or by
* the child itself; the parent waits until it is reapable, and the
* supervision below reaps it with its real exit status. */
bool observe_exiting = false;
#ifdef CBM_ENABLE_TEST_SEAMS
if (g_observe_exiting > 0) {
g_observe_exiting--;
observe_exiting = true;
}
#endif
bool contained = !observe_exiting && setpgid(pid, pid) == 0;
if (observe_exiting) {
errno = ESRCH;
}
if (!contained && (errno == EACCES || errno == EPERM || errno == ESRCH)) {
contained = getpgid(pid) == pid;
bool missing = errno == ESRCH;
contained = !observe_exiting && getpgid(pid) == pid;
if (!contained && (missing || errno == ESRCH)) {
contained = cbm_posix_child_reapable(pid);
}
}
if (!contained) {
(void)kill(pid, SIGKILL);
+26 -2
View File
@@ -63,8 +63,9 @@ typedef struct {
* be an absolute path ending in cmd.exe and argv must be
* NULL. The fixed /D /S /V:OFF /C prefix is added while
* this payload is copied verbatim for cmd.exe to parse. */
const char *log_file; /* child stdout+stderr are redirected here and tailed;
* NULL => discard child output, no tailing */
const char *log_file; /* stderr and, when stdout_file is NULL, stdout are
* redirected here and tailed; NULL => discard those
* streams, no tailing */
cbm_proc_log_cb on_log_line; /* optional per-line callback */
void *log_ud; /* user data for on_log_line */
int quiet_timeout_ms; /* <= 0 => no timeout; else kill+HANG after this many
@@ -77,6 +78,20 @@ typedef struct {
bool strip_git_repo_env; /* child env omits git's repository-local variables
* (foundation/git_env.h) — set for every git spawn so
* an inherited GIT_DIR never overrides `git -C` */
const char *stdout_file; /* optional separate stdout capture; NULL preserves
* merged log_file behavior. Must be nonempty and
* distinct from log_file; caller supplies private,
* non-aliasing paths and owns capture-file cleanup.
* Not tailed; quiet_timeout_ms observes log_file only.
* delete_log_on_exit never deletes this file. */
const char *stdin_file; /* optional private, immutable regular binary input;
* NULL => null device, never inherited stdin.
* Must be nonempty and non-aliasing with log_file and
* stdout_file; exact equal strings are rejected.
* Caller closes its writer before spawn and retains
* the file until the child tree is quiescent.
* The child chooses its CRT binary mode on Windows.
* Never modified or deleted by this API. */
} cbm_proc_opts_t;
#define CBM_SUBPROCESS_DEFAULT_CANCEL_GRACE_MS 1000
@@ -191,6 +206,15 @@ bool cbm_build_win_cmd_payload(char *buf, size_t cap, const char *cmd_executable
* of hoping a loaded machine reproduces it. Test builds only. */
void cbm_subprocess_force_spawn_eagain_for_testing(int attempts);
int cbm_subprocess_pending_spawn_eagain_for_testing(void);
/* Hold the parent of the next N POSIX spawns until the child has exited (left
* unreaped) before it checks the child's process group: the order a loaded
* machine produces when a short command finishes before the parent runs
* again. Test builds only. */
void cbm_subprocess_hold_parent_until_child_exits_for_testing(int spawns);
/* For the next N POSIX spawns, the parent's group check observes what macOS
* shows for a child that is EXITING: setpgid and getpgid answer ESRCH while
* waitid does not report it yet. Test builds only. */
void cbm_subprocess_observe_exiting_child_for_testing(int spawns);
#endif
/* How the POSIX fork+exec child closed its inherited descriptors (#1484). */
+8
View File
@@ -0,0 +1,8 @@
#ifndef CBM_TEST_SELECTION_H
#define CBM_TEST_SELECTION_H
/* Bytes in one executable suite or suite:test token, excluding whitespace,
* the line terminator and storage NUL. An individual token includes its colon. */
enum { CBM_TEST_SELECTION_TOKEN_MAX = 511 };
#endif /* CBM_TEST_SELECTION_H */
+74 -17
View File
@@ -10,7 +10,41 @@
#include <stdlib.h>
#include <wchar.h>
static inline wchar_t *cbm_utf8_to_wide(const char *utf8) {
#include "mem_core.h"
/* Converter heaps. The `*_in(cls, ...)` forms allocate through the memory
* core and their result is released with cbm_free(cls, p). The legacy names
* allocate with the C library for the callers that still release with free();
* that route is the residual the memory-core ratchet drives down, so new
* callers use the `_in` forms. */
typedef struct {
int core;
cbm_mem_class_t cls;
} cbm_wide_heap_t;
static inline cbm_wide_heap_t cbm_wide_heap_core(cbm_mem_class_t cls) {
cbm_wide_heap_t heap = {1, cls};
return heap;
}
static inline cbm_wide_heap_t cbm_wide_heap_libc(void) {
cbm_wide_heap_t heap = {0, CBM_MEM_CLASS_OTHER};
return heap;
}
static inline void *cbm_wide_heap_alloc(cbm_wide_heap_t heap, size_t bytes) {
return heap.core ? cbm_alloc(heap.cls, bytes) : malloc(bytes);
}
static inline void cbm_wide_heap_free(cbm_wide_heap_t heap, void *block) {
if (heap.core) {
cbm_free(heap.cls, block);
} else {
free(block);
}
}
static inline wchar_t *cbm_utf8_to_wide_heap(cbm_wide_heap_t heap, const char *utf8) {
if (!utf8) {
return NULL;
}
@@ -18,14 +52,22 @@ static inline wchar_t *cbm_utf8_to_wide(const char *utf8) {
if (len <= 0) {
return NULL;
}
wchar_t *w = (wchar_t *)malloc((size_t)len * sizeof(wchar_t));
wchar_t *w = (wchar_t *)cbm_wide_heap_alloc(heap, (size_t)len * sizeof(wchar_t));
if (!w || MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8, -1, w, len) != len) {
free(w);
cbm_wide_heap_free(heap, w);
return NULL;
}
return w;
}
static inline wchar_t *cbm_utf8_to_wide(const char *utf8) {
return cbm_utf8_to_wide_heap(cbm_wide_heap_libc(), utf8);
}
static inline wchar_t *cbm_utf8_to_wide_in(cbm_mem_class_t cls, const char *utf8) {
return cbm_utf8_to_wide_heap(cbm_wide_heap_core(cls), utf8);
}
static inline int cbm_win_path_separator(wchar_t value) {
return value == L'\\' || value == L'/';
}
@@ -54,7 +96,8 @@ static inline void cbm_win_path_normalize_wide_separators(wchar_t *path) {
* This makes behavior independent of the process manifest and LongPathsEnabled
* policy. Never use this for non-file strings (command lines, pipe names,
* registry paths). */
static inline wchar_t *cbm_wide_path_to_extended(const wchar_t *wide_path) {
static inline wchar_t *cbm_wide_path_to_extended_heap(cbm_wide_heap_t heap,
const wchar_t *wide_path) {
if (!wide_path) {
return NULL;
}
@@ -62,7 +105,7 @@ static inline wchar_t *cbm_wide_path_to_extended(const wchar_t *wide_path) {
if (input_length >= 32767U) {
return NULL;
}
wchar_t *wide = (wchar_t *)malloc((input_length + 1U) * sizeof(*wide));
wchar_t *wide = (wchar_t *)cbm_wide_heap_alloc(heap, (input_length + 1U) * sizeof(*wide));
if (!wide) {
return NULL;
}
@@ -77,11 +120,12 @@ static inline wchar_t *cbm_wide_path_to_extended(const wchar_t *wide_path) {
}
DWORD needed = GetFullPathNameW(wide, 0, NULL, NULL);
wchar_t *full = needed > 0U ? (wchar_t *)malloc((size_t)needed * sizeof(wchar_t)) : NULL;
wchar_t *full =
needed > 0U ? (wchar_t *)cbm_wide_heap_alloc(heap, (size_t)needed * sizeof(wchar_t)) : NULL;
DWORD copied = full ? GetFullPathNameW(wide, needed, full, NULL) : 0U;
free(wide);
cbm_wide_heap_free(heap, wide);
if (!full || copied == 0U || copied >= needed) {
free(full);
cbm_wide_heap_free(heap, full);
return NULL;
}
cbm_win_path_normalize_wide_separators(full);
@@ -92,7 +136,7 @@ static inline wchar_t *cbm_wide_path_to_extended(const wchar_t *wide_path) {
full[1] == L':' && full[2] == L'\\';
int unc_absolute = copied >= 5U && full[0] == L'\\' && full[1] == L'\\';
if (!drive_absolute && !unc_absolute) {
free(full);
cbm_wide_heap_free(heap, full);
return NULL;
}
@@ -103,27 +147,40 @@ static inline wchar_t *cbm_wide_path_to_extended(const wchar_t *wide_path) {
size_t extended_length = prefix_length + tail_length;
/* The documented extended-length limit includes the terminating NUL. */
if (extended_length >= 32767U) {
free(full);
cbm_wide_heap_free(heap, full);
return NULL;
}
wchar_t *prefixed = (wchar_t *)malloc((extended_length + 1U) * sizeof(wchar_t));
wchar_t *prefixed =
(wchar_t *)cbm_wide_heap_alloc(heap, (extended_length + 1U) * sizeof(wchar_t));
if (!prefixed) {
free(full);
cbm_wide_heap_free(heap, full);
return NULL;
}
wmemcpy(prefixed, prefix, prefix_length);
wmemcpy(prefixed + prefix_length, full + tail_offset, tail_length + 1U);
free(full);
cbm_wide_heap_free(heap, full);
return prefixed;
}
static inline wchar_t *cbm_path_to_wide(const char *utf8_path) {
wchar_t *wide = cbm_utf8_to_wide(utf8_path);
wchar_t *extended = cbm_wide_path_to_extended(wide);
free(wide);
static inline wchar_t *cbm_wide_path_to_extended(const wchar_t *wide_path) {
return cbm_wide_path_to_extended_heap(cbm_wide_heap_libc(), wide_path);
}
static inline wchar_t *cbm_path_to_wide_heap(cbm_wide_heap_t heap, const char *utf8_path) {
wchar_t *wide = cbm_utf8_to_wide_heap(heap, utf8_path);
wchar_t *extended = cbm_wide_path_to_extended_heap(heap, wide);
cbm_wide_heap_free(heap, wide);
return extended;
}
static inline wchar_t *cbm_path_to_wide(const char *utf8_path) {
return cbm_path_to_wide_heap(cbm_wide_heap_libc(), utf8_path);
}
static inline wchar_t *cbm_path_to_wide_in(cbm_mem_class_t cls, const char *utf8_path) {
return cbm_path_to_wide_heap(cbm_wide_heap_core(cls), utf8_path);
}
/* GetModuleFileNameW can preserve the \\?\ spelling used to launch a process.
* Internal UTF-8 paths use conventional DOS/UNC spelling and add the prefix
* only at the Win32 file-API boundary, so remove it before widening callers
+188 -3
View File
@@ -835,6 +835,162 @@ void cbm_gbuf_set_next_id(cbm_gbuf_t *gb, int64_t next_id) {
gb->next_id = next_id;
}
/* ── Definition variants ─────────────────────────────────────────────
* One name defined more than once under mutually exclusive conditions —
* C-family #if/#else, Rust #[cfg], build-tagged files, a definition under an
* `if` — is ONE identity: one QN, one node, whose edges are the union of
* every variant's body. The collision rule below keeps one canonical winner
* for name, file, lines and properties; every variant's span survives in the
* node's `variants` property ([{"file_path","start_line","end_line"}],
* sorted, unique), so a change in any variant maps to the node. Recorded only
* between definitions of the same label with real spans: a different kind
* sharing the QN (C: struct vs function) is no variant, a span on line 0 is
* no location (delta-route proxies), and a full tie is the same entity. */
static void *gb_yy_malloc(void *ctx, size_t size) {
(void)ctx;
return cbm_alloc(CBM_MEM_CLASS_GBUF_STRING, size);
}
static void *gb_yy_realloc(void *ctx, void *block, size_t old_size, size_t size) {
(void)ctx;
(void)old_size;
return cbm_realloc(CBM_MEM_CLASS_GBUF_STRING, block, size);
}
static void gb_yy_free(void *ctx, void *block) {
(void)ctx;
cbm_free(CBM_MEM_CLASS_GBUF_STRING, block);
}
static const yyjson_alc gb_yy_alc = {gb_yy_malloc, gb_yy_realloc, gb_yy_free, NULL};
enum { GB_VARIANTS_MAX = 64 };
typedef struct {
const char *file;
int start;
int end;
} gb_span_t;
static bool gb_variant_label(const char *label) {
static const char *const labels[] = {"Function", "Method", "Class", "Interface", "Enum",
"Struct", "Trait", "Type", "Macro"};
for (size_t i = 0; label && i < sizeof(labels) / sizeof(labels[0]); i++) {
if (strcmp(label, labels[i]) == 0) {
return true;
}
}
return false;
}
static int gb_span_cmp(const void *a, const void *b) {
const gb_span_t *x = a;
const gb_span_t *y = b;
int c = strcmp(x->file, y->file);
if (c == 0) {
c = (x->start > y->start) - (x->start < y->start);
}
if (c == 0) {
c = (x->end > y->end) - (x->end < y->end);
}
return c;
}
/* A definition's spans: its `variants` property, else its own span. */
static int gb_spans_of(yyjson_doc *doc, const char *file, int start, int end, gb_span_t *out,
int n) {
yyjson_val *list = doc ? yyjson_obj_get(yyjson_doc_get_root(doc), "variants") : NULL;
if (yyjson_is_arr(list) && yyjson_arr_size(list) > 0) {
size_t i;
size_t max;
yyjson_val *v;
yyjson_arr_foreach(list, i, max, v) {
const char *f = yyjson_get_str(yyjson_obj_get(v, "file_path"));
int s = (int)yyjson_get_int(yyjson_obj_get(v, "start_line"));
int e = (int)yyjson_get_int(yyjson_obj_get(v, "end_line"));
if (f && s > 0 && n < GB_VARIANTS_MAX) {
out[n++] = (gb_span_t){f, s, e};
}
}
return n;
}
if (start > 0 && n < GB_VARIANTS_MAX) {
out[n++] = (gb_span_t){file ? file : "", start, end};
}
return n;
}
/* The survivor's properties with the union of both definitions' variant
* spans. Returns a GBUF_STRING-owned JSON string, or NULL when there is
* nothing to record (fewer than two distinct spans) or on any failure — the
* survivor then keeps its own properties: a lost variants list costs
* precision, never data. */
static char *gb_variants_props(const char *survivor_props, const char *a_props, const char *a_file,
int a_start, int a_end, const char *b_props, const char *b_file,
int b_start, int b_end) {
if (a_start <= 0 || b_start <= 0) {
return NULL;
}
yyjson_doc *a_doc =
a_props ? yyjson_read_opts((char *)a_props, strlen(a_props), 0, &gb_yy_alc, NULL) : NULL;
yyjson_doc *b_doc =
b_props ? yyjson_read_opts((char *)b_props, strlen(b_props), 0, &gb_yy_alc, NULL) : NULL;
gb_span_t spans[GB_VARIANTS_MAX * 2];
int n = gb_spans_of(a_doc, a_file, a_start, a_end, spans, 0);
n = gb_spans_of(b_doc, b_file, b_start, b_end, spans, n);
qsort(spans, (size_t)n, sizeof(spans[0]), gb_span_cmp);
int unique = 0;
for (int i = 0; i < n; i++) {
if (unique == 0 || gb_span_cmp(&spans[unique - 1], &spans[i]) != 0) {
spans[unique++] = spans[i];
}
}
char *out = NULL;
if (unique >= 2) {
yyjson_mut_doc *mut = yyjson_mut_doc_new(&gb_yy_alc);
yyjson_doc *s_doc = survivor_props
? yyjson_read_opts((char *)survivor_props, strlen(survivor_props),
0, &gb_yy_alc, NULL)
: NULL;
yyjson_mut_val *root = s_doc && yyjson_is_obj(yyjson_doc_get_root(s_doc))
? yyjson_val_mut_copy(mut, yyjson_doc_get_root(s_doc))
: yyjson_mut_obj(mut);
yyjson_mut_val *list = yyjson_mut_arr(mut);
for (int i = 0; root && list && i < unique; i++) {
yyjson_mut_val *v = yyjson_mut_arr_add_obj(mut, list);
yyjson_mut_obj_add_strcpy(mut, v, "file_path", spans[i].file);
yyjson_mut_obj_add_int(mut, v, "start_line", spans[i].start);
yyjson_mut_obj_add_int(mut, v, "end_line", spans[i].end);
}
if (mut && root && list) {
yyjson_mut_obj_remove_key(root, "variants");
yyjson_mut_obj_add_val(mut, root, "variants", list);
yyjson_mut_doc_set_root(mut, root);
out = yyjson_mut_write_opts(mut, 0, &gb_yy_alc, NULL, NULL);
}
yyjson_doc_free(s_doc);
yyjson_mut_doc_free(mut);
}
yyjson_doc_free(a_doc);
yyjson_doc_free(b_doc);
return out;
}
/* Record the variant spans of a same-QN collision on the surviving node. */
static void gb_record_variants(cbm_gbuf_node_t *survivor, const char *a_label, const char *a_props,
const char *a_file, int a_start, int a_end, const char *b_label,
const char *b_props, const char *b_file, int b_start, int b_end) {
if (!gb_variant_label(a_label) || !b_label || strcmp(a_label, b_label) != 0) {
return;
}
char *merged = gb_variants_props(survivor->properties_json, a_props, a_file, a_start, a_end,
b_props, b_file, b_start, b_end);
if (merged) {
cbm_free(CBM_MEM_CLASS_GBUF_STRING, survivor->properties_json);
survivor->properties_json = merged;
}
}
/* ── Node operations ─────────────────────────────────────────────── */
int64_t cbm_gbuf_upsert_node(cbm_gbuf_t *gb, const char *label, const char *name,
@@ -892,8 +1048,19 @@ int64_t cbm_gbuf_upsert_node(cbm_gbuf_t *gb, const char *label, const char *name
c = strcmp(existing->label ? existing->label : "", label ? label : "");
}
if (c > 0) {
gb_record_variants(existing, existing->label, existing->properties_json,
existing->file_path, existing->start_line, existing->end_line, label,
properties_json, file_path, start_line, end_line);
return existing->id; /* existing entity is the canonical winner */
}
/* The arrival wins: its properties, plus every variant span both carry. */
char *variant_props = NULL;
if (gb_variant_label(label) && existing->label && strcmp(existing->label, label) == 0) {
variant_props =
gb_variants_props(properties_json, existing->properties_json, existing->file_path,
existing->start_line, existing->end_line, properties_json,
file_path, start_line, end_line);
}
/* Update in-place. name/properties are strdup'd BEFORE freeing old ones
* (callers may pass existing->name as an argument). label/file_path are
* interned: gb_intern returns a stable pool pointer (idempotent even when
@@ -903,7 +1070,9 @@ int64_t cbm_gbuf_upsert_node(cbm_gbuf_t *gb, const char *label, const char *name
* label/name — cbm_gbuf_find_by_label/name then missed or mis-listed it,
* which is how the flickering Function set reached the semantic pass). */
char *new_name = heap_strdup(name);
char *new_props = properties_json ? heap_strdup(properties_json) : NULL;
char *new_props = variant_props ? variant_props
: properties_json ? heap_strdup(properties_json)
: NULL;
const char *new_label_interned = gb_intern(gb, label);
bool label_changed = !existing->label || !new_label_interned ||
strcmp(existing->label, new_label_interned) != 0;
@@ -1493,7 +1662,22 @@ static void merge_update_existing(cbm_gbuf_t *dst, cbm_gbuf_node_t *existing,
c = strcmp(existing->label ? existing->label : "", sn->label ? sn->label : "");
}
bool sn_wins = c <= 0;
if (!sn_wins) {
gb_record_variants(existing, existing->label, existing->properties_json,
existing->file_path, existing->start_line, existing->end_line,
sn->label, sn->properties_json, sn->file_path, sn->start_line,
sn->end_line);
}
if (sn_wins) {
/* src's properties, plus every variant span both carry. */
char *variant_props = NULL;
if (gb_variant_label(sn->label) && existing->label &&
strcmp(existing->label, sn->label) == 0) {
variant_props = gb_variants_props(sn->properties_json, existing->properties_json,
existing->file_path, existing->start_line,
existing->end_line, sn->properties_json,
sn->file_path, sn->start_line, sn->end_line);
}
/* Keep the secondary indexes consistent when the surviving
* label/name changes (the old code left the node listed under its
* original label/name, so find_by_label/name mis-listed it). */
@@ -1518,9 +1702,10 @@ static void merge_update_existing(cbm_gbuf_t *dst, cbm_gbuf_node_t *existing,
existing->file_path = (char *)gb_intern(dst, sn->file_path);
existing->start_line = sn->start_line;
existing->end_line = sn->end_line;
if (sn->properties_json) {
if (variant_props || sn->properties_json) {
cbm_free(CBM_MEM_CLASS_GBUF_STRING, existing->properties_json);
existing->properties_json = heap_strdup(sn->properties_json);
existing->properties_json =
variant_props ? variant_props : heap_strdup(sn->properties_json);
}
if (label_changed) {
node_ptr_array_t *by_label = get_or_create_node_array(
+5
View File
@@ -33,6 +33,7 @@
#include "daemon/version_cohort.h"
#include "mcp/mcp.h"
#include "mcp/index_supervisor.h"
#include "mcp/test_impact_engine.h"
#include "cli/cli.h"
#include "cli/progress_sink.h"
#include "foundation/constants.h"
@@ -1280,6 +1281,10 @@ static int handle_subcommand(int argc, char **argv, cbm_project_lock_manager_t *
cbm_mem_init(cbm_mem_ram_fraction_for_total(cbm_system_info().total_ram));
return cbm_cmd_hook_augment(argc - i - SKIP_ONE, argv + i + SKIP_ONE);
}
if (strcmp(argv[i], "test-impact") == 0) {
cbm_mem_init(cbm_mem_ram_fraction_for_total(cbm_system_info().total_ram));
return cbm_test_impact_cli(argc - i - SKIP_ONE, argv + i + SKIP_ONE);
}
if (strcmp(argv[i], "install") == 0) {
return cbm_cmd_install(argc - i - SKIP_ONE, argv + i + SKIP_ONE);
}
+683 -33
View File
@@ -56,7 +56,10 @@ enum {
#define SLEN(s) (sizeof(s) - 1)
#include "mcp/mcp.h"
#include "mcp/mcp_internal.h"
#include "mcp/test_impact.h"
#include "mcp/test_impact_engine.h"
#include "store/store.h"
#include "store/store_impact.h"
#include <sqlite3.h>
#include "cypher/cypher.h"
#include "discover/discover.h"
@@ -68,6 +71,7 @@ enum {
#include "watcher/watcher.h"
#include "foundation/mem.h"
#include "foundation/mem_core.h"
#include "foundation/arena.h"
#include "foundation/diagnostics.h"
#include "foundation/platform.h"
#include "foundation/compat.h"
@@ -775,10 +779,24 @@ static const tool_def_t TOOLS[] = {
{"detect_changes", "Map a Git diff to files and impact. Page with snapshot cursors.",
"{\"type\":\"object\",\"properties\":{\"project\":{\"type\":\"string\"},\"scope\":{\"type\":"
"\"string\",\"enum\":[\"files\",\"impact\"],\"default\":\"impact\"},"
"\"string\",\"enum\":[\"files\",\"impact\",\"tests\"],\"default\":\"impact\"},"
"\"direction\":{\"type\":\"string\",\"enum\":[\"inbound\",\"outbound\",\"both\"],\"default\":"
"\"inbound\",\"description\":\"inbound=callers; outbound=dependencies; both=union.\"},"
"\"depth\":{\"type\":\"integer\",\"default\":2},"
"\"edge_types\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},"
"\"description\":\"Inbound walk; default CALLS.\"},"
"\"fixpoint\":{\"type\":\"boolean\",\"description\":\"Walk to closure; no depth.\"},"
"\"via\":{\"type\":\"boolean\",\"description\":\"Add via_qn, via_edge.\"},"
"\"seeds\":{\"type\":\"string\",\"enum\":[\"none\",\"list\"],\"default\":\"none\","
"\"description\":\"List changed-symbol seeds with independent pagination.\"},"
"\"seed_filter\":{\"type\":\"string\",\"enum\":[\"all\",\"product\"],\"default\":\"all\","
"\"description\":\"Product excludes indexed test metadata and configured test globs.\"},"
"\"config_path\":{\"type\":\"string\",\"description\":\"Project config override; absolute or "
"project-relative.\"},"
"\"seed_limit\":{\"type\":\"integer\",\"default\":50,\"minimum\":0,\"maximum\":1000},"
"\"seed_offset\":{\"type\":\"integer\",\"default\":0,\"minimum\":0},"
"\"seed_cursor\":{\"type\":\"string\",\"description\":\"Continue seeds:list; excludes nonzero "
"seed_offset.\"},"
"\"limit\":{\"type\":\"integer\",\"default\":200,\"maximum\":5000},"
"\"impact_offset\":{\"type\":\"integer\",\"default\":0,\"minimum\":0},"
"\"impact_cursor\":{\"type\":\"string\"},"
@@ -1688,8 +1706,19 @@ int cbm_mcp_get_int_arg(const char *args_json, const char *key, int default_val)
yyjson_val *root = yyjson_doc_get_root(doc);
yyjson_val *val = yyjson_obj_get(root, key);
int result = default_val;
if (val && yyjson_is_int(val)) {
result = yyjson_get_int(val);
/* yyjson_get_int truncates 64-bit integers to int (2^32 + 1 became 1);
* read the full width and treat anything outside int range like a
* non-integer, i.e. fall back to the caller's default. */
if (val && yyjson_is_sint(val)) {
int64_t parsed = yyjson_get_sint(val);
if (parsed >= INT_MIN && parsed <= INT_MAX) {
result = (int)parsed;
}
} else if (val && yyjson_is_uint(val)) {
uint64_t parsed = yyjson_get_uint(val);
if (parsed <= (uint64_t)INT_MAX) {
result = (int)parsed;
}
}
yyjson_doc_free(doc);
return result;
@@ -15905,12 +15934,169 @@ static bool detect_is_seedable_label(const char *lb) {
strcmp(lb, "Section") != 0;
}
/* Seed presentation is optional. The default request retains its traversal,
* ordering, output bytes and cursor identity. */
typedef struct {
bool list;
bool product;
int limit;
int offset;
char cursor[80];
char config_path[CBM_SZ_4K];
} detect_seed_request_t;
typedef struct {
const char *qn;
const char *label;
const char *file;
const char *reason;
int start;
int end;
} detect_seed_row_t;
typedef struct {
CBMArena arena;
detect_seed_row_t *rows;
int count;
int capacity;
bool failed;
bool config_failed;
cbm_test_config_t *config;
} detect_seed_rows_t;
static bool detect_seed_string(yyjson_val *value, const char **out) {
*out = yyjson_get_str(value);
return *out && strlen(*out) == yyjson_get_len(value);
}
static void detect_seed_parse(const char *args, detect_seed_request_t *out, char *err, size_t len) {
*out = (detect_seed_request_t){.limit = 50};
yyjson_doc *doc = yyjson_read(args, strlen(args), 0);
yyjson_val *root = doc ? yyjson_doc_get_root(doc) : NULL;
const char *keys[] = {"seeds", "seed_filter", "seed_limit",
"seed_offset", "seed_cursor", "config_path"};
for (int i = 0; i < 6 && !err[0]; i++) {
yyjson_val *value = yyjson_obj_get(root, keys[i]);
if (!value || yyjson_is_null(value))
continue;
const char *text = NULL;
if (i < 2) {
const char *off = i == 0 ? "none" : "all";
const char *on = i == 0 ? "list" : "product";
if (!detect_seed_string(value, &text) || (strcmp(text, off) && strcmp(text, on))) {
snprintf(err, len, "%s must be \"%s\" or \"%s\"", keys[i], off, on);
} else if (i == 0)
out->list = strcmp(text, on) == 0;
else
out->product = strcmp(text, on) == 0;
} else if (i < 4) {
uint64_t maximum = i == 2 ? 1000 : INT_MAX;
if (!yyjson_is_uint(value) || yyjson_get_uint(value) > maximum) {
snprintf(err, len, "%s must be an integer from 0 to %llu", keys[i],
(unsigned long long)maximum);
} else if (i == 2)
out->limit = (int)yyjson_get_uint(value);
else
out->offset = (int)yyjson_get_uint(value);
} else {
char *destination = i == 4 ? out->cursor : out->config_path;
size_t capacity = i == 4 ? sizeof(out->cursor) : sizeof(out->config_path);
if (!detect_seed_string(value, &text) || strlen(text) >= capacity ||
(i == 5 && !*text)) {
snprintf(err, len, "%s must be a nonempty string within the path/cursor size limit",
keys[i]);
} else {
memcpy(destination, text, strlen(text) + 1);
#ifdef _WIN32
if (i == 5)
for (char *p = destination; *p; p++)
if (*p == '\\')
*p = '/';
#endif
}
}
}
if (!err[0] && out->cursor[0] && !out->list) {
snprintf(err, len, "seed_cursor requires seeds:\"list\"");
}
yyjson_doc_free(doc);
}
static bool detect_seed_is_test(const cbm_node_t *node) {
if (!node->properties_json)
return false;
yyjson_doc *doc = yyjson_read(node->properties_json, strlen(node->properties_json), 0);
yyjson_val *root = doc ? yyjson_doc_get_root(doc) : NULL;
yyjson_val *test = yyjson_obj_get(root, "is_test");
yyjson_val *role = yyjson_obj_get(root, "test_role");
const char *role_name = yyjson_get_str(role);
bool is_test = yyjson_get_bool(test) || (yyjson_is_int(test) && yyjson_get_sint(test) == 1) ||
(role_name && strlen(role_name) == yyjson_get_len(role) &&
(strcmp(role_name, "case") == 0 || strcmp(role_name, "suite") == 0 ||
strcmp(role_name, "helper") == 0));
yyjson_doc_free(doc);
return is_test;
}
static void detect_seed_add_row(detect_seed_rows_t *rows, const cbm_node_t *node, bool hunk) {
if (rows->failed)
return;
if (rows->count == rows->capacity) {
if (rows->capacity > INT_MAX / 2) {
rows->failed = true;
return;
}
int capacity = rows->capacity ? rows->capacity * 2 : 16;
if ((size_t)capacity > SIZE_MAX / sizeof(*rows->rows)) {
rows->failed = true;
return;
}
detect_seed_row_t *next = cbm_arena_alloc(&rows->arena, (size_t)capacity * sizeof(*next));
if (!next) {
rows->failed = true;
return;
}
if (rows->count)
memcpy(next, rows->rows, (size_t)rows->count * sizeof(*next));
rows->rows = next;
rows->capacity = capacity;
}
detect_seed_row_t *row = &rows->rows[rows->count++];
*row = (detect_seed_row_t){
.qn = cbm_arena_strdup(&rows->arena, node->qualified_name ? node->qualified_name : ""),
.label = cbm_arena_strdup(&rows->arena, node->label ? node->label : ""),
.file = cbm_arena_strdup(&rows->arena, node->file_path ? node->file_path : ""),
.reason = hunk ? "hunk" : "whole_file",
.start = node->start_line,
.end = node->end_line};
rows->failed = !row->qn || !row->label || !row->file;
}
static int detect_seed_row_compare(const void *left, const void *right) {
const detect_seed_row_t *a = left, *b = right;
int cmp = strcmp(a->qn, b->qn);
if (!cmp)
cmp = strcmp(a->file, b->file);
if (!cmp)
cmp = (a->start > b->start) - (a->start < b->start);
if (!cmp)
cmp = (a->end > b->end) - (a->end < b->end);
if (!cmp)
cmp = strcmp(a->label, b->label);
return cmp;
}
static void detect_collect_seeds(cbm_store_t *store, const char *project, const char *file,
const cbm_changed_hunk_t *hunks, int hunk_count, int64_t **seeds,
int *n, int *cap) {
int *n, int *cap, const detect_seed_request_t *request,
detect_seed_rows_t *rows) {
cbm_node_t *nodes = NULL;
int ncount = 0;
cbm_store_find_nodes_by_file(store, project, file, &nodes, &ncount);
if (cbm_store_find_nodes_by_file(store, project, file, &nodes, &ncount) != CBM_STORE_OK) {
rows->failed = true;
return;
}
bool file_is_test = request->product && cbm_test_config_is_test_file(rows->config, file);
bool scope_to_hunks = false;
for (int h = 0; h < hunk_count; h++) {
if (strcmp(hunks[h].path, file) == 0) {
@@ -15942,6 +16128,14 @@ static void detect_collect_seeds(cbm_store_t *store, const char *project, const
if (scope_to_hunks && !cbm_detect_node_in_hunks(&nodes[i], hunks, hunk_count, file)) {
continue;
}
/* Probe overlap before filtering: a test-only hunk must not
* fall back onto untouched product definitions in this file. */
if (request->product && (file_is_test || detect_seed_is_test(&nodes[i]))) {
continue;
}
if (request->list) {
detect_seed_add_row(rows, &nodes[i], scope_to_hunks);
}
if (*n >= *cap) {
*cap = *cap ? *cap * 2 : 16;
*seeds = safe_realloc(*seeds, (size_t)*cap * sizeof(int64_t));
@@ -16023,14 +16217,28 @@ static void detect_module_rollup_free(detect_module_row_t *modules, int count) {
* is hop-ordered, so page zero preserves the closest, highest-signal rows.
* Engine saturation is reported as a lower-bound total, never as an exact
* count. */
/* How the walk reached a row: the parent one hop closer and the edge type that
* led there. Empty strings for a row the walk does not know. */
static void detect_walk_via(const cbm_impact_walk_t *walk, int64_t id, const char **via_qn,
const char **via_edge) {
const cbm_impact_hit_t *hit = cbm_impact_walk_hit(walk, id);
const cbm_impact_hit_t *parent = hit ? cbm_impact_walk_hit(walk, hit->via_id) : NULL;
*via_qn = (parent && parent->qualified_name) ? parent->qualified_name : "";
*via_edge = (hit && hit->via_edge) ? hit->via_edge : "";
}
/* `via_walk` is the open impact walk when the request asked for `via`, and adds
* the via_qn and via_edge columns; NULL keeps the four default columns. */
static void detect_emit_impacted_tree(cbm_sb_t *sb, const cbm_traverse_result_t *tr, int start,
int count, bool engine_saturated) {
int count, bool engine_saturated,
const cbm_impact_walk_t *via_walk) {
cbm_tree_scalar_int(sb, "impacted_total", tr->visited_count);
cbm_tree_scalar_str(sb, "impacted_total_relation", engine_saturated ? "gte" : "eq");
int shown = count;
/* qn order for stable grouping, but keep hop-closeness: sort by (hop) is
* lost under qn sort, so group AFTER selecting the nearest `shown` rows —
* the visited array is already (hop,id)-ordered from the BFS. */
* the visited array is already (hop,id)-ordered from the BFS, and
* (hop,qn)-ordered from the impact walk. */
cbm_tree_scalar_int(sb, "impacted_shown", shown);
cbm_node_hop_t *selected = NULL;
const cbm_node_hop_t *rows = tr->visited;
@@ -16046,24 +16254,28 @@ static void detect_emit_impacted_tree(cbm_sb_t *sb, const cbm_traverse_result_t
if (shown > 1 && selected) {
qsort(selected, (size_t)shown, sizeof(*selected), tree_hop_cmp_qn);
}
static const char *const columns[] = {"qn", "label", "file", "hop"};
const char **cells = shown > 0 ? calloc((size_t)shown * 4U, sizeof(*cells)) : NULL;
static const char *const columns[] = {"qn", "label", "file", "hop", "via_qn", "via_edge"};
size_t ncols = via_walk ? 6U : 4U;
const char **cells = shown > 0 ? calloc((size_t)shown * ncols, sizeof(*cells)) : NULL;
char (*hop_text)[32] = shown > 0 ? calloc((size_t)shown, sizeof(*hop_text)) : NULL;
if (shown == 0 || (cells && hop_text)) {
for (int i = 0; i < shown; i++) {
size_t base = (size_t)i * 4U;
size_t base = (size_t)i * ncols;
cells[base] = rows[i].node.qualified_name ? rows[i].node.qualified_name : "";
cells[base + 1U] = rows[i].node.label ? rows[i].node.label : "";
cells[base + 2U] = rows[i].node.file_path ? rows[i].node.file_path : "";
snprintf(hop_text[i], sizeof(hop_text[i]), "%d", rows[i].hop);
cells[base + 3U] = hop_text[i];
if (via_walk) {
detect_walk_via(via_walk, rows[i].node.id, &cells[base + 4U], &cells[base + 5U]);
}
}
static const bool string_cols[] = {true, true, true, false};
static const bool prefix_cols[] = {true, false, true, false};
cbm_tree_table_rows_profiled(sb, "impacted", shown, columns, 4, cells, string_cols,
static const bool string_cols[] = {true, true, true, false, true, true};
static const bool prefix_cols[] = {true, false, true, false, true, false};
cbm_tree_table_rows_profiled(sb, "impacted", shown, columns, (int)ncols, cells, string_cols,
prefix_cols);
} else {
cbm_tree_table_header(sb, "impacted", 0, columns, 4);
cbm_tree_table_header(sb, "impacted", 0, columns, (int)ncols);
cbm_tree_scalar_str(sb, "impacted_render_error", "out_of_memory");
}
free(hop_text);
@@ -16316,6 +16528,199 @@ static uint64_t detect_params_hash(const char *project, const char *base_branch,
return cursor_fnv1a64(depth_text, hash);
}
/* ── detect_changes: opt-in impact walk ───────────────────────────
* `edge_types`, `fixpoint` and `via` switch the impact step from the
* depth-bounded CALLS traversal to cbm_impact_walk (store/store_impact.h). A
* request that names none of them keeps the old traversal and its bytes. */
enum { DETECT_WALK_TYPE_MAX = 16, DETECT_WALK_TYPE_LEN = 64 };
typedef struct {
bool active; /* edge_types, fixpoint or via was requested */
bool fixpoint; /* walk until nothing new is reached; `depth` is ignored */
bool via; /* rows also say which parent and edge type reached them */
int type_count;
char types[DETECT_WALK_TYPE_MAX][DETECT_WALK_TYPE_LEN];
} detect_walk_request_t;
/* An argument that is absent or JSON null was not given. */
static yyjson_val *detect_walk_arg(yyjson_val *root, const char *key) {
yyjson_val *val = root ? yyjson_obj_get(root, key) : NULL;
return (val && !yyjson_is_null(val)) ? val : NULL;
}
/* "unknown edge type" plus the types this index does hold: a walk over a
* mistyped edge type would come back empty and read as "nothing depends on
* this". */
static void detect_walk_unknown_type(const cbm_schema_info_t *schema, const char *name, char *err,
size_t err_sz) {
int used = snprintf(err, err_sz, "unknown edge type \"%.60s\" — this index has: ", name);
for (int i = 0; i < schema->edge_type_count && used > 0 && (size_t)used < err_sz; i++) {
used += snprintf(err + used, err_sz - (size_t)used, "%s%s", i > 0 ? ", " : "",
schema->edge_types[i].type ? schema->edge_types[i].type : "");
}
}
static void detect_walk_parse_types(cbm_store_t *store, const char *project, yyjson_val *types,
detect_walk_request_t *out, char *err, size_t err_sz) {
static const char *const shape = "edge_types must be a non-empty array of at most 16 edge "
"type names, e.g. [\"CALLS\",\"USAGE\"]";
if (!yyjson_is_arr(types) || yyjson_arr_size(types) == 0 ||
yyjson_arr_size(types) > DETECT_WALK_TYPE_MAX) {
snprintf(err, err_sz, "%s", shape);
return;
}
cbm_schema_info_t schema = {0};
if (cbm_store_get_schema_counts(store, project, &schema) != CBM_STORE_OK) {
snprintf(err, err_sz,
"index_metadata_error: the edge types of this index are unreadable; reindex "
"before detecting changes");
return;
}
size_t idx;
size_t max;
yyjson_val *val;
yyjson_arr_foreach(types, idx, max, val) {
const char *name = yyjson_is_str(val) ? yyjson_get_str(val) : NULL;
if (!name) {
snprintf(err, err_sz, "%s", shape);
break;
}
bool known = false;
for (int i = 0; i < schema.edge_type_count && !known; i++) {
known = schema.edge_types[i].type && strcmp(schema.edge_types[i].type, name) == 0;
}
if (!known || strlen(name) >= DETECT_WALK_TYPE_LEN) {
detect_walk_unknown_type(&schema, name, err, err_sz);
break;
}
bool twice = false;
for (int i = 0; i < out->type_count && !twice; i++) {
twice = strcmp(out->types[i], name) == 0;
}
if (twice) {
/* The order is the rank that picks a row's `via` parent, so a
* repeated name has no single meaning. */
snprintf(err, err_sz, "edge type \"%s\" is listed twice in edge_types", name);
break;
}
snprintf(out->types[out->type_count++], DETECT_WALK_TYPE_LEN, "%s", name);
}
cbm_store_schema_free(&schema);
}
/* Read and validate the walk parameters. `err` stays empty when the request is
* usable; otherwise it holds the teaching error and nothing was corrected. */
static void detect_walk_parse(cbm_store_t *store, const char *project, const char *args,
const char *direction, detect_walk_request_t *out, char *err,
size_t err_sz) {
memset(out, 0, sizeof(*out));
yyjson_doc *doc = yyjson_read(args, strlen(args), 0);
yyjson_val *root = doc ? yyjson_doc_get_root(doc) : NULL;
yyjson_val *types = detect_walk_arg(root, "edge_types");
yyjson_val *fixpoint = detect_walk_arg(root, "fixpoint");
yyjson_val *via = detect_walk_arg(root, "via");
if (fixpoint && !yyjson_is_bool(fixpoint)) {
snprintf(err, err_sz, "fixpoint must be true or false");
} else if (via && !yyjson_is_bool(via)) {
snprintf(err, err_sz, "via must be true or false");
} else if (types) {
detect_walk_parse_types(store, project, types, out, err, err_sz);
}
if (!err[0]) {
out->fixpoint = fixpoint && yyjson_get_bool(fixpoint);
out->via = via && yyjson_get_bool(via);
out->active = types || out->fixpoint || out->via;
}
if (!err[0] && out->active && strcmp(direction, "inbound") != 0) {
snprintf(err, err_sz,
"edge_types, fixpoint and via walk the blast radius only — use direction "
"\"inbound\" (the default)");
}
if (!err[0] && out->active && out->type_count == 0) {
snprintf(out->types[out->type_count++], DETECT_WALK_TYPE_LEN, "CALLS");
}
yyjson_doc_free(doc);
}
/* The walk parameters are part of the question a cursor continues. A request
* without them hashes exactly as before, so its cursors are unchanged. */
static uint64_t detect_walk_hash(uint64_t hash, const detect_walk_request_t *walk) {
if (!walk->active) {
return hash;
}
hash = cursor_fnv1a64(walk->fixpoint ? "|walk:fixpoint" : "|walk:bounded", hash);
hash = cursor_fnv1a64(walk->via ? "|via" : "|novia", hash);
for (int i = 0; i < walk->type_count; i++) {
hash = cursor_fnv1a64("|", hash);
hash = cursor_fnv1a64(walk->types[i], hash);
}
return hash;
}
/* "CALLS,USAGE": the edge types of the walk, in rank order, for the answer. */
static void detect_walk_types_text(const detect_walk_request_t *walk, char *out, size_t out_sz) {
size_t used = 0;
out[0] = '\0';
for (int i = 0; i < walk->type_count && used < out_sz; i++) {
int n = snprintf(out + used, out_sz - used, "%s%s", i > 0 ? "," : "", walk->types[i]);
used += n > 0 ? (size_t)n : 0;
}
}
/* Run the walk and hand its answer over in the traversal's shape, so paging,
* the module rollup and the snapshot fingerprint are shared with the default
* path. Seeds are left out, as in cbm_store_bfs_multi: a changed symbol is not
* its own impact. Rows arrive in the walk's (hop, qualified name) order. The
* walk stays open for the `via` columns; the caller closes it.
*
* false = the walk or a row could not be completed. Nothing shorter is handed
* over: an incomplete impact set would read as "nothing else depends on this". */
static bool detect_walk_run(cbm_store_t *store, const char *project,
const detect_walk_request_t *req, int depth, const int64_t *seeds,
int seed_count, cbm_traverse_result_t *impact,
cbm_impact_walk_t **walk_out) {
const char *types[DETECT_WALK_TYPE_MAX];
for (int i = 0; i < req->type_count; i++) {
types[i] = req->types[i];
}
cbm_impact_policy_t policy = {.project = project,
.edge_types = types,
.edge_type_count = req->type_count,
.max_hops = req->fixpoint ? 0 : depth};
cbm_impact_walk_t *walk = NULL;
if (cbm_impact_walk_open(store, &policy, &walk) != CBM_STORE_OK) {
return false;
}
bool ok = cbm_impact_walk_run(walk, seeds, seed_count) == CBM_STORE_OK;
int total = ok ? cbm_impact_walk_count(walk) : 0;
const cbm_impact_hit_t *hits = cbm_impact_walk_hits(walk);
/* cbm_store_traverse_free owns and releases these rows. */
cbm_node_hop_t *rows = total > 0 ? safe_realloc(NULL, (size_t)total * sizeof(*rows)) : NULL;
ok = ok && (total == 0 || rows);
if (rows) {
memset(rows, 0, (size_t)total * sizeof(*rows));
}
int n = 0;
for (int i = 0; ok && i < total; i++) {
if (hits[i].hop == 0) {
continue;
}
ok = cbm_store_find_node_by_id(store, hits[i].id, &rows[n].node) == CBM_STORE_OK;
if (ok) {
rows[n++].hop = hits[i].hop;
}
}
impact->visited = rows;
impact->visited_count = n;
if (!ok) {
cbm_store_traverse_free(impact);
cbm_impact_walk_close(walk);
return false;
}
*walk_out = walk;
return true;
}
static void detect_cursor_encode(char stream, const char snapshot[33], uint64_t qhash, int offset,
char out[80]) {
snprintf(out, 80, "d1.%c.%s.%016llx.%d", stream, snapshot, (unsigned long long)qhash, offset);
@@ -16516,6 +16921,68 @@ static bool detect_snapshot_fingerprint(cbm_mcp_server_t *srv, const char *root_
return complete;
}
/* scope:"tests": which tests the change needs (mcp/test_impact_engine.h). The
* answer is computed from a graph of exactly the pinned HEAD commit, built
* for this request; the live index is not consulted. Missing evidence is a
* run-all answer that names why; only a failed request is an error. */
static char *detect_test_impact(const char *args, const char *root_path, const char *base_branch) {
detect_seed_request_t seed_req = {0};
char err[CBM_SZ_1K] = "";
detect_seed_parse(args, &seed_req, err, sizeof(err));
if (err[0]) {
return cbm_mcp_text_result(err, true);
}
char config_file[CBM_SZ_4K] = "";
if (seed_req.config_path[0]) {
if (repo_path_is_absolute(seed_req.config_path)) {
snprintf(config_file, sizeof(config_file), "%s", seed_req.config_path);
} else {
snprintf(config_file, sizeof(config_file), "%s/%s", root_path, seed_req.config_path);
}
}
char work[CBM_SZ_4K];
snprintf(work, sizeof(work), "%s/test-impact", cbm_resolve_cache_dir());
if (!cbm_mkdir_p(work, 0700)) {
return cbm_mcp_text_result("test_impact_failed: no private work directory", true);
}
cbm_test_impact_request_t request = {.repo_root = root_path,
.base_ref = base_branch,
.work_parent = work,
.config_path = config_file[0] ? config_file : NULL,
.deadline_ms = cbm_now_ms() + 30ULL * 60ULL * 1000ULL};
cbm_test_result_t *result = NULL;
char diagnostic[CBM_SZ_512] = "";
cbm_test_impact_status_t status =
cbm_test_impact_run(&request, &result, diagnostic, sizeof(diagnostic));
if (status != CBM_TEST_IMPACT_OK) {
char text[CBM_SZ_1K];
snprintf(text, sizeof(text), "test_impact_failed: status %d%s%s", (int)status,
diagnostic[0] ? ": " : "", diagnostic);
return cbm_mcp_text_result(text, true);
}
size_t length = 0;
const char *json = cbm_test_result_json(result, &length);
char *res = NULL;
size_t total = length + sizeof("{\"test_impact\":}");
char *text = json ? cbm_alloc(CBM_MEM_CLASS_OTHER, total) : NULL;
if (text) {
snprintf(text, total, "{\"test_impact\":%s}", json);
res = cbm_mcp_text_result(text, false);
cbm_free(CBM_MEM_CLASS_OTHER, text);
}
cbm_test_result_free(result);
return res ? res : cbm_mcp_text_result("test_impact_failed: out of memory", true);
}
/* The request strings handle_detect_changes owns, released on every early
* return (NULL is fine). */
static void detect_release(char *root_path, char *project, char *base_branch, char *scope) {
free(root_path);
free(project);
free(base_branch);
free(scope);
}
static char *handle_detect_changes(cbm_mcp_server_t *srv, const char *args) {
char *project = get_project_arg(args);
char *base_branch = cbm_mcp_get_string_arg(args, "base_branch");
@@ -16580,24 +17047,24 @@ static char *handle_detect_changes(cbm_mcp_server_t *srv, const char *args) {
base_branch = heap_strdup(default_base);
}
if (!root_ok || !base_branch) {
free(root_path);
free(project);
free(base_branch);
free(scope);
detect_release(root_path, project, base_branch, scope);
return cbm_mcp_text_result(
root_ok ? "out of memory" : "project path contains invalid characters", true);
}
if (scope && strcmp(scope, "tests") == 0) {
char *res = detect_test_impact(args, root_path, base_branch);
detect_release(root_path, project, base_branch, scope);
return res;
}
/* Every detect snapshot and cursor is generation-bound. Validate the
* metadata immediately after store resolution so fresh requests and
* cursor replays fail identically before Git work or cursor minting. */
cbm_store_t *store = srv->store;
char generation[96] = "";
if (cbm_store_generation(store, generation, sizeof(generation)) != CBM_STORE_OK) {
free(root_path);
free(project);
free(base_branch);
free(scope);
detect_release(root_path, project, base_branch, scope);
return cbm_mcp_text_result(
"index_metadata_error: generation metadata is unreadable; reindex before detecting "
"changes",
@@ -16613,13 +17080,22 @@ static char *handle_detect_changes(cbm_mcp_server_t *srv, const char *args) {
}
/* Teaching error, same contract as trace_path: never silently correct an
* unknown direction — the caller would misread the result's semantics. */
char errbuf[CBM_SZ_1K] = "";
detect_walk_request_t walk_req = {0};
detect_seed_request_t seed_req = {0};
if (strcmp(direction, "inbound") != 0 && strcmp(direction, "outbound") != 0 &&
strcmp(direction, "both") != 0) {
char errbuf[CBM_SZ_256];
snprintf(errbuf, sizeof(errbuf),
"invalid direction \"%s\" — use \"inbound\" (blast radius: transitive callers), "
"\"outbound\" (dependencies), or \"both\"",
direction);
} else {
/* Same contract for the opt-in walk parameters. */
detect_walk_parse(store, project, args, direction, &walk_req, errbuf, sizeof(errbuf));
if (!errbuf[0])
detect_seed_parse(args, &seed_req, errbuf, sizeof(errbuf));
}
if (errbuf[0]) {
free(direction);
free(root_path);
free(project);
@@ -16985,8 +17461,37 @@ static char *handle_detect_changes(cbm_mcp_server_t *srv, const char *args) {
char *impact_cursor_arg = cbm_mcp_get_string_arg(args, "impact_cursor");
char *changed_cursor_arg = cbm_mcp_get_string_arg(args, "changed_cursor");
char *module_cursor_arg = cbm_mcp_get_string_arg(args, "module_cursor");
uint64_t detect_qhash = detect_params_hash(project, base_branch,
want_symbols ? "impact" : "files", direction, depth);
uint64_t detect_qhash =
detect_walk_hash(detect_params_hash(project, base_branch, want_symbols ? "impact" : "files",
direction, depth),
&walk_req);
if (seed_req.list)
detect_qhash = cursor_fnv1a64("|seeds:list", detect_qhash);
if (seed_req.product)
detect_qhash = cursor_fnv1a64("|seed_filter:product", detect_qhash);
detect_seed_rows_t seed_rows = {0};
cbm_arena_init_lazy(&seed_rows.arena, 4096);
if (seed_req.product) {
const char *name = seed_req.config_path[0] ? seed_req.config_path : ".codebase-memory.json";
char *config_file = NULL;
if (repo_path_is_absolute(name))
config_file = cbm_arena_strdup(&seed_rows.arena, name);
else {
size_t a = strlen(root_path), b = strlen(name);
if (a <= SIZE_MAX - b - 2) {
config_file = cbm_arena_alloc(&seed_rows.arena, a + b + 2);
if (config_file)
snprintf(config_file, a + b + 2, "%s/%s", root_path, name);
}
}
seed_rows.config =
config_file ? cbm_test_config_load(config_file, !seed_req.config_path[0]) : NULL;
seed_rows.config_failed = !seed_rows.config;
seed_rows.failed = seed_rows.config_failed;
detect_qhash = cursor_fnv1a64("|test-config:", detect_qhash);
detect_qhash = cursor_fnv1a64(cbm_test_config_digest(seed_rows.config), detect_qhash);
}
/* Changed paths drive traversal seeds and both output encodings. */
int64_t *seeds = NULL;
@@ -17012,7 +17517,7 @@ static char *handle_detect_changes(cbm_mcp_server_t *srv, const char *args) {
* `files` when the project root is a repository subdirectory (#1951). */
cbm_changed_hunk_t *hunks = NULL;
int hunk_count = 0;
if (want_symbols) {
if (!seed_rows.failed && (want_symbols || seed_req.list)) {
char hunk_cmd[CBM_SZ_2K];
#ifdef _WIN32
snprintf(hunk_cmd, sizeof(hunk_cmd),
@@ -17070,17 +17575,39 @@ static char *handle_detect_changes(cbm_mcp_server_t *srv, const char *args) {
* decision. */
for (int i = 0; i < file_count; i++) {
detect_collect_seeds(store, project, files[i], hunks, hunk_count, &seeds, &seed_count,
&seed_cap);
&seed_cap, &seed_req, &seed_rows);
}
}
if (!seed_rows.failed && seed_rows.count > 1)
qsort(seed_rows.rows, (size_t)seed_rows.count, sizeof(*seed_rows.rows),
detect_seed_row_compare);
/* The impact traversal: ONE multi-source BFS over all seeds. */
cbm_traverse_result_t impact = {0};
bool engine_saturated = false;
if (want_symbols && seed_count > 0) {
/* The opt-in walk has no result ceiling, so it never saturates: it answers
* completely or the request fails. It stays open while `via` is rendered. */
cbm_impact_walk_t *impact_walk = NULL;
const char *walk_error =
seed_rows.config_failed
? "config_invalid: could not read a valid project test configuration"
: seed_rows.failed ? "seed_collection_failed: no partial answer is returned"
: NULL;
if (!walk_error && want_symbols && seed_count > 0 && walk_req.active) {
if (!detect_walk_run(store, project, &walk_req, depth, seeds, seed_count, &impact,
&impact_walk)) {
walk_error = "impact_walk_failed: the impact walk could not be completed — no "
"partial answer is returned; rerun, or drop edge_types, fixpoint and "
"via for the bounded default traversal";
}
} else if (!walk_error && want_symbols && seed_count > 0) {
(void)cbm_store_bfs_multi(store, seeds, seed_count, direction, NULL, 0, depth,
MCP_BFS_LIMIT_MAX, &impact, &engine_saturated);
}
const cbm_impact_walk_t *via_walk = walk_req.via ? impact_walk : NULL;
char walk_types_text[DETECT_WALK_TYPE_MAX * DETECT_WALK_TYPE_LEN] = "";
detect_walk_types_text(&walk_req, walk_types_text, sizeof(walk_types_text));
detect_module_row_t *modules = NULL;
int nmods = 0;
@@ -17095,17 +17622,39 @@ static char *handle_detect_changes(cbm_mcp_server_t *srv, const char *args) {
* number of pageable rollup rows for the materialized impact set; its
* relation becomes `gte` if traversal hit the engine ceiling. */
int module_total = nmods + (module_overflow > 0 ? 1 : 0);
const char *cursor_error = NULL;
const char *cursor_error = walk_error;
char detect_snapshot[33] = "";
bool detect_snapshot_complete = detect_snapshot_fingerprint(
srv, root_path, head_oid, base_oid, merge_base, generation, files, file_count, &impact,
modules, nmods, module_overflow, detect_snapshot);
if (seed_req.list && !seed_rows.failed) {
cbm_sha256_ctx hash;
cbm_sha256_init(&hash);
detect_snapshot_add_field(&hash, detect_snapshot);
for (int i = 0; i < seed_rows.count; i++) {
detect_seed_row_t *row = &seed_rows.rows[i];
detect_snapshot_add_field(&hash, row->qn);
detect_snapshot_add_field(&hash, row->label);
detect_snapshot_add_field(&hash, row->file);
detect_snapshot_add_field(&hash, row->reason);
char location[64];
snprintf(location, sizeof(location), "%d:%d", row->start, row->end);
detect_snapshot_add_field(&hash, location);
}
uint8_t digest[CBM_SHA256_DIGEST_LEN];
static const char hex[] = "0123456789abcdef";
cbm_sha256_final(&hash, digest);
for (int i = 0; i < 16; i++) {
detect_snapshot[i * 2] = hex[digest[i] >> 4];
detect_snapshot[i * 2 + 1] = hex[digest[i] & 15];
}
}
detect_cursor_t decoded_cursor = {0};
bool cursor_supplied = (changed_cursor_arg && changed_cursor_arg[0]) ||
bool cursor_supplied = seed_req.cursor[0] || (changed_cursor_arg && changed_cursor_arg[0]) ||
(impact_cursor_arg && impact_cursor_arg[0]) ||
(module_cursor_arg && module_cursor_arg[0]);
if (cursor_supplied && !detect_snapshot_complete) {
if (!cursor_error && cursor_supplied && !detect_snapshot_complete) {
cursor_error = "snapshot_unavailable: changed file bytes could not be fingerprinted — "
"rerun without the cursor after the files are readable";
}
@@ -17145,7 +17694,21 @@ static char *handle_detect_changes(cbm_mcp_server_t *srv, const char *args) {
}
}
}
if (!cursor_error && seed_req.cursor[0]) {
if (seed_req.offset != 0)
cursor_error =
"cursor_params_mismatch: seed_cursor cannot be combined with a nonzero seed_offset";
else {
cursor_error = detect_cursor_decode(seed_req.cursor, 's', detect_snapshot, detect_qhash,
&decoded_cursor);
if (!cursor_error)
seed_req.offset = decoded_cursor.offset;
}
}
if (cursor_error) {
cbm_test_config_free(seed_rows.config);
cbm_arena_destroy(&seed_rows.arena);
cbm_impact_walk_close(impact_walk);
cbm_store_traverse_free(&impact);
detect_module_rollup_free(modules, nmods);
for (int i = 0; i < file_count; i++) {
@@ -17165,6 +17728,10 @@ static char *handle_detect_changes(cbm_mcp_server_t *srv, const char *args) {
return cbm_mcp_text_result(cursor_error, true);
}
int seed_start = seed_req.offset < seed_rows.count ? seed_req.offset : seed_rows.count;
int seed_returned = seed_rows.count - seed_start;
if (seed_returned > seed_req.limit)
seed_returned = seed_req.limit;
int changed_start = changed_offset < file_count ? changed_offset : file_count;
int changed_returned = file_count - changed_start;
if (changed_returned > changed_limit) {
@@ -17187,11 +17754,19 @@ static char *handle_detect_changes(cbm_mcp_server_t *srv, const char *args) {
char *out_str = NULL;
render_detect_output:;
bool seed_has_more = seed_req.list && seed_start + seed_returned < seed_rows.count;
char seed_next_cursor[80] = "";
if (seed_has_more && seed_returned > 0 && detect_snapshot_complete)
detect_cursor_encode('s', detect_snapshot, detect_qhash, seed_start + seed_returned,
seed_next_cursor);
const char *seed_continuation = seed_req.limit == 0
? "seed_continuation_requires_positive_limit"
: "seed_continuation_requires_higher_budget";
bool changed_has_more = changed_start + changed_returned < file_count;
bool impacted_has_more = want_symbols && imp_start + imp_returned < impact.visited_count;
bool module_has_more = want_symbols && module_start + module_returned < module_total;
bool response_truncated = engine_saturated || output_budget_hit || changed_has_more ||
impacted_has_more || module_has_more;
impacted_has_more || module_has_more || seed_has_more;
if (!legacy_json) {
cbm_sb_t sb;
cbm_sb_init(&sb);
@@ -17200,6 +17775,10 @@ render_detect_output:;
cbm_tree_scalar_str(&sb, "merge_base", merge_base);
}
cbm_tree_scalar_str(&sb, "direction", direction);
if (walk_req.active) {
cbm_tree_scalar_str(&sb, "edge_types", walk_types_text);
cbm_tree_scalar_bool(&sb, "fixpoint", walk_req.fixpoint);
}
if (output_budget_hit) {
cbm_tree_scalar_str(&sb, "truncation_reason", "output_budget");
cbm_tree_scalar_int(&sb, "max_output_bytes", (long long)output_budget_bytes);
@@ -17250,8 +17829,34 @@ render_detect_output:;
}
free(changed_cells);
cbm_tree_scalar_int(&sb, "seed_symbols", seed_count);
if (seed_req.list) {
cbm_tree_scalar_int(&sb, "seed_total", seed_rows.count);
cbm_tree_scalar_int(&sb, "seed_returned", seed_returned);
cbm_tree_scalar_bool(&sb, "seed_has_more", seed_has_more);
if (seed_has_more && seed_returned > 0) {
cbm_tree_scalar_int(&sb, "seed_next_offset", seed_start + seed_returned);
if (seed_next_cursor[0])
cbm_tree_scalar_str(&sb, "seed_next_cursor", seed_next_cursor);
} else if (seed_has_more)
cbm_tree_scalar_bool(&sb, seed_continuation, true);
static const char *const columns[] = {"qn", "label", "file",
"start", "end", "seed_reason"};
cbm_tree_table_header(&sb, "seeds", seed_returned, columns, 6);
for (int i = seed_start; i < seed_start + seed_returned; i++) {
const detect_seed_row_t *row = &seed_rows.rows[i];
cbm_tree_row_begin(&sb);
cbm_tree_cell_str(&sb, row->qn, true);
cbm_tree_cell_str(&sb, row->label, false);
cbm_tree_cell_str(&sb, row->file, false);
cbm_tree_cell_int(&sb, row->start, false);
cbm_tree_cell_int(&sb, row->end, false);
cbm_tree_cell_str(&sb, row->reason, false);
cbm_tree_row_end(&sb);
}
}
if (want_symbols) {
detect_emit_impacted_tree(&sb, &impact, imp_start, imp_returned, engine_saturated);
detect_emit_impacted_tree(&sb, &impact, imp_start, imp_returned, engine_saturated,
via_walk);
if (detect_snapshot_complete && imp_start + imp_returned < impact.visited_count &&
imp_returned > 0) {
char cursor[80];
@@ -17323,6 +17928,14 @@ render_detect_output:;
yyjson_mut_obj_add_strcpy(doc, root_obj, "merge_base", merge_base);
}
yyjson_mut_obj_add_strcpy(doc, root_obj, "direction", direction);
if (walk_req.active) {
yyjson_mut_val *walk_types = yyjson_mut_arr(doc);
for (int i = 0; i < walk_req.type_count; i++) {
yyjson_mut_arr_add_strcpy(doc, walk_types, walk_req.types[i]);
}
yyjson_mut_obj_add_val(doc, root_obj, "edge_types", walk_types);
yyjson_mut_obj_add_bool(doc, root_obj, "fixpoint", walk_req.fixpoint);
}
if (output_budget_hit) {
yyjson_mut_obj_add_str(doc, root_obj, "truncation_reason", "output_budget");
yyjson_mut_obj_add_uint(doc, root_obj, "max_output_bytes", output_budget_bytes);
@@ -17361,6 +17974,31 @@ render_detect_output:;
}
yyjson_mut_obj_add_val(doc, root_obj, "changed_files", cf);
yyjson_mut_obj_add_int(doc, root_obj, "seed_symbols", seed_count);
if (seed_req.list) {
yyjson_mut_obj_add_int(doc, root_obj, "seed_total", seed_rows.count);
yyjson_mut_obj_add_int(doc, root_obj, "seed_returned", seed_returned);
yyjson_mut_obj_add_bool(doc, root_obj, "seed_has_more", seed_has_more);
if (seed_has_more && seed_returned > 0) {
yyjson_mut_obj_add_int(doc, root_obj, "seed_next_offset",
seed_start + seed_returned);
if (seed_next_cursor[0])
yyjson_mut_obj_add_strcpy(doc, root_obj, "seed_next_cursor", seed_next_cursor);
} else if (seed_has_more)
yyjson_mut_obj_add_bool(doc, root_obj, seed_continuation, true);
yyjson_mut_val *seed_array = yyjson_mut_arr(doc);
for (int i = seed_start; i < seed_start + seed_returned; i++) {
const detect_seed_row_t *row = &seed_rows.rows[i];
yyjson_mut_val *item = yyjson_mut_obj(doc);
yyjson_mut_obj_add_strcpy(doc, item, "qn", row->qn);
yyjson_mut_obj_add_strcpy(doc, item, "label", row->label);
yyjson_mut_obj_add_strcpy(doc, item, "file", row->file);
yyjson_mut_obj_add_int(doc, item, "start", row->start);
yyjson_mut_obj_add_int(doc, item, "end", row->end);
yyjson_mut_obj_add_strcpy(doc, item, "seed_reason", row->reason);
yyjson_mut_arr_add_val(seed_array, item);
}
yyjson_mut_obj_add_val(doc, root_obj, "seeds", seed_array);
}
yyjson_mut_obj_add_int(doc, root_obj, "impacted_total", impact.visited_count);
yyjson_mut_obj_add_str(doc, root_obj, "impacted_total_relation",
engine_saturated ? "gte" : "eq");
@@ -17377,6 +18015,13 @@ render_detect_output:;
doc, o, "file",
impact.visited[i].node.file_path ? impact.visited[i].node.file_path : "");
yyjson_mut_obj_add_int(doc, o, "hop", impact.visited[i].hop);
if (via_walk) {
const char *via_qn = "";
const char *via_edge = "";
detect_walk_via(via_walk, impact.visited[i].node.id, &via_qn, &via_edge);
yyjson_mut_obj_add_strcpy(doc, o, "via_qn", via_qn);
yyjson_mut_obj_add_strcpy(doc, o, "via_edge", via_edge);
}
yyjson_mut_arr_add_val(imp, o);
}
yyjson_mut_obj_add_val(doc, root_obj, "impacted", imp);
@@ -17445,6 +18090,8 @@ render_detect_output:;
module_returned = 0;
} else if (changed_returned > 0) {
changed_returned = 0;
} else if (seed_returned > 0) {
seed_returned--;
} else if (imp_returned > 0) {
/* Prefix-directory activation can make N compact rows smaller
* than N-1 direct rows. Probe every smaller whole-row prefix so
@@ -17496,6 +18143,9 @@ render_detect_output:;
detect_output_done:
cbm_test_config_free(seed_rows.config);
cbm_arena_destroy(&seed_rows.arena);
cbm_impact_walk_close(impact_walk);
cbm_store_traverse_free(&impact);
detect_module_rollup_free(modules, nmods);
for (int i = 0; i < file_count; i++) {
File diff suppressed because it is too large Load Diff
+568
View File
@@ -0,0 +1,568 @@
/*
* test_impact.h — which tests does a change reach? (detect_changes scope:"tests")
*
* Part 1, the test model: what a repository's test sources define and what
* its runner registers, read from the source text by convention.
*
* Suite membership comes from the registration itself — the name inside
* RUN_TEST(...) and the TEST of that name in the same file — and never from a
* resolver edge. An edge can be missing or point at a same-named symbol
* elsewhere; the registration is what the compiler sees, and a selection that
* names a test its suite never runs fails the run (tests/test_main.c).
*/
#ifndef CBM_MCP_TEST_IMPACT_H
#define CBM_MCP_TEST_IMPACT_H
#include "discover/test_conventions.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/* The spellings a test framework uses. A NULL entry is a convention the
* framework does not have. */
typedef struct {
const char *case_macro; /* TEST(name) { ... } */
const char *suite_macro; /* SUITE(name) { ... } */
const char *suite_fn_prefix; /* void suite_name(void) { ... }: what SUITE expands to */
const char *run_macro; /* RUN_TEST(name) inside a suite body */
const char *suite_run_macro; /* RUN_SELECTED_SUITE(name) in the runner */
const char *suite_run_perf_macro; /* RUN_SELECTED_SUITE_PERF(name): a perf suite */
} cbm_test_conventions_t;
/* This repository's own C test framework (tests/test_framework.h). */
const cbm_test_conventions_t *cbm_test_conventions_cbm(void);
typedef struct {
const char *file; /* as given to cbm_test_model_add_source */
const char *name;
int start_line; /* the line of TEST(name) */
int end_line; /* the line of the closing brace of its body */
/* Defined under a preprocessor condition (an include guard is not one):
* some builds do not have this test. */
bool conditional;
} cbm_test_case_t;
typedef struct {
const char *file;
const char *name;
int start_line;
int end_line;
/* The file defines a macro that registers tests. The suite may then run
* tests the model cannot name, so it can only be selected whole. */
bool macro_registrations;
/* The model could not read this suite's file with certainty (for
* example tests defined by a macro). The suite can only run whole; other
* suites stay narrowable (cbm_test_model_narrowable). */
bool uncertain;
} cbm_test_suite_t;
typedef struct {
const char *file;
const char *suite;
const char *test;
int line;
bool conditional; /* registered under a preprocessor condition */
bool resolved; /* a case of that name is defined in the same file */
} cbm_test_registration_t;
typedef struct {
const char *name;
bool perf;
const char *file; /* the runner file that registers it */
int line;
} cbm_test_runner_suite_t;
typedef struct cbm_test_model cbm_test_model_t;
/* NULL when out of memory. The conventions are copied. */
cbm_test_model_t *cbm_test_model_new(const cbm_test_conventions_t *conventions);
/* Read one source file. `text` need not be NUL-terminated; (NULL, 0) is empty.
* false = invalid input/state or out of memory. A successful read can still
* leave parse uncertainty; it does not establish completeness. */
bool cbm_test_model_add_source(cbm_test_model_t *m, const char *file, const char *text, size_t len);
/* Order and link what was read. Call once, after the last file and before any
* row accessors. Mapping and declaration accessors also work before finish.
* false = out of memory. */
bool cbm_test_model_finish(cbm_test_model_t *m);
void cbm_test_model_free(cbm_test_model_t *m);
/* True only after finish and no sticky parse/lexical/unsupported-form
* uncertainty in any added source. Rows remain available for diagnostics
* when false, but cannot justify narrowing. This concerns the supplied
* source text only: callers must separately establish that the external
* file/runner inventory is complete and conventions are applicable. */
bool cbm_test_model_complete(const cbm_test_model_t *m);
/* True after finish when every uncertainty the model found is scoped to the
* suites of the file it was found in (those suites are marked `uncertain` and
* can only run whole). Uncertainty in a file that registers runner suites, in
* a file with no suite of its own, in the configuration, or a failure keeps
* this false, and nothing may be narrowed. complete() implies narrowable(). */
bool cbm_test_model_narrowable(const cbm_test_model_t *m);
/* Descriptor mode is independent of the legacy six-spelling constructor.
* Copies every original record, ordered name_args, optional template and
* preset switch. NULL means invalid input or allocation failure. Unsupported
* mapping produces an owned diagnostic and prevents complete() from being true.
* Exact supported explicit case template: "{suite}:{name}". Missing templates
* do not acquire a default. Multi-component names are retained but unsupported.
* c-cbm supplies C declarations plus the existing suite_ function form. */
typedef enum {
CBM_TEST_MODEL_MAPPING_OK = 0,
CBM_TEST_MODEL_MAPPING_INVALID_INPUT,
CBM_TEST_MODEL_MAPPING_UNSUPPORTED_LANGUAGE,
CBM_TEST_MODEL_MAPPING_UNSUPPORTED_PRESET,
CBM_TEST_MODEL_MAPPING_UNSUPPORTED_NAME,
CBM_TEST_MODEL_MAPPING_UNSUPPORTED_TEMPLATE,
CBM_TEST_MODEL_MAPPING_AMBIGUOUS,
CBM_TEST_MODEL_MAPPING_MISSING_ARGUMENT,
CBM_TEST_MODEL_MAPPING_UNSUPPORTED_ARGUMENT,
CBM_TEST_MODEL_MAPPING_MALFORMED_INVOCATION,
CBM_TEST_MODEL_MAPPING_UNSUPPORTED_FORM
} cbm_test_model_mapping_status_t;
typedef struct {
cbm_test_model_mapping_status_t reason;
int declaration_index; /* original zero-based record index; -1 for builtin/input */
const char *file; /* borrowed model-owned copy, or NULL for config issues */
int line; /* 1-based source line, or 0 for config/input issues */
} cbm_test_model_mapping_issue_t;
cbm_test_model_t *cbm_test_model_new_declarations(const cbm_test_declarations_t *declarations);
/* Descriptor mode requires explicit language. Accepted canonical config names:
* c/cpp/cuda; also accepts the discover API's C/C++/CUDA display names, and c++.
* This adapter does not handle native presets. Applicable gtest in C++/CUDA
* prevents narrowing; unrelated default native presets do not poison C.
* false means invalid state/input or allocation failure. Mapping uncertainty
* may return true: callers MUST require model_complete() before narrowing. */
bool cbm_test_model_add_source_language(cbm_test_model_t *model, const char *file,
const char *language, const char *text, size_t len);
/* Canonically smallest issue by (file, line, declaration_index, reason), not
* discovery order. Strings and original declaration rows live until model free.
* NULL model yields INVALID_INPUT and initializes issue when supplied. */
cbm_test_model_mapping_status_t cbm_test_model_mapping_status(
const cbm_test_model_t *model, cbm_test_model_mapping_issue_t *issue);
const cbm_test_declaration_t *cbm_test_model_declarations(const cbm_test_model_t *model,
int *count);
/* Cases, suites and registrations are ordered by (file, line); runner suites by
* name, each once. The order never depends on the order the files were added
* in. */
const cbm_test_case_t *cbm_test_model_cases(const cbm_test_model_t *m, int *count);
const cbm_test_suite_t *cbm_test_model_suites(const cbm_test_model_t *m, int *count);
const cbm_test_registration_t *cbm_test_model_registrations(const cbm_test_model_t *m, int *count);
const cbm_test_runner_suite_t *cbm_test_model_runner_suites(const cbm_test_model_t *m, int *count);
/* ── Part 2: the change ──────────────────────────────────────────────
* A unified diff with zero context lines, as git writes it under the pinned
* flags below, read into files and hunks. The reader takes text, not a
* repository, so the selection is a function of the diff bytes. */
/* The flags that make `git diff` output independent of the user's git
* settings: no external diff or textconv driver, no renames, no color, one
* algorithm, fixed prefixes, zero context. Spliced in as
* `git <CBM_TEST_IMPACT_GIT_PINS> diff <CBM_TEST_IMPACT_DIFF_FLAGS> A B`. */
#define CBM_TEST_IMPACT_GIT_PINS \
"-c core.quotePath=false -c color.ui=never -c diff.noprefix=false " \
"-c diff.mnemonicPrefix=false"
#define CBM_TEST_IMPACT_DIFF_FLAGS \
"--no-ext-diff --no-textconv --no-color --no-renames --diff-algorithm=myers " \
"--unified=0 --inter-hunk-context=0 --src-prefix=a/ --dst-prefix=b/"
typedef struct {
/* Lines on the new side: [start, start + count). A hunk that only removes
* lines has count 0, and start is then the line the removal follows. */
int start;
int count;
const char *const *added; /* the new lines, without the leading '+' */
int added_count;
const char *const *removed; /* the old lines, without the leading '-' */
int removed_count;
} cbm_diff_hunk_t;
typedef struct {
const char *path;
bool created; /* the file did not exist before */
bool deleted; /* the file does not exist any more */
bool binary; /* git printed no text for it: there are no hunks to read */
const cbm_diff_hunk_t *hunks;
int hunk_count;
} cbm_diff_file_t;
typedef struct cbm_diff cbm_diff_t;
/* NULL when out of memory. A diff the reader does not fully understand still
* parses; cbm_diff_complete then says so. */
cbm_diff_t *cbm_diff_parse(const char *text, size_t len);
void cbm_diff_free(cbm_diff_t *d);
/* Files ordered by path. */
const cbm_diff_file_t *cbm_diff_files(const cbm_diff_t *d, int *count);
/* false = something in the text was not understood (a quoted path, a hunk
* shorter than its header says, a line outside any file). What was read is
* then not the whole change, and nothing may be narrowed by it. */
bool cbm_diff_complete(const cbm_diff_t *d);
/* Coverage-map format 1 from scripts/test-impact/coverage-map.py. These are
* internal data accessors, not an admission decision: callers must separately
* verify metadata, provenance, ancestry, age and harness/build compatibility.
* Missing tests and incomplete test/setup rows cannot justify narrowing. */
typedef struct {
int id;
const char *file; /* empty when only an external profile name was available */
const char *name;
} cbm_coverage_function_t;
typedef struct {
const char *suite;
const char *name; /* "*" is between-test setup for the entire suite */
bool complete;
const char *reason;
const int *function_ids; /* ascending, unique; IDs index the function table */
int function_count;
} cbm_coverage_test_t;
typedef struct cbm_coverage_map cbm_coverage_map_t;
/* Explicit single-image profile identity format; no admission is implied. */
typedef enum {
CBM_COVERAGE_FORMAT_NONE = 0,
CBM_COVERAGE_FORMAT_FUNCTIONS = 1,
CBM_COVERAGE_FORMAT_PROFILES = 2
} cbm_coverage_format_t;
typedef struct {
char image_sha256[65]; /* lowercase SHA-256 of the one immutable image */
} cbm_coverage_profile_binding_t;
typedef struct {
int id; /* dense 0-based; row index */
const unsigned char *name; /* owned bytes; NOT a C-string contract */
size_t name_length; /* positive; embedded NUL is representable */
uint64_t function_hash; /* all 64 bits, without masking */
uint64_t counter_count; /* positive shape; no counter values stored */
} cbm_coverage_profile_t;
typedef struct {
uint64_t max_input_bytes; /* profiles_len + tests_len; <= UINT64_MAX/8 */
uint64_t max_items; /* exactly 1 header + profile rows + test rows + ID references */
size_t max_alloc_bytes; /* cumulative logical allocation requests, including scratch */
int max_ids; /* positive, <= INT_MAX; dense universe cardinality */
} cbm_coverage_parse_limits_t;
typedef bool (*cbm_coverage_parse_cancel_fn)(void *context);
typedef enum {
CBM_COVERAGE_PARSE_OK = 0,
CBM_COVERAGE_PARSE_INVALID, /* bad pointers/limits, invalid C call */
CBM_COVERAGE_PARSE_FORMAT, /* malformed/noncanonical identities or invalid test rows */
CBM_COVERAGE_PARSE_UNSUPPORTED, /* well-formed recognized header, wire version != 2 */
CBM_COVERAGE_PARSE_LIMIT, /* byte/item/ID/logical allocation/representation limit */
CBM_COVERAGE_PARSE_CANCELLED,
CBM_COVERAGE_PARSE_OOM
} cbm_coverage_parse_status_t;
/* profiles.tsv strict header/rows are specified in the companion proposal.
* Clear *out before all other validation. profiles must contain its header;
* tests=NULL is allowed only with tests_len=0. No NUL terminators required.
* limits and out are required; every limit is positive. Callback may be NULL.
* Inputs/limits/context remain immutable for this synchronous call; callback
* is prompt, non-reentrant and cannot mutate/free inputs or call this owner.
* Poll before parsing, within <=65536 bytes or <=4096 visited elements,
* including hash/copy/comparison/sort work, and before publishing success.
* Every non-OK result leaves *out=NULL and frees all call-owned allocations.
* OK owns all data in CBMArena storage and permits unobserved IDs explicitly.
* OK never establishes image/universe/row completeness or artifact admission.
* Use existing cbm_coverage_map_free; no shared mutable or cancellation state. */
cbm_coverage_parse_status_t cbm_coverage_map_parse_v2(const void *profiles, size_t profiles_len,
const char *tests, size_t tests_len,
const cbm_coverage_parse_limits_t *limits,
cbm_coverage_parse_cancel_fn cancelled,
void *cancel_context,
cbm_coverage_map_t **out);
/* O(1) borrowed views until map_free. NULL map -> NONE / 0 / NULL.
* Generic count and exact digest work for formats 1 and 2. No reserialization:
* v1 digest = original functions.tsv; v2 digest = entire original profiles.tsv,
* including binding header. SHA-256 is a lowercase 64-hex C string. */
cbm_coverage_format_t cbm_coverage_map_format(const cbm_coverage_map_t *map);
int cbm_coverage_map_id_count(const cbm_coverage_map_t *map);
const char *cbm_coverage_map_identity_sha256(const cbm_coverage_map_t *map);
/* Format 2 only; other formats/NULL -> NULL and, for profiles, count=0.
* count may be NULL. For a zero-ID v2 map profiles returns NULL/count=0,
* but binding remains available. Records remain in dense ID order. */
const cbm_coverage_profile_binding_t *cbm_coverage_map_profile_binding(
const cbm_coverage_map_t *map);
const cbm_coverage_profile_t *cbm_coverage_map_profiles(const cbm_coverage_map_t *map, int *count);
/* Existing test/find_test/intersection/free APIs retain their row semantics.
* On v2, old functions() returns NULL/count=0, find_function() returns NULL,
* functions_sha256() returns NULL, metadata_matches() returns false, and
* check_receipt() returns INVALID|METADATA before interpreting v1 evidence.
* All format-1 behavior, uniqueness and observed-ID guards stay unchanged. */
/* Explicit byte lengths; inputs need not be NUL-terminated. LF and CRLF are
* accepted. NULL means invalid/incomplete syntax, inconsistent references, or
* allocation failure; no partial map is returned. Does not read meta.json. */
cbm_coverage_map_t *cbm_coverage_map_parse(const char *functions, size_t functions_len,
const char *tests, size_t tests_len);
void cbm_coverage_map_free(cbm_coverage_map_t *map);
/* Exact original functions.tsv byte digest; borrowed until map_free. */
const char *cbm_coverage_map_functions_sha256(const cbm_coverage_map_t *map);
const cbm_coverage_function_t *cbm_coverage_map_functions(const cbm_coverage_map_t *map,
int *count);
/* Rows are ordered by (suite, name), independent of input order. */
const cbm_coverage_test_t *cbm_coverage_map_tests(const cbm_coverage_map_t *map, int *count);
const cbm_coverage_function_t *cbm_coverage_map_find_function(const cbm_coverage_map_t *map,
const char *file, const char *name);
const cbm_coverage_test_t *cbm_coverage_map_find_test(const cbm_coverage_map_t *map,
const char *suite, const char *name);
/* Coverage intersection only. false does not establish that a test is safe
* to omit: static reachability, incomplete/setup rows and other rules still apply. */
bool cbm_coverage_test_intersects(const cbm_coverage_test_t *test, const int *changed_ids,
int changed_count);
/* Cross-check format-1 metadata against every parsed row and the trusted
* artifact manifest's commit. Does not establish age, ancestry, harness/build
* compatibility, provenance or expected runner membership; all remain required.
* Valid incomplete rows stay incomplete and cannot justify narrowing. */
bool cbm_coverage_map_metadata_matches(const cbm_coverage_map_t *map, const char *metadata,
size_t len, const char *expected_commit);
/* Query-local project configuration. No process environment or global config
* participates. Only the fields consumed by this API are validated here;
* the selection engine must validate its additional rules before narrowing. */
typedef struct cbm_test_config cbm_test_config_t;
/* Read one regular JSON file, at most 64 KiB. optional permits an absent
* default file only; malformed, unreadable and oversized files always fail.
* NULL means invalid configuration or allocation failure. */
cbm_test_config_t *cbm_test_config_load(const char *path, bool optional);
void cbm_test_config_free(cbm_test_config_t *config);
bool cbm_test_config_is_test_file(const cbm_test_config_t *config, const char *file);
/* Exact source-byte digest (or a domain-separated absent-file digest). */
const char *cbm_test_config_digest(const cbm_test_config_t *config);
/* Pure hybrid-selection kernel. It performs no I/O and does not admit an
* artifact. The adapter must establish the evidence flags before calling it.
* A false completeness flag is uncertainty, never proof of no changes. */
typedef struct {
const char *file;
const char *test;
/* An unambiguous case mapping in the complete static traversal. Merely
* finding a graph node is insufficient. Missing rows mean unknown. */
bool mapped;
bool reached;
bool changed;
} cbm_test_reach_t;
enum {
CBM_TEST_SELECT_CHANGED = 1U << 0,
CBM_TEST_SELECT_STATIC = 1U << 1,
CBM_TEST_SELECT_COVERAGE = 1U << 2,
CBM_TEST_SELECT_UNMAPPED = 1U << 3,
CBM_TEST_SELECT_COVERAGE_UNKNOWN = 1U << 4,
CBM_TEST_SELECT_SETUP_HIT = 1U << 5,
CBM_TEST_SELECT_SETUP_UNKNOWN = 1U << 6,
CBM_TEST_SELECT_INVENTORY_UNKNOWN = 1U << 7,
CBM_TEST_SELECT_CONDITIONAL = 1U << 8,
CBM_TEST_SELECT_ARTIFACT_REJECTED = 1U << 9,
CBM_TEST_SELECT_DIFF_INCOMPLETE = 1U << 10,
CBM_TEST_SELECT_STATIC_INCOMPLETE = 1U << 11,
CBM_TEST_SELECT_CHANGE_IDENTITY_UNKNOWN = 1U << 12,
CBM_TEST_SELECT_INVALID_INPUT = 1U << 13,
CBM_TEST_SELECT_CHANGED_UNREGISTERED = 1U << 14,
CBM_TEST_SELECT_RULE = 1U << 15
};
/* Reached/changed suite bodies and explicit suite rules select the whole
* suite even when no individual case is reached. Names come from the runner. */
typedef struct {
const char *suite;
bool reached;
bool changed;
bool rule;
} cbm_test_suite_trigger_t;
typedef struct {
const cbm_test_model_t *model; /* finished, borrowed for this call only */
const cbm_coverage_map_t *coverage;
const cbm_test_reach_t *reach;
int reach_count;
const cbm_test_suite_trigger_t *suite_triggers;
int suite_trigger_count;
const int *changed_function_ids;
int changed_function_count;
bool has_changes;
bool diff_complete;
bool inventory_complete; /* external file/runner inventory, separate from model parse */
bool static_complete; /* complete seeds and uncapped no-lift traversal */
bool coverage_admitted; /* independently verified artifact admission */
bool coverage_changes_complete; /* includes old-side/deleted identities */
} cbm_test_selection_input_t;
typedef struct {
const char *name;
bool whole;
unsigned reasons; /* union of whole-suite causes and retained-test reasons */
} cbm_test_selected_suite_t;
typedef struct {
const char *suite;
const char *test;
const char *file;
unsigned reasons;
} cbm_test_selected_case_t;
typedef struct cbm_test_selection cbm_test_selection_t;
/* NULL means allocation failure: callers MUST use their full-run fallback.
* No successful partial result is returned. Results own all their strings.
* A run-all result has no suite/case rows; its reason mask is nonzero.
* Whole suites dominate individual rows. Selected conditional registrations
* use whole suites until the runner has an explicit optional-test contract. */
cbm_test_selection_t *cbm_test_select(const cbm_test_selection_input_t *input);
void cbm_test_selection_free(cbm_test_selection_t *selection);
unsigned cbm_test_selection_run_all(const cbm_test_selection_t *selection);
/* Only selected suites/cases appear, ordered by name and (suite,test). */
const cbm_test_selected_suite_t *cbm_test_selection_suites(const cbm_test_selection_t *selection,
int *count);
const cbm_test_selected_case_t *cbm_test_selection_cases(const cbm_test_selection_t *selection,
int *count);
/* Coverage receipt-policy checks. This is NOT artifact retrieval or proof of
* provenance: the adapter must independently establish the context below.
* SHA-256 strings use 64 lowercase hex digits and bind exact file bytes. */
typedef struct {
const char *commit;
const char *functions_sha256;
const char *tests_sha256;
const char *metadata_sha256;
const char *graph_sha256;
const char *compatibility_sha256;
/* Oldest retained test/setup observation, including complete empty rows.
* A full-refresh baseline is valid only if all retained rows were refreshed.
* Incremental publication MUST NOT advance this timestamp by itself. */
int64_t oldest_observation_at;
} cbm_coverage_receipt_t;
typedef struct {
/* Authenticates the entire receipt tuple and expected producer/repository,
* not merely a download location. Never populated from artifact JSON. */
bool source_verified;
const char *trusted_commit;
/* Evidence is for this exact pair, in this one frozen comparison. Git
* errors/unknown ancestry are false; replacement objects must be disabled. */
bool ancestor_verified;
const char *ancestor_commit;
const char *ancestor_merge_base;
const char *merge_base;
/* Independently measured from the exact team graph imported for this map,
* before the adapter incrementally updates it to the PR head. */
const char *graph_commit;
const char *graph_sha256;
/* Independently computed with the same versioned input encoding as the
* producer, covering harness, runner, spawn helpers, config, build flags,
* platform and toolchain. Equality cannot establish those inputs itself. */
const char *compatibility_sha256;
int64_t now;
} cbm_coverage_receipt_context_t;
enum {
CBM_COVERAGE_RECEIPT_INVALID = 1U << 0,
CBM_COVERAGE_RECEIPT_SOURCE = 1U << 1,
CBM_COVERAGE_RECEIPT_ANCESTRY = 1U << 2,
CBM_COVERAGE_RECEIPT_COMMIT = 1U << 3,
CBM_COVERAGE_RECEIPT_CONTENT = 1U << 4,
CBM_COVERAGE_RECEIPT_AGE = 1U << 5,
CBM_COVERAGE_RECEIPT_COMPATIBILITY = 1U << 6,
CBM_COVERAGE_RECEIPT_METADATA = 1U << 7
};
/* Zero means every receipt-policy check passed against the supplied evidence.
* Newest eligible ancestor discovery, authenticated receipt delivery, actual
* ancestry checks, graph freshness at head and runner inventory stay with the
* adapter. A nonzero result prohibits coverage-based omission. */
unsigned cbm_coverage_map_check_receipt(const cbm_coverage_map_t *map, const char *metadata,
size_t metadata_len, const cbm_coverage_receipt_t *receipt,
const cbm_coverage_receipt_context_t *context);
/* Exact bytes retained by the query-local loader; borrowed until config free.
* An optional absent file is distinct from an invalid config (NULL). */
const char *cbm_test_config_source(const cbm_test_config_t *config, size_t *len, bool *absent);
typedef enum {
CBM_TEST_RULE_RUN_ALL,
CBM_TEST_RULE_LANES,
CBM_TEST_RULE_SUITES,
CBM_TEST_RULE_REFERENCING_TESTS,
CBM_TEST_RULE_IGNORE
} cbm_test_rule_action_t;
typedef struct {
const char *id;
cbm_test_rule_action_t action;
const char *const *paths;
int path_count;
const char *const *suites;
int suite_count;
const char *const *lanes;
int lane_count;
int key_segment; /* zero-based path_segment:N; -1 for other actions */
bool builtin;
} cbm_test_rule_t;
typedef struct {
const char *name;
const char *const *suites;
int suite_count;
const char *const *exclude_suites;
int exclude_count;
const char *const *signals;
int signal_count;
bool default_run;
bool narrow; /* omitted => false; never changes lane activation itself */
} cbm_test_lane_t;
typedef struct cbm_test_policy cbm_test_policy_t;
/* Parse rules/lanes from the exact loaded source into an independent owner.
* NULL means invalid consumed policy or allocation/expansion failure; no
* partial policy escapes. Presets/conventions/signals definitions are left to
* their adapters. Existing seed-only config loading does not call this API.
* Missing policy uses conservative built-ins and one unit lane (narrow=true,
* all suites except @perf). Project rules precede built-ins, FIRST MATCH wins.
* Pattern grammar: positive gitignore-style globs plus comma brace alternatives
* (including nested/adjacent groups); no shell evaluation, ranges or regex.
* Expansion is bounded; overflow rejects the whole policy. */
cbm_test_policy_t *cbm_test_policy_new(const cbm_test_config_t *config);
void cbm_test_policy_free(cbm_test_policy_t *policy);
const cbm_test_rule_t *cbm_test_policy_rules(const cbm_test_policy_t *policy, int *count);
const cbm_test_lane_t *cbm_test_policy_lanes(const cbm_test_policy_t *policy, int *count);
const char *cbm_test_policy_digest(const cbm_test_policy_t *policy);
/* test_impact.coverage.compatibility_paths in config order: the files besides
* the measured code whose bytes a coverage map depends on (harness, runner,
* spawn helpers, build flags). NULL with *count 0 when none are configured. */
const char *const *cbm_test_policy_compatibility_paths(const cbm_test_policy_t *policy, int *count);
/* Successful classification returns true and either the first matched rule
* or NULL for no match. false is an evaluation error, NEVER permission to
* fall through or omit. Target applicability is resolved separately: an
* unavailable target does not permit consulting a later rule. */
bool cbm_test_policy_match_path(const cbm_test_policy_t *policy, const char *path,
const cbm_test_rule_t **rule);
/* Match one known suite against lane membership or a suites rule. @perf is a
* semantic selector. These functions do not establish inventory completeness,
* platform applicability, or whether a lane is activated. false means error. */
bool cbm_test_policy_lane_selects_suite(const cbm_test_policy_t *policy, int lane,
const char *suite, bool perf, bool *selected);
bool cbm_test_policy_rule_selects_suite(const cbm_test_policy_t *policy, int rule,
const char *suite, bool perf, bool *selected);
#endif /* CBM_MCP_TEST_IMPACT_H */
+283
View File
@@ -0,0 +1,283 @@
/*
* test_impact_artifact.c — receipt.json and the bundle digests.
*/
#include "mcp/test_impact_artifact.h"
#include "foundation/compat_fs.h"
#include "foundation/mem_core.h"
#include "foundation/sha256.h"
#include <stdio.h>
#include <string.h>
#include <yyjson/yyjson.h>
enum { TA_READ_CHUNK = 65536, TA_RECEIPT_MAX = 65536, TA_PATH_CAP = 4096 };
/* yyjson allocates through the memory core, so documents and written text
* are released with cbm_free like everything else here. */
static void *ta_yy_malloc(void *ctx, size_t size) {
(void)ctx;
return cbm_alloc(CBM_MEM_CLASS_OTHER, size);
}
static void *ta_yy_realloc(void *ctx, void *block, size_t old_size, size_t size) {
(void)ctx;
(void)old_size;
return cbm_realloc(CBM_MEM_CLASS_OTHER, block, size);
}
static void ta_yy_free(void *ctx, void *block) {
(void)ctx;
cbm_free(CBM_MEM_CLASS_OTHER, block);
}
static const yyjson_alc ta_yy_alc = {ta_yy_malloc, ta_yy_realloc, ta_yy_free, NULL};
static bool ta_hex(const char *text, size_t want_a, size_t want_b) {
size_t n = text ? strlen(text) : 0;
if (n != want_a && n != want_b) {
return false;
}
for (size_t i = 0; i < n; i++) {
if (!((text[i] >= '0' && text[i] <= '9') || (text[i] >= 'a' && text[i] <= 'f'))) {
return false;
}
}
return true;
}
static void ta_hex_out(const uint8_t digest[CBM_SHA256_DIGEST_LEN], char hex[65]) {
static const char digits[] = "0123456789abcdef";
for (int i = 0; i < CBM_SHA256_DIGEST_LEN; i++) {
hex[2 * i] = digits[digest[i] >> 4];
hex[2 * i + 1] = digits[digest[i] & 15];
}
hex[64] = '\0';
}
static bool ta_path(char *out, const char *dir, const char *name) {
int n = snprintf(out, TA_PATH_CAP, "%s/%s", dir, name);
return n > 0 && n < TA_PATH_CAP;
}
/* A length-prefixed frame, so no field can run into the next. */
static void ta_frame(cbm_sha256_ctx *ctx, const void *bytes, uint64_t length) {
uint8_t prefix[8];
for (int i = 0; i < 8; i++) {
prefix[i] = (uint8_t)(length >> (56 - 8 * i));
}
cbm_sha256_update(ctx, prefix, sizeof(prefix));
if (length) {
cbm_sha256_update(ctx, bytes, (size_t)length);
}
}
/* Feeds a file's exact bytes as one frame; false on a read error.
* *absent: the file does not exist (a frame-free outcome the caller encodes). */
static bool ta_feed_file(cbm_sha256_ctx *ctx, const char *path, bool framed, bool *absent) {
*absent = false;
FILE *f = cbm_fopen(path, "rb");
if (!f) {
cbm_path_info_t info;
*absent = cbm_path_info_utf8(path, &info) != 0;
return *absent;
}
unsigned char *buf = cbm_alloc(CBM_MEM_CLASS_OTHER, TA_READ_CHUNK);
if (!buf) {
(void)fclose(f);
return false;
}
/* Size first, so a framed file's prefix is its real length. */
uint64_t total = 0;
size_t got;
while ((got = fread(buf, 1, TA_READ_CHUNK, f)) > 0) {
total += got;
}
bool ok = !ferror(f) && fseek(f, 0, SEEK_SET) == 0;
if (ok && framed) {
ta_frame(ctx, NULL, 0); /* domain: a present file */
uint8_t prefix[8];
for (int i = 0; i < 8; i++) {
prefix[i] = (uint8_t)(total >> (56 - 8 * i));
}
cbm_sha256_update(ctx, prefix, sizeof(prefix));
}
uint64_t again = 0;
while (ok && (got = fread(buf, 1, TA_READ_CHUNK, f)) > 0) {
cbm_sha256_update(ctx, buf, got);
again += got;
}
ok = ok && !ferror(f) && again == total;
cbm_free(CBM_MEM_CLASS_OTHER, buf);
ok = fclose(f) == 0 && ok;
return ok;
}
bool cbm_ti_sha256_file(const char *path, char hex[65]) {
hex[0] = '\0';
cbm_sha256_ctx ctx;
cbm_sha256_init(&ctx);
bool absent = false;
if (!path || !ta_feed_file(&ctx, path, false, &absent) || absent) {
return false;
}
uint8_t digest[CBM_SHA256_DIGEST_LEN];
cbm_sha256_final(&ctx, digest);
ta_hex_out(digest, hex);
return true;
}
bool cbm_ti_compatibility_digest(const char *root, const char *const *paths, int path_count,
const char *platform, char hex[65]) {
hex[0] = '\0';
if (!root || !platform || path_count < 0 || (path_count && !paths)) {
return false;
}
static const char domain[] = "cbm.test_impact.compatibility.v1";
cbm_sha256_ctx ctx;
cbm_sha256_init(&ctx);
ta_frame(&ctx, domain, sizeof(domain) - 1);
ta_frame(&ctx, platform, strlen(platform));
for (int i = 0; i < path_count; i++) {
char path[TA_PATH_CAP];
if (!paths[i] || !*paths[i] || !ta_path(path, root, paths[i])) {
return false;
}
ta_frame(&ctx, paths[i], strlen(paths[i]));
bool absent = false;
if (!ta_feed_file(&ctx, path, true, &absent)) {
return false;
}
if (absent) {
static const char marker[] = "absent";
ta_frame(&ctx, marker, sizeof(marker) - 1);
}
}
uint8_t digest[CBM_SHA256_DIGEST_LEN];
cbm_sha256_final(&ctx, digest);
ta_hex_out(digest, hex);
return true;
}
static bool ta_copy_string(yyjson_val *root, const char *key, char *out, size_t cap) {
yyjson_val *v = yyjson_obj_get(root, key);
const char *s = yyjson_is_str(v) ? yyjson_get_str(v) : NULL;
if (!s || strlen(s) >= cap) {
return false;
}
memcpy(out, s, strlen(s) + 1);
return true;
}
cbm_ti_receipt_status_t cbm_ti_receipt_read(const char *bundle_dir, cbm_ti_receipt_t *out) {
if (!out) {
return CBM_TI_RECEIPT_INVALID;
}
memset(out, 0, sizeof(*out));
char path[TA_PATH_CAP];
if (!bundle_dir || !ta_path(path, bundle_dir, CBM_TI_RECEIPT_FILE)) {
return CBM_TI_RECEIPT_INVALID;
}
FILE *f = cbm_fopen(path, "rb");
if (!f) {
cbm_path_info_t info;
return cbm_path_info_utf8(path, &info) != 0 ? CBM_TI_RECEIPT_ABSENT : CBM_TI_RECEIPT_IO;
}
char *text = cbm_alloc(CBM_MEM_CLASS_OTHER, TA_RECEIPT_MAX + 1);
size_t len = text ? fread(text, 1, TA_RECEIPT_MAX + 1, f) : 0;
bool read_ok = text && !ferror(f) && len <= TA_RECEIPT_MAX;
(void)fclose(f);
if (!read_ok) {
cbm_free(CBM_MEM_CLASS_OTHER, text);
return text ? CBM_TI_RECEIPT_INVALID : CBM_TI_RECEIPT_IO;
}
yyjson_doc *doc = yyjson_read_opts(text, len, 0, &ta_yy_alc, NULL);
cbm_free(CBM_MEM_CLASS_OTHER, text);
yyjson_val *root = doc ? yyjson_doc_get_root(doc) : NULL;
cbm_ti_receipt_t r = {0};
char schema[64] = "";
bool ok = yyjson_is_obj(root) && ta_copy_string(root, "schema", schema, sizeof(schema)) &&
strcmp(schema, CBM_TI_RECEIPT_SCHEMA) == 0 &&
ta_copy_string(root, "commit", r.commit, sizeof(r.commit)) &&
ta_hex(r.commit, 40, 64) &&
ta_copy_string(root, "graph_sha256", r.graph_sha256, sizeof(r.graph_sha256)) &&
ta_hex(r.graph_sha256, 64, 64) &&
ta_copy_string(root, "graph_content_sha256", r.graph_content_sha256,
sizeof(r.graph_content_sha256)) &&
ta_hex(r.graph_content_sha256, 64, 64) &&
ta_copy_string(root, "graph_topology_sha256", r.graph_topology_sha256,
sizeof(r.graph_topology_sha256)) &&
ta_hex(r.graph_topology_sha256, 64, 64) &&
ta_copy_string(root, "platform", r.platform, sizeof(r.platform));
yyjson_val *coverage = ok ? yyjson_obj_get(root, "coverage") : NULL;
if (ok && coverage && !yyjson_is_null(coverage)) {
yyjson_val *oldest = yyjson_obj_get(coverage, "oldest_observation_at");
r.has_coverage = true;
ok = yyjson_is_obj(coverage) &&
ta_copy_string(coverage, "functions_sha256", r.functions_sha256, 65) &&
ta_hex(r.functions_sha256, 64, 64) &&
ta_copy_string(coverage, "tests_sha256", r.tests_sha256, 65) &&
ta_hex(r.tests_sha256, 64, 64) &&
ta_copy_string(coverage, "metadata_sha256", r.metadata_sha256, 65) &&
ta_hex(r.metadata_sha256, 64, 64) &&
ta_copy_string(coverage, "compatibility_sha256", r.compatibility_sha256, 65) &&
ta_hex(r.compatibility_sha256, 64, 64) && yyjson_is_int(oldest) &&
yyjson_get_sint(oldest) > 0;
r.oldest_observation_at = ok ? yyjson_get_sint(oldest) : 0;
}
yyjson_doc_free(doc);
if (!ok) {
return CBM_TI_RECEIPT_INVALID;
}
*out = r;
return CBM_TI_RECEIPT_OK;
}
bool cbm_ti_receipt_write(const char *bundle_dir, const cbm_ti_receipt_t *r) {
char path[TA_PATH_CAP];
char tmp[TA_PATH_CAP];
if (!bundle_dir || !r || !ta_path(path, bundle_dir, CBM_TI_RECEIPT_FILE) ||
!ta_path(tmp, bundle_dir, CBM_TI_RECEIPT_FILE ".tmp")) {
return false;
}
yyjson_mut_doc *doc = yyjson_mut_doc_new(&ta_yy_alc);
yyjson_mut_val *root = doc ? yyjson_mut_obj(doc) : NULL;
if (!root) {
yyjson_mut_doc_free(doc);
return false;
}
yyjson_mut_doc_set_root(doc, root);
yyjson_mut_obj_add_str(doc, root, "schema", CBM_TI_RECEIPT_SCHEMA);
yyjson_mut_obj_add_str(doc, root, "commit", r->commit);
yyjson_mut_obj_add_str(doc, root, "graph_sha256", r->graph_sha256);
yyjson_mut_obj_add_str(doc, root, "graph_content_sha256", r->graph_content_sha256);
yyjson_mut_obj_add_str(doc, root, "graph_topology_sha256", r->graph_topology_sha256);
yyjson_mut_obj_add_str(doc, root, "platform", r->platform);
if (r->has_coverage) {
yyjson_mut_val *coverage = yyjson_mut_obj_add_obj(doc, root, "coverage");
yyjson_mut_obj_add_str(doc, coverage, "functions_sha256", r->functions_sha256);
yyjson_mut_obj_add_str(doc, coverage, "tests_sha256", r->tests_sha256);
yyjson_mut_obj_add_str(doc, coverage, "metadata_sha256", r->metadata_sha256);
yyjson_mut_obj_add_str(doc, coverage, "compatibility_sha256", r->compatibility_sha256);
yyjson_mut_obj_add_int(doc, coverage, "oldest_observation_at", r->oldest_observation_at);
} else {
yyjson_mut_obj_add_null(doc, root, "coverage");
}
size_t len = 0;
char *json = yyjson_mut_write_opts(doc, YYJSON_WRITE_PRETTY, &ta_yy_alc, &len, NULL);
yyjson_mut_doc_free(doc);
if (!json) {
return false;
}
FILE *f = cbm_fopen(tmp, "wb");
bool ok = f && fwrite(json, 1, len, f) == len && fputc('\n', f) != EOF;
cbm_free(CBM_MEM_CLASS_OTHER, json);
if (f) {
ok = fclose(f) == 0 && ok;
}
ok = ok && cbm_rename_replace(tmp, path) == 0;
if (!ok) {
(void)cbm_unlink(tmp);
}
return ok;
}
+75
View File
@@ -0,0 +1,75 @@
/*
* test_impact_artifact.h — The team artifact bundle of one commit.
*
* A bundle travels from the main-branch producer to PR selection (never
* committed). Layout under its directory:
*
* receipt.json this module; the bundle's authority
* .codebase-memory/graph.db.zst the commit's frozen graph (artifact.h)
* .codebase-memory/artifact.json
* coverage/functions.tsv optional coverage map, format 1
* coverage/tests.tsv
* coverage/meta.json
*
* The receipt binds every part by SHA-256: the graph by the digest of the
* graph as an importer sees it (cbm_store_graph_digest of the imported
* database), the map by the exact bytes of its three files. A digest is
* content binding only; where a bundle came from is established by whoever
* fetched it, never by anything inside it.
*/
#ifndef CBM_TEST_IMPACT_ARTIFACT_H
#define CBM_TEST_IMPACT_ARTIFACT_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define CBM_TI_RECEIPT_FILE "receipt.json"
#define CBM_TI_RECEIPT_SCHEMA "cbm.test_impact.artifact.v1"
#define CBM_TI_COVERAGE_DIR "coverage"
typedef struct {
char commit[65]; /* 40 or 64 lowercase hex */
char graph_sha256[65]; /* 64 lowercase hex */
/* cbm_store_graph_content_digest of the same graph: equal for equal graphs
* however they were built (the weekly incremental-equals-full check). */
char graph_content_sha256[65];
/* cbm_store_graph_topology_digest: what selection reads. The weekly check
* fails when an incremental graph's topology differs from the full one. */
char graph_topology_sha256[65];
bool has_coverage;
char functions_sha256[65];
char tests_sha256[65];
char metadata_sha256[65];
char compatibility_sha256[65];
/* Oldest retained coverage observation (seconds since the epoch). A full
* refresh sets it; an incremental publish carries it forward unchanged. */
int64_t oldest_observation_at;
char platform[128]; /* producer's platform/toolchain label, informational */
} cbm_ti_receipt_t;
typedef enum {
CBM_TI_RECEIPT_OK = 0,
CBM_TI_RECEIPT_ABSENT, /* no receipt.json */
CBM_TI_RECEIPT_INVALID, /* unreadable shape, unknown schema, a malformed field */
CBM_TI_RECEIPT_IO,
} cbm_ti_receipt_status_t;
/* Strict: every field present and well formed, no unknown schema. Clears *out
* first; on any non-OK status *out stays zero. */
cbm_ti_receipt_status_t cbm_ti_receipt_read(const char *bundle_dir, cbm_ti_receipt_t *out);
/* Writes receipt.json atomically (temporary file + rename). */
bool cbm_ti_receipt_write(const char *bundle_dir, const cbm_ti_receipt_t *receipt);
/* Lowercase SHA-256 hex of a file's exact bytes. false when unreadable. */
bool cbm_ti_sha256_file(const char *path, char hex[65]);
/* Versioned digest of what coverage depends on beyond the code it measures:
* the platform/toolchain label and the exact bytes of each listed path under
* root (an absent path is encoded as absent, so adding one changes it).
* Producer and consumer call this same function; equality is the check. */
bool cbm_ti_compatibility_digest(const char *root, const char *const *paths, int path_count,
const char *platform, char hex[65]);
#endif /* CBM_TEST_IMPACT_ARTIFACT_H */
+524
View File
@@ -0,0 +1,524 @@
#include "mcp/test_impact_changes.h"
#include "foundation/arena.h"
#include <limits.h>
#include <stdint.h>
#include <string.h>
struct cbm_changes {
CBMArena arena;
unsigned char *name_status;
size_t name_status_length;
unsigned char *patch;
size_t patch_length;
cbm_diff_t *diff;
cbm_change_file_t *files;
size_t count;
cbm_changes_state_t state;
unsigned issues;
bool reconciled;
bool can_narrow;
};
typedef struct {
const cbm_diff_file_t *file;
size_t count;
} changes_match_t;
typedef struct {
const unsigned char *bytes;
size_t length;
} changes_slice_t;
/* Git tree paths use '/' on every host; backslash/colon are literal bytes. */
static bool changes_path_valid(const unsigned char *path, size_t length) {
if (!length || path[0] == '/' || path[length - 1] == '/') {
return false;
}
size_t start = 0;
for (size_t i = 0; i <= length; i++) {
if (i < length && path[i] != '/') {
if (path[i] == 0) {
return false;
}
continue;
}
size_t component = i - start;
if (!component || (component == 1 && path[start] == '.') ||
(component == 2 && path[start] == '.' && path[start + 1] == '.')) {
return false;
}
start = i + 1;
}
return true;
}
static bool changes_record(const unsigned char *bytes, size_t length, size_t *offset, char *status,
changes_slice_t *path) {
size_t at = *offset;
if (length - at < 4 || bytes[at + 1] != 0 ||
(bytes[at] != 'A' && bytes[at] != 'M' && bytes[at] != 'D' && bytes[at] != 'T')) {
return false;
}
const unsigned char *first = bytes + at + 2;
const unsigned char *end = memchr(first, 0, length - at - 2);
if (!end) {
return false;
}
path->bytes = first;
path->length = (size_t)(end - first);
if (!changes_path_valid(path->bytes, path->length)) {
return false;
}
*status = (char)bytes[at];
*offset = (size_t)(end - bytes) + 1;
return true;
}
static int changes_path_compare(const unsigned char *a, size_t a_length, const unsigned char *b,
size_t b_length) {
size_t common = a_length < b_length ? a_length : b_length;
int order = memcmp(a, b, common); /* specified unsigned-byte comparison */
return order ? order : (a_length > b_length) - (a_length < b_length);
}
/* Stable merge order, including equal keys before duplicate rejection. */
static void changes_sort(cbm_change_file_t *files, cbm_change_file_t *scratch, size_t count) {
cbm_change_file_t *source = files;
cbm_change_file_t *target = scratch;
for (size_t width = 1; width < count;) {
for (size_t left = 0; left < count;) {
size_t middle = left + (width < count - left ? width : count - left);
size_t right = middle + (width < count - middle ? width : count - middle);
size_t a = left;
size_t b = middle;
for (size_t i = left; i < right; i++) {
if (b == right || (a < middle && changes_path_compare(
source[a].path, source[a].path_length,
source[b].path, source[b].path_length) <= 0)) {
target[i] = source[a++];
} else {
target[i] = source[b++];
}
}
left = right;
}
cbm_change_file_t *swap = source;
source = target;
target = swap;
if (width > count / 2) {
break;
}
width *= 2;
}
if (source != files) {
memcpy(files, source, count * sizeof(*files));
}
}
static size_t changes_find(const cbm_changes_t *changes, const unsigned char *path, size_t length) {
size_t low = 0;
size_t high = changes->count;
while (low < high) {
size_t middle = low + (high - low) / 2;
const cbm_change_file_t *file = &changes->files[middle];
int order = changes_path_compare(file->path, file->path_length, path, length);
if (!order) {
return middle;
}
if (order < 0) {
low = middle + 1;
} else {
high = middle;
}
}
return SIZE_MAX;
}
static bool changes_starts(changes_slice_t line, const char *prefix) {
size_t length = strlen(prefix);
return line.length >= length && memcmp(line.bytes, prefix, length) == 0;
}
static bool changes_line(const unsigned char *bytes, size_t length, size_t *offset,
changes_slice_t *line) {
if (*offset == length) {
return false;
}
const unsigned char *first = bytes + *offset;
const unsigned char *end = memchr(first, '\n', length - *offset);
line->bytes = first;
line->length = end ? (size_t)(end - first) : length - *offset;
*offset += line->length + (end ? 1 : 0);
return true;
}
static bool changes_header_path(changes_slice_t line, changes_slice_t *path) {
static const char prefix[] = "diff --git a/";
const size_t prefix_length = sizeof(prefix) - 1;
if (!changes_starts(line, prefix)) {
return false;
}
size_t rest = line.length - prefix_length;
if (rest < 5 || (rest - 3) % 2) {
return false;
}
size_t length = (rest - 3) / 2;
const unsigned char *first = line.bytes + prefix_length;
if (memcmp(first + length, " b/", 3) || memcmp(first, first + length + 3, length) ||
!changes_path_valid(first, length)) {
return false;
}
for (size_t i = 0; i < length; i++) {
/* Git quotes these bytes even with core.quotePath=false. The existing
* reader does not decode that representation. Non-UTF8 remains raw. */
if (first[i] < 32 || first[i] == 127 || first[i] == '"' || first[i] == '\\') {
return false;
}
}
*path = (changes_slice_t){first, length};
return true;
}
static bool changes_mode(changes_slice_t line, const char *prefix) {
size_t length = strlen(prefix);
if (line.length != length + 6) {
return false;
}
const unsigned char *mode = line.bytes + length;
return memcmp(mode, "100644", 6) == 0 || memcmp(mode, "100755", 6) == 0 ||
memcmp(mode, "120000", 6) == 0 || memcmp(mode, "160000", 6) == 0;
}
static bool changes_side_path(changes_slice_t line, const cbm_change_file_t *file, bool old) {
const unsigned char *bytes = line.bytes + 4; /* checked ---/+++ prefix */
size_t length = line.length - 4;
bool absent = old ? file->status == 'A' : file->status == 'D';
if (absent) {
return length == 9 && memcmp(bytes, "/dev/null", 9) == 0;
}
if (length < 2 || bytes[0] != (old ? 'a' : 'b') || bytes[1] != '/') {
return false;
}
bytes += 2;
length -= 2;
/* Accept an exact trailing TAB delimiter after the known path, never
* timestamps, path truncation or another spelling. */
if (length > file->path_length && length - file->path_length == 1 &&
bytes[length - 1] == '\t') {
length--;
}
return length == file->path_length && memcmp(bytes, file->path, length) == 0;
}
/* The legacy reader discards the old start. Keep its old-side range from
* certifying an impossible nonempty range beginning at zero. */
static bool changes_old_range_usable(changes_slice_t line) {
if (!changes_starts(line, "@@ -")) {
return false;
}
size_t offset = 4;
int values[2] = {0, 1};
for (int part = 0; part < 2; part++) {
size_t first = offset;
int value = 0;
while (offset < line.length && line.bytes[offset] >= '0' && line.bytes[offset] <= '9') {
int digit = line.bytes[offset++] - '0';
if (value > (INT_MAX - digit) / 10) {
return false;
}
value = value * 10 + digit;
}
if (offset == first) {
return false;
}
values[part] = value;
if (part == 0 && offset < line.length && line.bytes[offset] == ',') {
offset++;
continue;
}
break;
}
return offset < line.length && line.bytes[offset] == ' ' && (values[1] == 0 || values[0] > 0);
}
/* The legacy file result does not retain old/new mode metadata or the old
* hunk start. Audit headers, never payload (removed text can look like ---). */
static void changes_audit_headers(cbm_changes_t *changes) {
size_t offset = 0;
size_t current = SIZE_MAX;
bool in_hunks = false;
bool old_seen = false;
bool new_seen = false;
changes_slice_t line;
while (changes_line(changes->patch, changes->patch_length, &offset, &line)) {
if (changes_starts(line, "diff ")) {
changes_slice_t path;
current = SIZE_MAX;
in_hunks = false;
old_seen = false;
new_seen = false;
if (!changes_header_path(line, &path)) {
changes->issues |= CBM_CHANGES_PATCH_INCOMPLETE;
} else {
current = changes_find(changes, path.bytes, path.length);
if (current == SIZE_MAX) {
changes->issues |= CBM_CHANGES_PATCH_EXTRA_PATH;
}
}
continue;
}
if (changes_starts(line, "@@ ")) {
if (!changes_old_range_usable(line)) {
changes->issues |= CBM_CHANGES_PATCH_INCOMPLETE;
}
if (current != SIZE_MAX && (!old_seen || !new_seen)) {
changes->issues |= CBM_CHANGES_PATCH_INCOMPLETE;
}
in_hunks = true;
}
if (current == SIZE_MAX || in_hunks) {
continue;
}
cbm_change_file_t *file = &changes->files[current];
if (changes_starts(line, "old mode ") || changes_starts(line, "new mode ")) {
file->reasons |= CBM_CHANGE_MODE;
if (!changes_mode(line,
changes_starts(line, "old mode ") ? "old mode " : "new mode ")) {
changes->issues |= CBM_CHANGES_PATCH_INCOMPLETE;
}
} else if (changes_starts(line, "new file mode ") ||
changes_starts(line, "deleted file mode ")) {
if (!changes_mode(line, changes_starts(line, "new file mode ")
? "new file mode "
: "deleted file mode ")) {
changes->issues |= CBM_CHANGES_PATCH_INCOMPLETE;
}
} else if (changes_starts(line, "--- ")) {
if (old_seen || !changes_side_path(line, file, true)) {
changes->issues |= CBM_CHANGES_PATCH_INCOMPLETE;
}
old_seen = true;
} else if (changes_starts(line, "+++ ")) {
if (new_seen || !changes_side_path(line, file, false)) {
changes->issues |= CBM_CHANGES_PATCH_INCOMPLETE;
}
new_seen = true;
}
}
}
static bool changes_hunks_usable(const cbm_diff_file_t *file) {
if (file->hunk_count <= 0 || !file->hunks) {
return false;
}
for (int i = 0; i < file->hunk_count; i++) {
const cbm_diff_hunk_t *hunk = &file->hunks[i];
if (hunk->start < 0 || hunk->count < 0 || hunk->count > INT_MAX - hunk->start ||
hunk->added_count != hunk->count || hunk->removed_count < 0 ||
(hunk->count > 0 && (!hunk->added || hunk->start == 0)) ||
(hunk->removed_count > 0 && !hunk->removed) ||
(hunk->added_count == 0 && hunk->removed_count == 0)) {
return false;
}
}
return true;
}
static void changes_reconcile(cbm_changes_t *changes, changes_match_t *matches) {
int patch_count = 0;
const cbm_diff_file_t *files = cbm_diff_files(changes->diff, &patch_count);
if (!cbm_diff_complete(changes->diff)) {
changes->issues |= CBM_CHANGES_PATCH_INCOMPLETE;
}
for (int i = 0; i < patch_count; i++) {
const cbm_diff_file_t *file = &files[i];
size_t length = strlen(file->path);
size_t index = changes_path_valid((const unsigned char *)file->path, length)
? changes_find(changes, (const unsigned char *)file->path, length)
: SIZE_MAX;
if (index == SIZE_MAX) {
changes->issues |= CBM_CHANGES_PATCH_EXTRA_PATH;
continue;
}
changes_match_t *match = &matches[index];
match->file = file;
if (match->count++ != 0) {
changes->issues |= CBM_CHANGES_PATCH_DUPLICATE_PATH;
}
char status = changes->files[index].status;
if (file->created != (status == 'A') || file->deleted != (status == 'D')) {
changes->issues |= CBM_CHANGES_PATCH_FLAG_MISMATCH;
}
}
for (size_t i = 0; i < changes->count; i++) {
if (!matches[i].count) {
changes->issues |= CBM_CHANGES_PATCH_MISSING_PATH;
}
}
if (changes->count == 0 && changes->patch_length != 0) {
changes->issues |= CBM_CHANGES_PATCH_WITHOUT_NAMES;
}
changes_audit_headers(changes);
changes->reconciled = changes->issues == 0;
changes->can_narrow = changes->reconciled;
for (size_t i = 0; i < changes->count; i++) {
cbm_change_file_t *file = &changes->files[i];
if (file->status == 'T') {
file->reasons |= CBM_CHANGE_TYPE;
}
if (matches[i].count == 1) {
if (matches[i].file->binary) {
file->reasons |= CBM_CHANGE_BINARY;
}
if (!changes_hunks_usable(matches[i].file)) {
file->reasons |= CBM_CHANGE_NO_HUNKS;
}
}
if (!changes->reconciled) {
file->reasons |= CBM_CHANGE_UNRECONCILED;
}
if (file->reasons) {
file->evidence = CBM_CHANGE_WHOLE_FILE;
changes->can_narrow = false;
} else {
file->evidence = CBM_CHANGE_HUNKS;
file->patch_file = matches[i].file;
}
}
changes->state =
changes->count ? CBM_CHANGES_NONEMPTY
: (changes->patch_length == 0 && changes->reconciled ? CBM_CHANGES_EMPTY
: CBM_CHANGES_UNKNOWN);
}
cbm_changes_status_t cbm_changes_parse(const unsigned char *name_status, size_t name_status_length,
const unsigned char *patch, size_t patch_length,
cbm_changes_t **out) {
if (out) {
*out = NULL;
}
if (!out || (!name_status && name_status_length) || (!patch && patch_length) ||
name_status_length > PTRDIFF_MAX || patch_length > PTRDIFF_MAX) {
return CBM_CHANGES_INVALID;
}
size_t count = 0;
size_t offset = 0;
while (offset < name_status_length) {
char status;
changes_slice_t path;
if (!changes_record(name_status, name_status_length, &offset, &status, &path)) {
return CBM_CHANGES_INVALID;
}
count++;
}
if (count > SIZE_MAX / sizeof(cbm_change_file_t) ||
count > SIZE_MAX / sizeof(changes_match_t)) {
return CBM_CHANGES_OOM;
}
CBMArena arena;
cbm_arena_init(&arena);
cbm_changes_t *changes = cbm_arena_calloc(&arena, sizeof(*changes));
if (!changes) {
cbm_arena_destroy(&arena);
return CBM_CHANGES_OOM;
}
changes->arena = arena;
changes->name_status = cbm_arena_alloc(&changes->arena, name_status_length + 1);
changes->patch = cbm_arena_alloc(&changes->arena, patch_length + 1);
if (!changes->name_status || !changes->patch) {
goto oom;
}
if (name_status_length) {
memcpy(changes->name_status, name_status, name_status_length);
}
if (patch_length) {
memcpy(changes->patch, patch, patch_length);
}
changes->name_status[name_status_length] = 0;
changes->patch[patch_length] = 0;
changes->name_status_length = name_status_length;
changes->patch_length = patch_length;
changes->count = count;
changes_match_t *matches = NULL;
if (count) {
changes->files = cbm_arena_calloc(&changes->arena, count * sizeof(*changes->files));
cbm_change_file_t *scratch = cbm_arena_alloc(&changes->arena, count * sizeof(*scratch));
matches = cbm_arena_calloc(&changes->arena, count * sizeof(*matches));
if (!changes->files || !scratch || !matches) {
goto oom;
}
offset = 0;
for (size_t i = 0; i < count; i++) {
changes_slice_t path;
char status;
if (!changes_record(changes->name_status, name_status_length, &offset, &status,
&path)) {
cbm_changes_free(changes);
return CBM_CHANGES_INVALID;
}
changes->files[i].path = path.bytes;
changes->files[i].path_length = path.length;
changes->files[i].status = status;
}
changes_sort(changes->files, scratch, count);
for (size_t i = 1; i < count; i++) {
if (changes_path_compare(changes->files[i - 1].path, changes->files[i - 1].path_length,
changes->files[i].path, changes->files[i].path_length) == 0) {
cbm_changes_free(changes);
return CBM_CHANGES_INVALID;
}
}
}
/* Raw NUL is not a representable text patch. Avoid passing it to a legacy
* C-string path consumer; retain the exact bytes and conservative inventory. */
bool binary_text = patch_length && memchr(changes->patch, 0, patch_length);
changes->diff = cbm_diff_parse(binary_text ? "" : (const char *)changes->patch,
binary_text ? 0 : patch_length);
if (!changes->diff) {
goto oom;
}
if (binary_text) {
changes->issues |= CBM_CHANGES_PATCH_INCOMPLETE;
}
changes_reconcile(changes, matches);
*out = changes;
return CBM_CHANGES_OK;
oom:
cbm_changes_free(changes);
return CBM_CHANGES_OOM;
}
void cbm_changes_free(cbm_changes_t *changes) {
if (!changes) {
return;
}
cbm_diff_free(changes->diff);
CBMArena arena = changes->arena;
cbm_arena_destroy(&arena);
}
const cbm_change_file_t *cbm_changes_files(const cbm_changes_t *changes, size_t *count) {
if (count) {
*count = changes ? changes->count : 0;
}
return changes ? changes->files : NULL;
}
cbm_changes_state_t cbm_changes_state(const cbm_changes_t *changes) {
return changes ? changes->state : CBM_CHANGES_UNKNOWN;
}
bool cbm_changes_patch_reconciled(const cbm_changes_t *changes) {
return changes && changes->reconciled;
}
bool cbm_changes_can_narrow(const cbm_changes_t *changes) {
return changes && changes->can_narrow;
}
unsigned cbm_changes_issues(const cbm_changes_t *changes) {
return changes ? changes->issues : 0;
}

Some files were not shown because too many files have changed in this diff Show More