Skip to content

Commit 4ce04dd

Browse files
authored
Merge pull request #72 from dgenio/feat/v0.2-ci-integration-policy
feat: CI integration and policy controls (v0.2)
2 parents 7bd73ff + 764c12e commit 4ce04dd

28 files changed

Lines changed: 2803 additions & 19 deletions
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"problemMatcher": [
3+
{
4+
"owner": "vibeguard",
5+
"severity": "error",
6+
"pattern": [
7+
{
8+
"regexp": "^│\\s*(?:☠ CRITICAL|✗ HIGH)\\s*│\\s*(\\S+)\\s*│\\s*([^:│]+?)(?::(\\d+))?\\s*│\\s*(.+?)\\s*│$",
9+
"file": 2,
10+
"line": 3,
11+
"message": 4
12+
}
13+
]
14+
},
15+
{
16+
"owner": "vibeguard-warning",
17+
"severity": "warning",
18+
"pattern": [
19+
{
20+
"regexp": "^│\\s*(?:⚠ MEDIUM)\\s*│\\s*(\\S+)\\s*│\\s*([^:│]+?)(?::(\\d+))?\\s*│\\s*(.+?)\\s*│$",
21+
"file": 2,
22+
"line": 3,
23+
"message": 4
24+
}
25+
]
26+
},
27+
{
28+
"owner": "vibeguard-notice",
29+
"severity": "notice",
30+
"pattern": [
31+
{
32+
"regexp": "^│\\s*(?:↓ LOW|ℹ INFO)\\s*│\\s*(\\S+)\\s*│\\s*([^:│]+?)(?::(\\d+))?\\s*│\\s*(.+?)\\s*│$",
33+
"file": 2,
34+
"line": 3,
35+
"message": 4
36+
}
37+
]
38+
}
39+
]
40+
}

.github/workflows/test-action.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Test Action
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'action.yml'
7+
- 'vibeguard/**'
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
test-action:
14+
name: Test local action
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0
20+
21+
- uses: ./
22+
id: vibeguard
23+
with:
24+
path: 'examples/vulnerable-node-package'
25+
fail-on: 'critical'
26+
output-format: 'json'
27+
output-file: 'test-output.json'
28+
continue-on-error: true
29+
30+
- name: Verify outputs
31+
run: |
32+
echo "Gate passed: ${{ steps.vibeguard.outputs.gate-passed }}"
33+
echo "Findings: ${{ steps.vibeguard.outputs.findings-count }}"
34+
test -f test-output.json

action.yml

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
name: 'VibeGuard'
2+
description: 'Deterministic pre-merge safety gate for AI-generated code'
3+
branding:
4+
icon: shield
5+
color: blue
6+
7+
inputs:
8+
path:
9+
description: 'Directory to scan'
10+
required: false
11+
default: '.'
12+
config:
13+
description: 'Path to vibeguard.yaml'
14+
required: false
15+
default: ''
16+
diff:
17+
description: 'Scan only changed files'
18+
required: false
19+
default: 'false'
20+
fail-on:
21+
description: 'Severity threshold for gate failure (info|low|medium|high|critical)'
22+
required: false
23+
default: 'high'
24+
output-format:
25+
description: 'Output format: console, json, markdown, sarif, pr-comment'
26+
required: false
27+
default: 'console'
28+
output-file:
29+
description: 'Write output to this file path'
30+
required: false
31+
default: ''
32+
baseline:
33+
description: 'Path to baseline file'
34+
required: false
35+
default: ''
36+
python-version:
37+
description: 'Python version to use'
38+
required: false
39+
default: '3.11'
40+
41+
outputs:
42+
findings-count:
43+
description: >-
44+
Total number of findings. Accurate when output-format is json (parsed from
45+
the JSON payload); otherwise the literal string "unknown".
46+
value: ${{ steps.scan.outputs.findings-count }}
47+
blocking-count:
48+
description: >-
49+
Number of findings at or above the fail-on threshold. Accurate when
50+
output-format is json; otherwise "unknown". The boolean gate-passed
51+
output is always accurate.
52+
value: ${{ steps.scan.outputs.blocking-count }}
53+
report-path:
54+
description: 'Path to the output file (if output-file was set)'
55+
value: ${{ steps.scan.outputs.report-path }}
56+
gate-passed:
57+
description: 'Whether the gate passed (true or false)'
58+
value: ${{ steps.scan.outputs.gate-passed }}
59+
60+
runs:
61+
using: composite
62+
steps:
63+
- name: Set up Python
64+
uses: actions/setup-python@v5
65+
with:
66+
python-version: ${{ inputs.python-version }}
67+
cache: pip
68+
69+
- name: Install VibeGuard
70+
shell: bash
71+
# Install from the checked-out action source so the CLI version matches
72+
# the action ref (e.g. `uses: dgenio/vibeguard@v0.2`). Installing from
73+
# PyPI here would let the CLI float independently of the action tag,
74+
# which defeats the point of pinning.
75+
run: pip install "${{ github.action_path }}"
76+
77+
- name: Run VibeGuard gate
78+
id: scan
79+
shell: bash
80+
# Pipe each input through an intermediate env var rather than inlining
81+
# `${{ inputs.* }}` into the shell command. See GitHub's security-
82+
# hardening guidance for composite actions:
83+
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
84+
env:
85+
INPUT_PATH: ${{ inputs.path }}
86+
INPUT_FAIL_ON: ${{ inputs.fail-on }}
87+
INPUT_DIFF: ${{ inputs.diff }}
88+
INPUT_CONFIG: ${{ inputs.config }}
89+
INPUT_BASELINE: ${{ inputs.baseline }}
90+
INPUT_OUTPUT_FORMAT: ${{ inputs.output-format }}
91+
INPUT_OUTPUT_FILE: ${{ inputs.output-file }}
92+
run: |
93+
set +e
94+
95+
# Build command using only env vars (quoted) — never inline templating.
96+
CMD=(vibeguard gate --path "$INPUT_PATH" --fail-on "$INPUT_FAIL_ON")
97+
98+
if [ "$INPUT_DIFF" = "true" ]; then
99+
CMD+=(--diff)
100+
fi
101+
102+
if [ -n "$INPUT_CONFIG" ]; then
103+
CMD+=(--config "$INPUT_CONFIG")
104+
fi
105+
106+
if [ -n "$INPUT_BASELINE" ]; then
107+
CMD+=(--baseline "$INPUT_BASELINE")
108+
fi
109+
110+
# Output format
111+
FORMAT="$INPUT_OUTPUT_FORMAT"
112+
case "$FORMAT" in
113+
json) CMD+=(--json) ;;
114+
markdown) CMD+=(--markdown) ;;
115+
sarif) CMD+=(--sarif) ;;
116+
pr-comment) CMD+=(--pr-comment) ;;
117+
esac
118+
119+
# Run and capture output. Redirect only stdout to OUTPUT_FILE so that
120+
# warnings on stderr (Git missing, config notices, rich formatting)
121+
# don't corrupt JSON/SARIF/Markdown reports; stderr flows to the job
122+
# log instead.
123+
OUTPUT_FILE="$INPUT_OUTPUT_FILE"
124+
if [ -n "$OUTPUT_FILE" ]; then
125+
"${CMD[@]}" > "$OUTPUT_FILE"
126+
EXIT_CODE=$?
127+
echo "report-path=$OUTPUT_FILE" >> "$GITHUB_OUTPUT"
128+
else
129+
"${CMD[@]}"
130+
EXIT_CODE=$?
131+
fi
132+
133+
# Parse counts from JSON output. We only know real counts when the user
134+
# asked for json + output-file; for any other format we emit "unknown"
135+
# rather than lying with a hardcoded zero. gate-passed below remains
136+
# authoritative regardless. jq is preinstalled on GitHub-hosted runners.
137+
FINDINGS_COUNT="unknown"
138+
BLOCKING_COUNT="unknown"
139+
if [ "$FORMAT" = "json" ] && [ -n "$OUTPUT_FILE" ] && [ -f "$OUTPUT_FILE" ] && command -v jq >/dev/null 2>&1; then
140+
FC=$(jq '.findings | length' "$OUTPUT_FILE" 2>/dev/null)
141+
BC=$(jq --arg t "$INPUT_FAIL_ON" '
142+
({info:0,low:1,medium:2,high:3,critical:4}) as $o
143+
| ($o[$t] // 3) as $thresh
144+
| [.findings[] | select(($o[(.severity // "info") | ascii_downcase] // 0) >= $thresh)]
145+
| length
146+
' "$OUTPUT_FILE" 2>/dev/null)
147+
if [ -n "$FC" ]; then FINDINGS_COUNT="$FC"; fi
148+
if [ -n "$BC" ]; then BLOCKING_COUNT="$BC"; fi
149+
fi
150+
echo "findings-count=$FINDINGS_COUNT" >> "$GITHUB_OUTPUT"
151+
echo "blocking-count=$BLOCKING_COUNT" >> "$GITHUB_OUTPUT"
152+
153+
if [ $EXIT_CODE -eq 0 ]; then
154+
echo "gate-passed=true" >> "$GITHUB_OUTPUT"
155+
else
156+
echo "gate-passed=false" >> "$GITHUB_OUTPUT"
157+
fi
158+
159+
exit $EXIT_CODE

docs/github-action-reference.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# VibeGuard GitHub Action
2+
3+
The `dgenio/vibeguard` action runs VibeGuard as a composite GitHub Action step.
4+
5+
## Usage
6+
7+
```yaml
8+
- uses: dgenio/vibeguard@v0.2
9+
with:
10+
path: '.'
11+
diff: 'true'
12+
fail-on: 'high'
13+
output-format: 'sarif'
14+
output-file: 'vibeguard.sarif'
15+
```
16+
17+
## Inputs
18+
19+
| Input | Required | Default | Description |
20+
|---|---|---|---|
21+
| `path` | No | `.` | Directory to scan |
22+
| `config` | No | `` | Path to vibeguard.yaml |
23+
| `diff` | No | `false` | Scan only changed files |
24+
| `fail-on` | No | `high` | Severity threshold for gate failure |
25+
| `output-format` | No | `console` | `console`, `json`, `markdown`, `sarif`, `pr-comment` |
26+
| `output-file` | No | `` | Write output to this file path |
27+
| `baseline` | No | `` | Path to baseline file |
28+
| `python-version` | No | `3.11` | Python version to use |
29+
30+
## Outputs
31+
32+
| Output | Description |
33+
|---|---|
34+
| `findings-count` | Total number of findings |
35+
| `blocking-count` | Number of findings at or above fail-on threshold |
36+
| `report-path` | Path to the output file (if output-file was set) |
37+
| `gate-passed` | `true` or `false` |
38+
39+
## Complete Example
40+
41+
```yaml
42+
name: VibeGuard
43+
on: [pull_request]
44+
45+
permissions:
46+
contents: read
47+
security-events: write
48+
49+
jobs:
50+
vibeguard:
51+
runs-on: ubuntu-latest
52+
steps:
53+
- uses: actions/checkout@v4
54+
with:
55+
fetch-depth: 0
56+
57+
- uses: dgenio/vibeguard@v0.2
58+
id: vibeguard
59+
with:
60+
diff: 'true'
61+
fail-on: 'high'
62+
output-format: 'sarif'
63+
output-file: 'vibeguard.sarif'
64+
65+
- name: Upload SARIF
66+
if: always()
67+
uses: github/codeql-action/upload-sarif@v3
68+
with:
69+
sarif_file: vibeguard.sarif
70+
```

0 commit comments

Comments
 (0)