Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .claude/config/baseline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"created_at": "2026-07-15T00:00:00Z",
"at_commit": "TBD",
"schema_version": 1,
"exemptions": {
"PY-LIC-01": {
"reason": "Repo uses REUSE.toml aggregate — pre-existing files exempt",
"detector": "REUSE.toml exists with path='**' aggregate annotation",
"scope": "repo",
"files_exempted_glob": "src/**/*.py"
},
"PY-LIC-02": {
"reason": "Same as PY-LIC-01",
"scope": "repo"
},
"JV-LIC-01": {
"reason": "cloud-sdk-java has no REUSE.toml AND no per-file SPDX baseline — rule kept SHADOW pending team decision on Wave-4 baseline PR",
"scope": "repo"
},
"JV-LIC-02": {
"reason": "Same as JV-LIC-01",
"scope": "repo"
}
}
}
24 changes: 24 additions & 0 deletions .claude/config/module-aliases.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Module name aliases across the Python and Java cloud SDKs.
# Consumed by .claude/scripts/check-bdd.sh to resolve the sibling repo's
# feature-file path when the two languages happen to name the same module
# differently.
#
# Only add an entry when the module exists in BOTH SDKs under DIFFERENT names.
# When the names match on both sides, the resolver falls back to the same name
# — no entry needed. Examples of matching names today: `destination`,
# `objectstore`. Adding those here would be a no-op.
#
# Audit summary (2026-07):
# - Python-only modules (no Java counterpart yet): adms, agent_decorators,
# agent_memory, agentgateway, aicore, extensibility, ias, print
# - Java-only packages: core (SDK baseline, not a client module — skipped
# by check-bdd via the `^core$` filter)
# - Same-name modules on both sides: destination, objectstore
# - Different-name pair: dms ↔ documentmanagement (see below)
#
# When a new cross-language module lands that uses a different name on either
# side, append it here and update the audit summary above.

aliases:
- python: dms
java: documentmanagement
131 changes: 131 additions & 0 deletions .claude/config/rules.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
version: 2

# =====================================================
# rules.yaml — SDK Module Review rule catalog
# All rules default to their tier here; may be overridden per-repo.
# BLOCK_LOCKED rules cannot be softened or suppressed.
# =====================================================

baseline:
file: .claude/baseline.json
regenerate_with: orchestrate.sh --baseline

tiers:
SHADOW: { posts: false, blocks: false }
FLAG: { posts: true, blocks: false }
BLOCK: { posts: true, blocks: true }
BLOCK_LOCKED: { posts: true, blocks: true, suppressible: false, downgradable: false }

rules:
# -------- BREAKING FAMILY (all locked) --------
BREAKING-01: { tier: BLOCK_LOCKED }
BREAKING-02: { tier: BLOCK_LOCKED }
BREAKING-03: { tier: FLAG }
BREAKING-04: { tier: FLAG }

# -------- SECRETS (all locked) --------
SEC-01: { tier: BLOCK_LOCKED }
SEC-02: { tier: BLOCK_LOCKED }
SEC-03: { tier: BLOCK_LOCKED }
SEC-04: { tier: BLOCK_LOCKED }
SEC-05: { tier: BLOCK_LOCKED }
SEC-06: { tier: BLOCK_LOCKED }
SEC-07: { tier: BLOCK_LOCKED }
SEC-08: { tier: BLOCK_LOCKED }
SEC-10: { tier: BLOCK_LOCKED }

# -------- LICENSE --------
LIC-01: { tier: BLOCK, predicates: { not: reuse_toml_aggregate_present, diff_scope: file_in_added_set } }
LIC-02: { tier: BLOCK, predicates: { not: reuse_toml_aggregate_present, diff_scope: file_in_added_set } }

# -------- DISCLOSURE --------
DIS-01: { tier: BLOCK, profile: { public: BLOCK, internal: FLAG } }
DIS-02: { tier: BLOCK, profile: { public: BLOCK, internal: PASS } }
DIS-06: { tier: BLOCK_LOCKED }
DIS-07: { tier: BLOCK }
DIS-08: { tier: SHADOW }

# -------- HARDCODE --------
HC-01: { tier: BLOCK }
HC-02: { tier: BLOCK }
HC-03: { tier: BLOCK_LOCKED }
HC-04: { tier: FLAG }
HC-06: { tier: FLAG }

# -------- TELEMETRY --------
PY-TEL-02: { tier: BLOCK }
JV-TEL-02: { tier: BLOCK }
PY-TEL-06: { tier: BLOCK, predicates: { diff_scope: new_decorator_added } }
PY-TEL-08: { tier: SHADOW }

# -------- DOCS --------
DC-01: { tier: BLOCK }
DC-02: { tier: BLOCK }
DC-11: { tier: BLOCK }
DC-12: { tier: BLOCK }
DC-13: { tier: BLOCK }
DC-14: { tier: BLOCK }

# -------- BDD --------
BDD-01: { tier: BLOCK }
BDD-02: { tier: BLOCK }

# -------- PATTERNS --------
PY-PT-01: { tier: BLOCK, predicates: { module_shape: client } }
JV-PT-01: { tier: BLOCK, predicates: { module_shape: client } }
PY-PT-03: { tier: FLAG }
PY-PT-04: { tier: FLAG }
PY-PT-08: { tier: FLAG }
JV-PT-05: { tier: FLAG }

# -------- VERSIONING --------
VER-01: { tier: BLOCK, predicates: { commit_types_or_breaking: [feat, feat!, breaking] } }
VER-07: { tier: FLAG }
PY-VER-08: { tier: SHADOW }
PY-VER-09: { tier: SHADOW }

# -------- ERRORS/LOGGING --------
EL-01: { tier: FLAG }
EL-02: { tier: FLAG }
EL-04: { tier: BLOCK }
PY-EL-11: { tier: SHADOW }

# -------- TESTING --------
TD-01: { tier: FLAG }
TD-10: { tier: BLOCK }
TD-checkbox: { tier: FLAG }

# -------- HTTP --------
HTTP-01: { tier: FLAG, predicates: { diff_scope: line_in_added_set } }

# -------- CONCURRENCY --------
CC-01: { tier: FLAG }
JV-CC-05: { tier: SHADOW }

# -------- DEPS --------
DEP-03: { tier: FLAG }
DEP-04: { tier: BLOCK_LOCKED }

# -------- COMMITS --------
COM-01: { tier: BLOCK }

# -------- DELETION --------
DEL-01: { tier: BLOCK }

# -------- CONSTANTS --------
PY-CON-01: { tier: FLAG }

# -------- BINDING --------
BND-02: { tier: BLOCK_LOCKED }
BND-04: { tier: BLOCK }
BND-05: { tier: BLOCK_LOCKED }

# -------- QUALITY GATE --------
QG-01: { tier: FLAG }
QG-02: { tier: FLAG }

# -------- PR SIZE (advisory, SHADOW during rollout) --------
PR-SIZE-01: { tier: SHADOW }
PR-SIZE-02: { tier: SHADOW }
PR-SIZE-03: { tier: SHADOW }
PR-SIZE-05: { tier: SHADOW }
119 changes: 119 additions & 0 deletions .claude/scripts/lib/apply-suppression.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
# lib/apply-suppression.sh — filter findings against suppression comments in source.
# Called after a check produces its JSON report; reads suppression tuples and drops findings.
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source suppression.sh so we don't fork bash+jq per finding.
# shellcheck source=./suppression.sh
source "$SCRIPT_DIR/suppression.sh"

# collect_suppressions_for_files <file1> <file2> ...
# Emits "<file>:<line>:<rule>" tuples for all suppression comments found.
collect_suppressions_for_files() {
for f in "$@"; do
[ -f "$f" ] || continue
parse_line_suppressions "$f" 2>/dev/null || true
parse_file_suppressions "$f" 2>/dev/null || true
done
}

# apply_to_report <report_json> <suppressions_file> → prints filtered report
apply_to_report() {
local report="$1" supp_file="$2"
local suppressed_count=0
local input; input=$(cat "$report")
local n; n=$(echo "$input" | jq '.findings | length')
if [ "$n" -eq 0 ]; then
echo "$input"; return
fi

# Extract all (rule, file, line) tuples in a single jq call — one fork total
# instead of one per finding.
local tuples; tuples=$(echo "$input" | jq -r '.findings[] | "\(.rule)|\(.file)|\(.line)"')

local keep_flags=()
local i=0
while IFS='|' read -r rule file line; do
if [ "$(is_suppressed "$rule" "$file" "$line" "$supp_file")" = "true" ]; then
keep_flags+=("0")
suppressed_count=$((suppressed_count + 1))
else
keep_flags+=("1")
fi
i=$((i + 1))
done <<< "$tuples"

# Build a JSON array of booleans and use jq to filter
local keep_json
if [ "${#keep_flags[@]}" -eq 0 ]; then
keep_json="[]"
else
keep_json=$(printf '%s\n' "${keep_flags[@]}" | jq -s 'map(. == "1")')
fi

echo "$input" | jq \
--argjson keep "$keep_json" \
--argjson suppressed "$suppressed_count" \
'.findings = [.findings[] as $f | ($f | . as $x | (input_line_number - 1) as $i | select($keep[$i]))] // .findings
| .findings = [range(.findings | length) as $i | .findings[$i] | select($keep[$i])]
| .summary.suppressed_count = $suppressed' 2>/dev/null || {
# Fallback: simple length-preserving filter
echo "$input" | jq \
--argjson keep "$keep_json" \
--argjson suppressed "$suppressed_count" \
'(.findings | length) as $n
| .findings = [range($n) | . as $i | (($keep[$i] // true) | if . then $i else null end) | select(. != null)] as $indices
| .findings = [$indices[] as $i | .findings[$i]]
| .summary.suppressed_count = $suppressed'
}
}

# Simpler variant: build kept-list explicitly (safe fallback)
apply_to_report_v2() {
local report="$1" supp_file="$2"
local suppressed_count=0
local input; input=$(cat "$report")
local n; n=$(echo "$input" | jq '.findings | length')
if [ "$n" -eq 0 ]; then
echo "$input"; return
fi

local kept=()
local i=0
while [ "$i" -lt "$n" ]; do
local finding rule file line
finding=$(echo "$input" | jq -c ".findings[$i]")
rule=$(echo "$finding" | jq -r '.rule')
file=$(echo "$finding" | jq -r '.file')
line=$(echo "$finding" | jq -r '.line')

if [ "$(is_suppressed "$rule" "$file" "$line" "$supp_file")" = "true" ]; then
suppressed_count=$((suppressed_count + 1))
else
kept+=("$finding")
fi
i=$((i + 1))
done

local kept_json
if [ "${#kept[@]}" -eq 0 ]; then
kept_json="[]"
else
kept_json=$(printf '%s\n' "${kept[@]}" | jq -s -c '.')
fi

echo "$input" | jq \
--argjson kept "$kept_json" \
--argjson suppressed "$suppressed_count" \
'.findings = $kept | .summary.suppressed_count = $suppressed'
}

if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
cmd="${1:-}"; shift || true
case "$cmd" in
collect) collect_suppressions_for_files "$@" ;;
apply) apply_to_report_v2 "$@" ;; # simpler + safer
*) echo "Usage: apply-suppression.sh {collect|apply} args" >&2; exit 2 ;;
esac
fi
Loading
Loading