diff --git a/.github/workflows/agent-shield-reusable.yml b/.github/workflows/agent-shield-reusable.yml index 86c1c99..2c1e157 100644 --- a/.github/workflows/agent-shield-reusable.yml +++ b/.github/workflows/agent-shield-reusable.yml @@ -112,11 +112,13 @@ jobs: continue fi - if ! echo "$frontmatter" | grep -q '^name:'; then + # Allow optional leading whitespace so indented YAML keys + # (e.g. under a `metadata:` parent) are recognised. + if ! echo "$frontmatter" | grep -qE '^[[:space:]]*name:'; then echo "::error file=$file::Missing 'name' field" status=1 fi - if ! echo "$frontmatter" | grep -q '^description:'; then + if ! echo "$frontmatter" | grep -qE '^[[:space:]]*description:'; then echo "::error file=$file::Missing 'description' field" status=1 fi diff --git a/.github/workflows/dependabot-rebase-reusable.yml b/.github/workflows/dependabot-rebase-reusable.yml index 83eff82..e4f4dfa 100644 --- a/.github/workflows/dependabot-rebase-reusable.yml +++ b/.github/workflows/dependabot-rebase-reusable.yml @@ -113,20 +113,29 @@ jobs: continue fi - # Check if all required checks pass (look at overall rollup) - CHECKS_PASS=$(gh pr view "$PR_NUMBER" --repo "$REPO" \ - --json statusCheckRollup \ - --jq '[.statusCheckRollup[]? | select(.name != null and .status == "COMPLETED") | .conclusion] | all(. == "SUCCESS" or . == "NEUTRAL" or . == "SKIPPED")') - - CHECKS_PENDING=$(gh pr view "$PR_NUMBER" --repo "$REPO" \ - --json statusCheckRollup \ - --jq '[.statusCheckRollup[]? | select(.name != null and .status != "COMPLETED")] | length') + # Check the status check rollup. We need: + # - at least one completed check (so an empty rollup is NOT + # vacuously "passing"), and + # - every completed check is SUCCESS / NEUTRAL / SKIPPED, and + # - zero checks still pending. + ROLLUP=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json statusCheckRollup) + CHECKS_COMPLETED=$(echo "$ROLLUP" | \ + jq '[.statusCheckRollup[]? | select(.name != null and .status == "COMPLETED")] | length') + CHECKS_PASS=$(echo "$ROLLUP" | \ + jq '[.statusCheckRollup[]? | select(.name != null and .status == "COMPLETED") | .conclusion] | all(. == "SUCCESS" or . == "NEUTRAL" or . == "SKIPPED")') + CHECKS_PENDING=$(echo "$ROLLUP" | \ + jq '[.statusCheckRollup[]? | select(.name != null and .status != "COMPLETED")] | length') if [[ "$CHECKS_PENDING" -gt 0 ]]; then echo " Skipping — $CHECKS_PENDING check(s) still pending" continue fi + if [[ "$CHECKS_COMPLETED" -eq 0 ]]; then + echo " Skipping — no completed status checks (cannot confirm safety)" + continue + fi + if [[ "$CHECKS_PASS" != "true" ]]; then echo " Skipping — some checks failed" continue diff --git a/.github/workflows/dependency-audit-reusable.yml b/.github/workflows/dependency-audit-reusable.yml index 51a0613..4e5fbe8 100644 --- a/.github/workflows/dependency-audit-reusable.yml +++ b/.github/workflows/dependency-audit-reusable.yml @@ -163,16 +163,57 @@ jobs: - name: Audit Cargo dependencies run: | - # cargo audit operates on Cargo.lock at workspace root - # For workspaces, a single audit at root covers all crates + # cargo audit operates on Cargo.lock at workspace root, so for a + # workspace a single audit at the root covers every member crate. + # Iterating every Cargo.toml would re-audit the same lockfile N + # times in workspaces. + # + # Strategy: + # 1. Find every Cargo.toml that declares [workspace] — those + # are workspace roots; emit them. + # 2. Find every Cargo.toml that does NOT declare [workspace]; + # those are either standalone crates or workspace members. + # Emit them only if no parent dir is already a workspace + # root we found in step 1. status=0 - while IFS= read -r dir; do + + mapfile -t WORKSPACES < <( + find . -name Cargo.toml -not -path '*/target/*' -print0 \ + | xargs -0 grep -l '^\[workspace\]' 2>/dev/null \ + | xargs -n1 dirname 2>/dev/null \ + | sort -u + ) + + mapfile -t NON_WORKSPACES < <( + find . -name Cargo.toml -not -path '*/target/*' -print0 \ + | xargs -0 grep -L '^\[workspace\]' 2>/dev/null \ + | xargs -n1 dirname 2>/dev/null \ + | sort -u + ) + + # Standalone crates: those whose dir is not under any workspace root + STANDALONE=() + for dir in "${NON_WORKSPACES[@]}"; do + covered=0 + for ws in "${WORKSPACES[@]}"; do + # Trim trailing slash from $ws for accurate prefix match + ws_trim="${ws%/}" + case "$dir/" in + "$ws_trim/"*) covered=1; break ;; + esac + done + if [ "$covered" -eq 0 ]; then + STANDALONE+=("$dir") + fi + done + + for dir in "${WORKSPACES[@]}" "${STANDALONE[@]}"; do echo "::group::cargo audit $dir" if ! (cd "$dir" && cargo generate-lockfile 2>/dev/null; cargo audit); then status=1 fi echo "::endgroup::" - done < <(find . -name 'Cargo.toml' -not -path '*/target/*' -exec dirname {} \; | sort -u) + done exit $status audit-pip: @@ -193,14 +234,18 @@ jobs: - name: Audit Python dependencies run: | status=0 - # Audit each Python project found in the repo + # Audit each Python project found in the repo. Some projects ship + # both pyproject.toml (tooling/build) and requirements.txt (pinned + # runtime deps) — audit BOTH when both are present rather than + # short-circuiting on pyproject.toml. while IFS= read -r dir; do echo "::group::pip-audit $dir" if [ -f "$dir/pyproject.toml" ]; then if ! pip-audit "$dir"; then status=1 fi - elif [ -f "$dir/requirements.txt" ]; then + fi + if [ -f "$dir/requirements.txt" ]; then if ! pip-audit -r "$dir/requirements.txt"; then status=1 fi diff --git a/standards/workflows/feature-ideation.yml b/standards/workflows/feature-ideation.yml index 4807fbc..2b757a4 100644 --- a/standards/workflows/feature-ideation.yml +++ b/standards/workflows/feature-ideation.yml @@ -37,7 +37,7 @@ name: Feature Research & Ideation (BMAD Analyst) on: schedule: - - cron: '0 7 * * 5' # Friday 07:00 UTC (3 AM EDT / 2 AM EST) + - cron: '0 7 * * 5' # Friday 07:00 UTC (3 AM EDT / 2 AM EST) workflow_dispatch: inputs: focus_area: