Skip to content
Open
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
118 changes: 114 additions & 4 deletions .github/workflows/droid-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,133 @@ on:
types: [opened, ready_for_review, reopened]

jobs:
droid-review:
prepare:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
outputs:
comment_id: ${{ steps.prepare.outputs.comment_id }}
run_code_review: ${{ steps.prepare.outputs.run_code_review }}
run_security_review: ${{ steps.prepare.outputs.run_security_review }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1

- name: Run Droid Auto Review
uses: Factory-AI/droid-action@v1
- name: Prepare
id: prepare
uses: Factory-AI/droid-action/prepare@v1
with:
factory_api_key: ${{ secrets.FACTORY_API_KEY }}
automatic_review: true
automatic_security_review: true

code-review:
needs: prepare
if: needs.prepare.outputs.run_code_review == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1

- name: Run Code Review
uses: Factory-AI/droid-action/review@v1
with:
factory_api_key: ${{ secrets.FACTORY_API_KEY }}
tracking_comment_id: ${{ needs.prepare.outputs.comment_id }}
output_file: ${{ runner.temp }}/code-review-results.json

- name: Upload Results
uses: actions/upload-artifact@v4
with:
name: code-review-results
path: ${{ runner.temp }}/code-review-results.json
if-no-files-found: ignore

security-review:
needs: prepare
if: needs.prepare.outputs.run_security_review == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1

- name: Run Security Review
uses: Factory-AI/droid-action/security@v1
with:
factory_api_key: ${{ secrets.FACTORY_API_KEY }}
tracking_comment_id: ${{ needs.prepare.outputs.comment_id }}
security_severity_threshold: medium
output_file: ${{ runner.temp }}/security-results.json

- name: Upload Results
uses: actions/upload-artifact@v4
with:
name: security-results
path: ${{ runner.temp }}/security-results.json
if-no-files-found: ignore

combine:
needs: [prepare, code-review, security-review]
# Run combine when EITHER code review OR security review was executed
if: |
always() &&
(needs.prepare.outputs.run_code_review == 'true' ||
needs.prepare.outputs.run_security_review == 'true')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Run combine when either review ran (avoid skipping summary update)

combine currently runs only if both run_code_review and run_security_review are true, so if security is skipped by “run once” behavior (or code review is disabled), the tracking comment may never get a final combined/status summary update.

actions: read
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1

- name: Download Code Review Results
uses: actions/download-artifact@v4
with:
name: code-review-results
path: ${{ runner.temp }}
continue-on-error: true

- name: Download Security Results
uses: actions/download-artifact@v4
with:
name: security-results
path: ${{ runner.temp }}
continue-on-error: true

- name: Combine Results
uses: Factory-AI/droid-action/combine@v1
with:
factory_api_key: ${{ secrets.FACTORY_API_KEY }}
tracking_comment_id: ${{ needs.prepare.outputs.comment_id }}
code_review_results: ${{ runner.temp }}/code-review-results.json
security_results: ${{ runner.temp }}/security-results.json
code_review_status: ${{ needs.code-review.result }}
security_review_status: ${{ needs.security-review.result }}
61 changes: 61 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,67 @@ runs:
env:
EXPERIMENTAL_ALLOWED_DOMAINS: ${{ inputs.experimental_allowed_domains }}

- name: Install Security Skills
if: steps.prepare.outputs.contains_trigger == 'true' && steps.prepare.outputs.install_security_skills == 'true'
shell: bash
run: |
echo "Installing security skills from Factory-AI/skills..."
SKILLS_DIR="$HOME/.factory/skills"
mkdir -p "$SKILLS_DIR"

# Clone public skills repo (sparse checkout for efficiency)
TEMP_DIR=$(mktemp -d)
git clone --filter=blob:none --sparse \
"https://github.com/Factory-AI/skills.git" \
"$TEMP_DIR" 2>/dev/null || {
echo "Warning: Could not clone skills repo. Security skills will not be available."
exit 0
}

cd "$TEMP_DIR"
git sparse-checkout set \
skills/threat-model-generation \
skills/commit-security-scan \
skills/vulnerability-validation \
skills/security-review 2>/dev/null || true

# Copy skills to ~/.factory/skills/ and track installed count
INSTALLED_COUNT=0
for skill in threat-model-generation commit-security-scan vulnerability-validation security-review; do
if [ -d "skills/$skill" ]; then
cp -r "skills/$skill" "$SKILLS_DIR/"
echo " Installed skill: $skill"
INSTALLED_COUNT=$((INSTALLED_COUNT + 1))
else
echo " Warning: Skill not found in repo: $skill"
fi
done

# Cleanup
rm -rf "$TEMP_DIR"

# Verify at least one skill was installed
if [ "$INSTALLED_COUNT" -eq 0 ]; then
echo "Warning: No security skills were installed. The skills may not exist in the Factory-AI/skills repository."
echo "Security review will proceed but may have limited functionality."
else
echo "Security skills installation complete ($INSTALLED_COUNT skills installed)"
fi

# Verify skills exist in the target directory
echo "Verifying installed skills in $SKILLS_DIR..."
VERIFIED_COUNT=0
for skill in threat-model-generation commit-security-scan vulnerability-validation security-review; do
if [ -d "$SKILLS_DIR/$skill" ]; then
echo " Verified: $skill"
VERIFIED_COUNT=$((VERIFIED_COUNT + 1))
fi
done

if [ "$VERIFIED_COUNT" -ne "$INSTALLED_COUNT" ]; then
echo "Warning: Skill verification mismatch. Expected $INSTALLED_COUNT, found $VERIFIED_COUNT in $SKILLS_DIR"
fi

- name: Checkout PR branch for review
if: steps.prepare.outputs.contains_trigger == 'true' && steps.prepare.outputs.review_pr_number != ''
shell: bash
Expand Down
6 changes: 6 additions & 0 deletions base-action/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ inputs:
required: false
default: ""

reasoning_effort:
description: "Optional reasoning effort to pass to Droid Exec via --reasoning-effort (e.g., 'low', 'medium', 'high', 'xhigh'). If empty, no --reasoning-effort flag is passed."
required: false
default: ""

# Action settings
droid_args:
description: "Additional arguments to pass directly to Droid CLI (e.g., '--auto', '--enabled-tools read,edit')"
Expand Down Expand Up @@ -113,6 +118,7 @@ runs:
INPUT_PROMPT_FILE: ${{ inputs.prompt_file }}
INPUT_SETTINGS: ${{ inputs.settings }}
INPUT_DROID_ARGS: ${{ inputs.droid_args }}
INPUT_REASONING_EFFORT: ${{ inputs.reasoning_effort }}
INPUT_PATH_TO_DROID_EXECUTABLE: ${{ inputs.path_to_droid_executable }}
INPUT_PATH_TO_BUN_EXECUTABLE: ${{ inputs.path_to_bun_executable }}
INPUT_SHOW_FULL_OUTPUT: ${{ inputs.show_full_output }}
Expand Down
4 changes: 2 additions & 2 deletions base-action/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ async function run() {

await runDroid(promptConfig.path, {
droidArgs: process.env.INPUT_DROID_ARGS,
reasoningEffort: process.env.INPUT_REASONING_EFFORT,
allowedTools: process.env.INPUT_ALLOWED_TOOLS,
disallowedTools: process.env.INPUT_DISALLOWED_TOOLS,
maxTurns: process.env.INPUT_MAX_TURNS,
mcpTools: process.env.INPUT_MCP_TOOLS,
systemPrompt: process.env.INPUT_SYSTEM_PROMPT,
appendSystemPrompt: process.env.INPUT_APPEND_SYSTEM_PROMPT,
pathToDroidExecutable:
process.env.INPUT_PATH_TO_DROID_EXECUTABLE,
pathToDroidExecutable: process.env.INPUT_PATH_TO_DROID_EXECUTABLE,
showFullOutput: process.env.INPUT_SHOW_FULL_OUTPUT,
});
} catch (error) {
Expand Down
81 changes: 81 additions & 0 deletions combine/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: "Droid Combine Results"
description: "Combine code review and security review results, post inline comments, update summary"

inputs:
factory_api_key:
description: "Factory API key"
required: true
tracking_comment_id:
description: "ID of the tracking comment to update"
required: true
code_review_results:
description: "Path to code review results JSON (optional)"
required: false
default: ""
security_results:
description: "Path to security results JSON (optional)"
required: false
default: ""
code_review_status:
description: "Code review job status (success/failure/skipped)"
required: false
default: "skipped"
security_review_status:
description: "Security review job status (success/failure/skipped)"
required: false
default: "skipped"

runs:
using: "composite"
steps:
- name: Install Bun
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76
with:
bun-version: 1.2.11

- name: Install Dependencies
shell: bash
run: |
cd ${{ github.action_path }}/..
bun install
cd ${{ github.action_path }}/../base-action
bun install

- name: Install Droid CLI
shell: bash
run: |
curl --retry 5 --retry-delay 2 --retry-all-errors -fsSL https://app.factory.ai/cli | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
"$HOME/.local/bin/droid" --version

- name: Get GitHub Token
id: token
shell: bash
run: |
bun run ${{ github.action_path }}/../src/entrypoints/get-token.ts
env:
FACTORY_API_KEY: ${{ inputs.factory_api_key }}

- name: Generate Combine Prompt
id: prompt
shell: bash
run: |
bun run ${{ github.action_path }}/../src/entrypoints/generate-combine-prompt.ts
env:
GITHUB_TOKEN: ${{ steps.token.outputs.github_token }}
DROID_COMMENT_ID: ${{ inputs.tracking_comment_id }}
CODE_REVIEW_RESULTS: ${{ inputs.code_review_results }}
SECURITY_RESULTS: ${{ inputs.security_results }}
CODE_REVIEW_STATUS: ${{ inputs.code_review_status }}
SECURITY_REVIEW_STATUS: ${{ inputs.security_review_status }}

- name: Run Combine
shell: bash
run: |
bun run ${{ github.action_path }}/../base-action/src/index.ts
env:
INPUT_PROMPT_FILE: ${{ runner.temp }}/droid-prompts/droid-prompt.txt
INPUT_DROID_ARGS: ${{ steps.prompt.outputs.droid_args }}
INPUT_MCP_TOOLS: ${{ steps.prompt.outputs.mcp_tools }}
FACTORY_API_KEY: ${{ inputs.factory_api_key }}
GITHUB_TOKEN: ${{ steps.token.outputs.github_token }}
Loading