From bb0016789698b17930272a747a5376919304bb72 Mon Sep 17 00:00:00 2001
From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com>
Date: Thu, 9 Apr 2026 12:35:32 +0000
Subject: [PATCH 1/3] fix(compliance-audit): eliminate false positives in three
 checks
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Dependabot YAML quote style: grep patterns now accept both
  single-quoted ('github-actions') and double-quoted ("github-actions")
  YAML values. YAML allows either; the old patterns required double
  quotes, flagging valid single-quoted configs as non-compliant.

- Workflow permissions: reusable workflows (workflow_call-only triggers)
  are now skipped. Their permissions are set by the caller, so requiring
  a top-level permissions: block in *-reusable.yml files was a false
  positive that blocked every org-level reusable workflow.

- AGENTS.md org reference: accept GitHub blob URLs
  (petry-projects/.github/blob/<branch>/AGENTS.md) in addition to the
  canonical path format (.github/AGENTS.md in link text). Both forms
  unambiguously point to the org-level standards file.

Closes #119 (partial — API-based fixes applied separately)

Co-authored-by: don-petry <don-petry@users.noreply.github.com>
---
 scripts/compliance-audit.sh | 34 ++++++++++++++++++++++++++--------
 1 file changed, 26 insertions(+), 8 deletions(-)

diff --git a/scripts/compliance-audit.sh b/scripts/compliance-audit.sh
index 3c1d2a8fa..2473eb732 100755
--- a/scripts/compliance-audit.sh
+++ b/scripts/compliance-audit.sh
@@ -238,20 +238,22 @@ check_dependabot_config() {
   decoded=$(echo "$content" | base64 -d 2>/dev/null || echo "")
 
   # Check github-actions ecosystem entry exists
-  if ! echo "$decoded" | grep -q 'package-ecosystem:.*"github-actions"'; then
+  # Accept both double-quoted ("github-actions") and single-quoted ('github-actions') YAML values.
+  if ! echo "$decoded" | grep -qE "package-ecosystem:[[:space:]]*(\"github-actions\"|'github-actions')"; then
     add_finding "$repo" "dependabot" "missing-github-actions-ecosystem" "error" \
       "Dependabot config missing \`github-actions\` ecosystem entry" \
       "standards/dependabot-policy.md#github-actions-all-repos"
   fi
 
   # Check that app ecosystem entries use open-pull-requests-limit: 0
-  # Extract ecosystem blocks and check limits
+  # Extract ecosystem blocks and check limits.
+  # Accept both double-quoted and single-quoted YAML string values.
   for eco in npm pip gomod cargo terraform; do
-    if echo "$decoded" | grep -q "package-ecosystem:.*\"$eco\""; then
+    if echo "$decoded" | grep -qE "package-ecosystem:[[:space:]]*(\"$eco\"|'$eco')"; then
       # Check if this ecosystem has limit: 0
       # Simple heuristic: find the ecosystem line and look for limit in the next ~10 lines
       local block
-      block=$(echo "$decoded" | awk "/package-ecosystem:.*\"$eco\"/{found=1} found{print; if(/package-ecosystem:/ && NR>1 && !/\"$eco\"/) exit}" | head -15)
+      block=$(echo "$decoded" | awk "/package-ecosystem:.*$eco/{found=1} found{print; if(/package-ecosystem:/ && NR>1 && !/$eco/) exit}" | head -15)
       local limit
       limit=$(echo "$block" | grep 'open-pull-requests-limit:' | head -1 | grep -oE '[0-9]+' || echo "")
       if [ -n "$limit" ] && [ "$limit" != "0" ]; then
@@ -262,13 +264,14 @@ check_dependabot_config() {
     fi
   done
 
-  # Check for required labels in dependabot config
-  if ! echo "$decoded" | grep -q '"security"'; then
+  # Check for required labels in dependabot config.
+  # Accept both double-quoted and single-quoted YAML string values.
+  if ! echo "$decoded" | grep -qE '("security"|'"'"'security'"'"')'; then
     add_finding "$repo" "dependabot" "missing-security-label" "warning" \
       "Dependabot config missing \`security\` label on updates" \
       "standards/dependabot-policy.md#policy"
   fi
-  if ! echo "$decoded" | grep -q '"dependencies"'; then
+  if ! echo "$decoded" | grep -qE '("dependencies"|'"'"'dependencies'"'"')'; then
     add_finding "$repo" "dependabot" "missing-dependencies-label" "warning" \
       "Dependabot config missing \`dependencies\` label on updates" \
       "standards/dependabot-policy.md#policy"
@@ -479,6 +482,17 @@ check_workflow_permissions() {
     decoded=$(echo "$content" | base64 -d 2>/dev/null || echo "")
     [ -z "$decoded" ] && continue
 
+    # Skip reusable workflows (workflow_call-only triggers).
+    # Their permissions are controlled entirely by the caller workflow, so
+    # requiring a top-level permissions: block here would be redundant and
+    # would generate false positives for every *-reusable.yml in the org.
+    local on_triggers
+    on_triggers=$(echo "$decoded" | awk '/^on:/{found=1; next} found && /^[^ ]/{exit} found{print}')
+    if echo "$on_triggers" | grep -q 'workflow_call:' && \
+       ! echo "$on_triggers" | grep -qE '^\s+(push|pull_request|schedule|workflow_dispatch|issues|issue_comment|pull_request_target):'; then
+      continue
+    fi
+
     # Check if the workflow has a top-level permissions key
     # Single-job workflows may define permissions at job level instead
     if ! echo "$decoded" | grep -qE '^permissions:'; then
@@ -776,7 +790,11 @@ check_agents_md() {
     local decoded
     decoded=$(echo "$content" | base64 -d 2>/dev/null || echo "")
 
-    if ! echo "$decoded" | grep -qE '\.github/AGENTS\.md'; then
+    # Accept two forms of reference:
+    #   1. Canonical path format: petry-projects/.github/AGENTS.md (appears in link text)
+    #   2. GitHub blob URL format: /petry-projects/.github/blob/<branch>/AGENTS.md (in href)
+    # Both unambiguously point to the org-level standards file.
+    if ! echo "$decoded" | grep -qE '(\.github/AGENTS\.md|petry-projects/\.github/blob/[^/]+/AGENTS\.md)'; then
       add_finding "$repo" "standards" "agents-md-missing-org-ref" "error" \
         "\`AGENTS.md\` does not reference the org-level \`.github/AGENTS.md\` standards" \
         "AGENTS.md"

From 4b906a531f604540b74971c0e2fa0d7d565abbc5 Mon Sep 17 00:00:00 2001
From: DJ <dj@Rachels-Air.localdomain>
Date: Thu, 9 Apr 2026 18:02:36 -0700
Subject: [PATCH 2/3] fix(compliance-audit): address CodeRabbit + Copilot
 review feedback

- Replace brittle awk/grep reusable-workflow detection with filename
  convention check (*-reusable.yml), avoiding false skips from unlisted
  triggers or inline on: syntax
- Fix blob URL regex to allow slashed branch names (blob/.+/AGENTS.md
  instead of blob/[^/]+/AGENTS.md)
- Align comment with what the regex actually matches

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 scripts/compliance-audit.sh | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)

diff --git a/scripts/compliance-audit.sh b/scripts/compliance-audit.sh
index 2473eb732..696fb61d3 100755
--- a/scripts/compliance-audit.sh
+++ b/scripts/compliance-audit.sh
@@ -486,10 +486,8 @@ check_workflow_permissions() {
     # Their permissions are controlled entirely by the caller workflow, so
     # requiring a top-level permissions: block here would be redundant and
     # would generate false positives for every *-reusable.yml in the org.
-    local on_triggers
-    on_triggers=$(echo "$decoded" | awk '/^on:/{found=1; next} found && /^[^ ]/{exit} found{print}')
-    if echo "$on_triggers" | grep -q 'workflow_call:' && \
-       ! echo "$on_triggers" | grep -qE '^\s+(push|pull_request|schedule|workflow_dispatch|issues|issue_comment|pull_request_target):'; then
+    # All reusable workflows follow the -reusable.yml naming convention.
+    if [[ "$wf" == *-reusable.yml || "$wf" == *-reusable.yaml ]]; then
       continue
     fi
 
@@ -791,10 +789,10 @@ check_agents_md() {
     decoded=$(echo "$content" | base64 -d 2>/dev/null || echo "")
 
     # Accept two forms of reference:
-    #   1. Canonical path format: petry-projects/.github/AGENTS.md (appears in link text)
-    #   2. GitHub blob URL format: /petry-projects/.github/blob/<branch>/AGENTS.md (in href)
-    # Both unambiguously point to the org-level standards file.
-    if ! echo "$decoded" | grep -qE '(\.github/AGENTS\.md|petry-projects/\.github/blob/[^/]+/AGENTS\.md)'; then
+    #   1. Any path containing .github/AGENTS.md (relative link text or path reference)
+    #   2. GitHub blob URL format: /petry-projects/.github/blob/<ref>/AGENTS.md (in href)
+    # Both are treated as references to the org-level standards file.
+    if ! echo "$decoded" | grep -qE '(\.github/AGENTS\.md|petry-projects/\.github/blob/.+/AGENTS\.md)'; then
       add_finding "$repo" "standards" "agents-md-missing-org-ref" "error" \
         "\`AGENTS.md\` does not reference the org-level \`.github/AGENTS.md\` standards" \
         "AGENTS.md"

From 9d5c4e7689e9c91b3cddd0ba1465dcbd746ca0ee Mon Sep 17 00:00:00 2001
From: DJ <dj@Rachels-Air.localdomain>
Date: Thu, 9 Apr 2026 18:08:16 -0700
Subject: [PATCH 3/3] fix(compliance-audit): quote-aware awk pattern for
 ecosystem block extraction

The awk pattern used unquoted $eco which caused "npm" to substring-match
"pnpm", potentially spanning the extracted block into the wrong ecosystem
entry. Align awk with the existing grep by requiring quoted values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 scripts/compliance-audit.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/compliance-audit.sh b/scripts/compliance-audit.sh
index 696fb61d3..8550bf3fb 100755
--- a/scripts/compliance-audit.sh
+++ b/scripts/compliance-audit.sh
@@ -253,7 +253,7 @@ check_dependabot_config() {
       # Check if this ecosystem has limit: 0
       # Simple heuristic: find the ecosystem line and look for limit in the next ~10 lines
       local block
-      block=$(echo "$decoded" | awk "/package-ecosystem:.*$eco/{found=1} found{print; if(/package-ecosystem:/ && NR>1 && !/$eco/) exit}" | head -15)
+      block=$(echo "$decoded" | awk "/package-ecosystem:.*(\"$eco\"|'$eco')/{found=1} found{print; if(/package-ecosystem:/ && NR>1 && !/(\"$eco\"|'$eco')/) exit}" | head -15)
       local limit
       limit=$(echo "$block" | grep 'open-pull-requests-limit:' | head -1 | grep -oE '[0-9]+' || echo "")
       if [ -n "$limit" ] && [ "$limit" != "0" ]; then
