cicaddy-github 0.7.1__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/.agents/skills/cicaddy-action/SKILL.md +8 -2
  2. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/.github/workflows/pr-review.yml +6 -1
  3. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/AGENTS.md +5 -1
  4. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/PKG-INFO +41 -4
  5. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/README.md +39 -2
  6. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/action.yml +8 -0
  7. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/entrypoint.sh +2 -0
  8. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/pyproject.toml +2 -2
  9. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/src/cicaddy_github/__init__.py +1 -1
  10. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/src/cicaddy_github/config/settings.py +10 -0
  11. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/src/cicaddy_github/github_integration/agents.py +194 -4
  12. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/src/cicaddy_github/github_integration/analyzer.py +77 -4
  13. cicaddy_github-0.8.0/tests/unit/test_inline_review.py +1369 -0
  14. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/.github/dependabot.yml +0 -0
  15. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/.github/workflows/changelog.yml +0 -0
  16. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/.github/workflows/ci.yml +0 -0
  17. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/.github/workflows/release.yml +0 -0
  18. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/.gitignore +0 -0
  19. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/.pre-commit-config.yaml +0 -0
  20. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/CLAUDE.md +0 -0
  21. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/CODE_OF_CONDUCT.md +0 -0
  22. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/CONTRIBUTING.md +0 -0
  23. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/Dockerfile +0 -0
  24. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/LICENSE +0 -0
  25. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/docs/delegation.md +0 -0
  26. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/docs/providers.md +0 -0
  27. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/src/cicaddy_github/config/__init__.py +0 -0
  28. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/src/cicaddy_github/github_integration/__init__.py +0 -0
  29. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/src/cicaddy_github/github_integration/detector.py +0 -0
  30. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/src/cicaddy_github/github_integration/go_dep_review_tools.py +0 -0
  31. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/src/cicaddy_github/github_integration/tools.py +0 -0
  32. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/src/cicaddy_github/plugin.py +0 -0
  33. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/src/cicaddy_github/security/__init__.py +0 -0
  34. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/src/cicaddy_github/security/leak_detector.py +0 -0
  35. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/src/cicaddy_github/validation.py +0 -0
  36. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tasks/changelog_report.yml +0 -0
  37. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tasks/go_dep_impact_review.yml +0 -0
  38. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tasks/pr_review.yml +0 -0
  39. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/templates/report_template.html +0 -0
  40. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tests/__init__.py +0 -0
  41. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tests/conftest.py +0 -0
  42. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tests/unit/__init__.py +0 -0
  43. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tests/unit/test_agents.py +0 -0
  44. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tests/unit/test_analyzer.py +0 -0
  45. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tests/unit/test_detector.py +0 -0
  46. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tests/unit/test_go_dep_review.py +0 -0
  47. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tests/unit/test_leak_detector.py +0 -0
  48. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tests/unit/test_plugin.py +0 -0
  49. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tests/unit/test_review_delegation_hooks.py +0 -0
  50. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tests/unit/test_settings.py +0 -0
  51. {cicaddy_github-0.7.1 → cicaddy_github-0.8.0}/tests/unit/test_tools.py +0 -0
@@ -127,10 +127,12 @@ can reference them as bash variables (`INPUT_AI_PROVIDER`, `INPUT_AI_API_KEY`, e
127
127
  | `task_prompt` | No | Inline task prompt |
128
128
  | `post_pr_comment` | No | Post results as PR comment (default: `false`) |
129
129
  | `submit_review` | No | Submit formal PR review with APPROVE/REQUEST_CHANGES (default: `false`) |
130
+ | `inline_review_comments` | No | Post AI findings as inline comments on PR diff lines (default: `false`) |
130
131
  | `run_govulncheck` | No | Run govulncheck for vulnerability reachability analysis (default: `false`) |
131
132
  | `dep_review_severity_threshold` | No | Minimum semver bump to analyze: `minor` or `major` (default: `minor`) |
132
133
  | `delegation_mode` | No | `none` (default) or `auto` for sub-agent delegation |
133
134
  | `max_sub_agents` | No | Max concurrent sub-agents, 1-10 (default: `3`) |
135
+ | `delegation_verify_findings` | No | Verify sub-agent findings against codebase (default: `false`) |
134
136
  | `github_token` | No | GitHub token (default: `${{ github.token }}`) |
135
137
  | `mcp_servers_config` | No | JSON array of MCP server configs |
136
138
  | `slack_webhook_url` | No | Slack webhook URL |
@@ -196,6 +198,8 @@ github = "cicaddy_github.plugin:validate"
196
198
  - `github_token`, `github_repository`, `github_ref`, `github_event_name`
197
199
  - `github_sha`, `github_run_id`, `github_pr_number`
198
200
  - `post_pr_comment` (bool)
201
+ - `submit_review` (bool)
202
+ - `inline_review_comments` (bool)
199
203
 
200
204
  All loaded from environment variables via `load_settings()`.
201
205
 
@@ -233,7 +237,7 @@ the `safe-to-review` label. The label is auto-removed on new pushes to prevent
233
237
  TOCTOU bypasses.
234
238
 
235
239
  ```yaml
236
- - uses: redhat-community-ai-tools/cicaddy-action@v0.7.1
240
+ - uses: redhat-community-ai-tools/cicaddy-action@v0.8.0
237
241
  with:
238
242
  ai_provider: gemini
239
243
  ai_model: gemini-3.5-flash
@@ -329,11 +333,13 @@ cicaddy-action v0.5.0+ supports AI-powered sub-agent delegation via cicaddy>=0.8
329
333
  **Action Inputs:**
330
334
  - `delegation_mode`: `none` (default) or `auto`
331
335
  - `max_sub_agents`: 1-10 (default: `3`)
336
+ - `delegation_verify_findings`: `false` (default) — verify findings against codebase
332
337
 
333
338
  **Environment Variables:**
334
339
  - `DELEGATION_MODE`: `none` or `auto`
335
340
  - `MAX_SUB_AGENTS`: 1-10 (default: `3`)
336
- - `SUB_AGENT_MAX_ITERS`: 1-15 (default: `10`)
341
+ - `SUB_AGENT_MAX_ITERS`: 1-15 (default: `5`)
342
+ - `DELEGATION_VERIFY_FINDINGS`: verify findings against codebase (`true`/`false`)
337
343
  - `DELEGATION_AGENTS_DIR`: `.agents/delegation` (custom agent YAML directory)
338
344
  - `DELEGATION_AGENTS`: JSON array for inline custom agents
339
345
  - `TRIAGE_PROMPT`: Custom triage instructions
@@ -49,7 +49,7 @@ jobs:
49
49
  fi
50
50
 
51
51
  - name: AI Code Review
52
- uses: redhat-community-ai-tools/cicaddy-action@v0.7.1
52
+ uses: redhat-community-ai-tools/cicaddy-action@v0.8.0
53
53
  id: review
54
54
  with:
55
55
  ai_provider: gemini
@@ -57,7 +57,12 @@ jobs:
57
57
  ai_api_key: ${{ secrets.AI_API_KEY }}
58
58
  task_file: tasks/pr_review.yml
59
59
  post_pr_comment: 'true'
60
+ # submit_review and inline_review_comments intentionally omitted —
61
+ # this workflow uses pull_request_target for fork PR support, and
62
+ # formal reviews should not be combined with pull_request_target.
63
+ # See docs/providers.md#submit_review-and-fork-pull-requests
60
64
  delegation_mode: auto
65
+ delegation_verify_findings: 'true'
61
66
  mcp_servers_config: ${{ steps.mcp.outputs.config }}
62
67
  env:
63
68
  ANALYSIS_FOCUS: "general"
@@ -102,7 +102,7 @@ The cicaddy-github plugin provides:
102
102
  | `DELEGATION_AGENTS` | (empty) | JSON config for inline custom sub-agent definitions |
103
103
  | `TRIAGE_PROMPT` | (empty) | Custom triage instructions |
104
104
 
105
- Action inputs: `delegation_mode`, `max_sub_agents`
105
+ Action inputs: `delegation_mode`, `max_sub_agents`, `delegation_verify_findings`
106
106
  CLI flags: `--delegation-mode auto --max-sub-agents 2`
107
107
 
108
108
  See cicaddy's [sub-agent delegation docs](https://github.com/waynesun09/cicaddy/blob/main/docs/sub-agent-delegation.md) for built-in agents, custom YAML format, and tool filtering.
@@ -124,12 +124,16 @@ All inputs use **underscores** (not hyphens) for Docker container compatibility:
124
124
  | `task_prompt` | No | Inline task prompt |
125
125
  | `post_pr_comment` | No | Post results as PR comment (default: `false`) |
126
126
  | `submit_review` | No | Submit formal PR review with APPROVE/REQUEST_CHANGES (default: `false`) |
127
+ | `inline_review_comments` | No | Post AI findings as inline comments on PR diff lines (default: `false`) |
127
128
  | `github_token` | No | GitHub token (default: `${{ github.token }}`) |
128
129
  | `mcp_servers_config` | No | JSON array of MCP server configs |
129
130
  | `slack_webhook_url` | No | Slack webhook URL |
130
131
  | `report_template` | No | Custom HTML report template path |
132
+ | `run_govulncheck` | No | Run govulncheck for vulnerability reachability analysis (default: `false`) |
133
+ | `dep_review_severity_threshold` | No | Minimum semver bump to analyze: `minor` or `major` (default: `minor`) |
131
134
  | `delegation_mode` | No | `none` (default) or `auto` for sub-agent delegation |
132
135
  | `max_sub_agents` | No | Max concurrent sub-agents, 1-10 (default: `3`) |
136
+ | `delegation_verify_findings` | No | Verify sub-agent findings against codebase (default: `false`) |
133
137
 
134
138
  *Not required if provider-specific key is set via `env:`.
135
139
 
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cicaddy-github
3
- Version: 0.7.1
3
+ Version: 0.8.0
4
4
  Summary: GitHub Actions plugin for cicaddy AI agent framework
5
5
  Project-URL: Homepage, https://github.com/redhat-community-ai-tools/cicaddy-action
6
6
  Project-URL: Repository, https://github.com/redhat-community-ai-tools/cicaddy-action.git
7
7
  Project-URL: Issues, https://github.com/redhat-community-ai-tools/cicaddy-action/issues
8
8
  Author: Wayne Sun
9
- License: Apache-2.0
9
+ License-Expression: Apache-2.0
10
10
  License-File: LICENSE
11
11
  Requires-Python: >=3.11
12
12
  Requires-Dist: cicaddy>=0.11.0
@@ -95,8 +95,10 @@ jobs:
95
95
  google_cloud_project: ${{ vars.GCP_PROJECT_ID }}
96
96
  task_file: tasks/pr_review.yml
97
97
  post_pr_comment: 'true'
98
- env:
99
- DELEGATION_MODE: auto
98
+ submit_review: 'true'
99
+ inline_review_comments: 'true'
100
+ delegation_mode: auto
101
+ delegation_verify_findings: 'true'
100
102
  ```
101
103
 
102
104
  > **Fork PRs**: The `pull_request` event cannot mint OIDC tokens for PRs
@@ -215,12 +217,42 @@ SA key fallback, and an authentication method comparison table.
215
217
  | `slack_webhook_url` | No | Slack webhook URL for notifications |
216
218
  | `post_pr_comment` | No | Post results as PR comment (default: `false`) |
217
219
  | `submit_review` | No | Submit formal PR review with APPROVE/REQUEST_CHANGES (default: `false`) |
220
+ | `inline_review_comments` | No | Post AI findings as inline comments on PR diff lines (default: `false`) |
218
221
  | `run_govulncheck` | No | Run govulncheck for vulnerability reachability analysis (default: `false`) |
219
222
  | `dep_review_severity_threshold` | No | Minimum semver bump to analyze: `minor` or `major` (default: `minor`) |
220
223
  | `delegation_mode` | No | Enable AI-powered sub-agent delegation: `none` (default) or `auto` |
221
224
  | `max_sub_agents` | No | Maximum concurrent sub-agents, 1-10 (default: `3`) |
225
+ | `delegation_verify_findings` | No | Verify sub-agent findings against codebase to reduce false positives (default: `false`) |
222
226
  | `github_token` | No | GitHub token (default: `${{ github.token }}`) |
223
227
 
228
+ ### Review Output Options
229
+
230
+ The action supports three independent review output options that control how results appear on a PR. All three default to `false` and can be combined:
231
+
232
+ | Option | What it does |
233
+ |--------|-------------|
234
+ | `post_pr_comment` | Posts (or updates) a bot comment on the PR with the full review summary. On subsequent runs the same comment is updated in place. |
235
+ | `submit_review` | Submits a formal GitHub review (APPROVE / REQUEST_CHANGES / COMMENT) based on the AI analysis. |
236
+ | `inline_review_comments` | Attaches AI findings as inline comments on the exact diff lines. Requires `submit_review: 'true'` since inline comments are part of the GitHub review. |
237
+
238
+ **Recommended combinations:**
239
+
240
+ ```yaml
241
+ # Summary comment only (simplest)
242
+ post_pr_comment: 'true'
243
+
244
+ # Summary comment + inline diff comments (recommended for most teams)
245
+ post_pr_comment: 'true'
246
+ submit_review: 'true'
247
+ inline_review_comments: 'true'
248
+
249
+ # Formal review with inline comments, no standalone comment
250
+ submit_review: 'true'
251
+ inline_review_comments: 'true'
252
+ ```
253
+
254
+ > **Note:** `inline_review_comments` works best with `submit_review: 'true'` and `delegation_mode: auto`. Delegation produces structured findings with file/line info that inline comments need. Without `submit_review`, inline comments are posted as a `COMMENT` review (no approval/rejection verdict).
255
+
224
256
  ## Outputs
225
257
 
226
258
  | Output | Description |
@@ -271,6 +303,8 @@ GITHUB_PR_NUMBER=42
271
303
 
272
304
  # Agent Settings
273
305
  POST_PR_COMMENT=true
306
+ SUBMIT_REVIEW=true
307
+ INLINE_REVIEW_COMMENTS=true
274
308
  ENABLE_LOCAL_TOOLS=true
275
309
  LOCAL_TOOLS_WORKING_DIR=.
276
310
 
@@ -326,9 +360,12 @@ uv run cicaddy validate --env-file .env.my-review
326
360
  | `GITHUB_EVENT_NAME` | No | Set to `pull_request` for auto-detection (optional if `GITHUB_PR_NUMBER` is set) |
327
361
  | `GITHUB_PR_NUMBER` | Yes | PR number to review |
328
362
  | `POST_PR_COMMENT` | No | Post results as PR comment (`true`/`false`) |
363
+ | `SUBMIT_REVIEW` | No | Submit formal PR review with APPROVE/REQUEST_CHANGES (`true`/`false`) |
364
+ | `INLINE_REVIEW_COMMENTS` | No | Post AI findings as inline comments on PR diff lines (`true`/`false`) |
329
365
  | `AGENT_TASKS` | No | Agent task type (e.g. `go_dep_review` for Go dependency analysis) |
330
366
  | `DELEGATION_MODE` | No | `auto` for AI-powered sub-agent delegation, `none` for single-agent (default: `none`) |
331
367
  | `MAX_SUB_AGENTS` | No | Max concurrent sub-agents for delegation, 1-10 (default: `3`) |
368
+ | `DELEGATION_VERIFY_FINDINGS` | No | Verify sub-agent findings against codebase to reduce false positives (`true`/`false`) |
332
369
  | `SUB_AGENT_MAX_ITERS` | No | Max iterations per sub-agent, 1-15 (default: `5`) |
333
370
  | `AI_TASK_FILE` | No | Path to DSPy YAML task file for custom workflows |
334
371
  | `RUN_GOVULNCHECK` | No | Run govulncheck for reachability analysis (`true`/`false`) |
@@ -75,8 +75,10 @@ jobs:
75
75
  google_cloud_project: ${{ vars.GCP_PROJECT_ID }}
76
76
  task_file: tasks/pr_review.yml
77
77
  post_pr_comment: 'true'
78
- env:
79
- DELEGATION_MODE: auto
78
+ submit_review: 'true'
79
+ inline_review_comments: 'true'
80
+ delegation_mode: auto
81
+ delegation_verify_findings: 'true'
80
82
  ```
81
83
 
82
84
  > **Fork PRs**: The `pull_request` event cannot mint OIDC tokens for PRs
@@ -195,12 +197,42 @@ SA key fallback, and an authentication method comparison table.
195
197
  | `slack_webhook_url` | No | Slack webhook URL for notifications |
196
198
  | `post_pr_comment` | No | Post results as PR comment (default: `false`) |
197
199
  | `submit_review` | No | Submit formal PR review with APPROVE/REQUEST_CHANGES (default: `false`) |
200
+ | `inline_review_comments` | No | Post AI findings as inline comments on PR diff lines (default: `false`) |
198
201
  | `run_govulncheck` | No | Run govulncheck for vulnerability reachability analysis (default: `false`) |
199
202
  | `dep_review_severity_threshold` | No | Minimum semver bump to analyze: `minor` or `major` (default: `minor`) |
200
203
  | `delegation_mode` | No | Enable AI-powered sub-agent delegation: `none` (default) or `auto` |
201
204
  | `max_sub_agents` | No | Maximum concurrent sub-agents, 1-10 (default: `3`) |
205
+ | `delegation_verify_findings` | No | Verify sub-agent findings against codebase to reduce false positives (default: `false`) |
202
206
  | `github_token` | No | GitHub token (default: `${{ github.token }}`) |
203
207
 
208
+ ### Review Output Options
209
+
210
+ The action supports three independent review output options that control how results appear on a PR. All three default to `false` and can be combined:
211
+
212
+ | Option | What it does |
213
+ |--------|-------------|
214
+ | `post_pr_comment` | Posts (or updates) a bot comment on the PR with the full review summary. On subsequent runs the same comment is updated in place. |
215
+ | `submit_review` | Submits a formal GitHub review (APPROVE / REQUEST_CHANGES / COMMENT) based on the AI analysis. |
216
+ | `inline_review_comments` | Attaches AI findings as inline comments on the exact diff lines. Requires `submit_review: 'true'` since inline comments are part of the GitHub review. |
217
+
218
+ **Recommended combinations:**
219
+
220
+ ```yaml
221
+ # Summary comment only (simplest)
222
+ post_pr_comment: 'true'
223
+
224
+ # Summary comment + inline diff comments (recommended for most teams)
225
+ post_pr_comment: 'true'
226
+ submit_review: 'true'
227
+ inline_review_comments: 'true'
228
+
229
+ # Formal review with inline comments, no standalone comment
230
+ submit_review: 'true'
231
+ inline_review_comments: 'true'
232
+ ```
233
+
234
+ > **Note:** `inline_review_comments` works best with `submit_review: 'true'` and `delegation_mode: auto`. Delegation produces structured findings with file/line info that inline comments need. Without `submit_review`, inline comments are posted as a `COMMENT` review (no approval/rejection verdict).
235
+
204
236
  ## Outputs
205
237
 
206
238
  | Output | Description |
@@ -251,6 +283,8 @@ GITHUB_PR_NUMBER=42
251
283
 
252
284
  # Agent Settings
253
285
  POST_PR_COMMENT=true
286
+ SUBMIT_REVIEW=true
287
+ INLINE_REVIEW_COMMENTS=true
254
288
  ENABLE_LOCAL_TOOLS=true
255
289
  LOCAL_TOOLS_WORKING_DIR=.
256
290
 
@@ -306,9 +340,12 @@ uv run cicaddy validate --env-file .env.my-review
306
340
  | `GITHUB_EVENT_NAME` | No | Set to `pull_request` for auto-detection (optional if `GITHUB_PR_NUMBER` is set) |
307
341
  | `GITHUB_PR_NUMBER` | Yes | PR number to review |
308
342
  | `POST_PR_COMMENT` | No | Post results as PR comment (`true`/`false`) |
343
+ | `SUBMIT_REVIEW` | No | Submit formal PR review with APPROVE/REQUEST_CHANGES (`true`/`false`) |
344
+ | `INLINE_REVIEW_COMMENTS` | No | Post AI findings as inline comments on PR diff lines (`true`/`false`) |
309
345
  | `AGENT_TASKS` | No | Agent task type (e.g. `go_dep_review` for Go dependency analysis) |
310
346
  | `DELEGATION_MODE` | No | `auto` for AI-powered sub-agent delegation, `none` for single-agent (default: `none`) |
311
347
  | `MAX_SUB_AGENTS` | No | Max concurrent sub-agents for delegation, 1-10 (default: `3`) |
348
+ | `DELEGATION_VERIFY_FINDINGS` | No | Verify sub-agent findings against codebase to reduce false positives (`true`/`false`) |
312
349
  | `SUB_AGENT_MAX_ITERS` | No | Max iterations per sub-agent, 1-15 (default: `5`) |
313
350
  | `AI_TASK_FILE` | No | Path to DSPy YAML task file for custom workflows |
314
351
  | `RUN_GOVULNCHECK` | No | Run govulncheck for reachability analysis (`true`/`false`) |
@@ -50,6 +50,10 @@ inputs:
50
50
  description: 'Submit a formal PR review with APPROVE or REQUEST_CHANGES based on AI analysis (requires github-token with pull-requests: write)'
51
51
  required: false
52
52
  default: 'false'
53
+ inline_review_comments:
54
+ description: 'Post AI findings as inline comments on PR diff lines. Best used with submit_review and delegation_mode=auto for structured findings. (requires github-token with pull-requests: write)'
55
+ required: false
56
+ default: 'false'
53
57
  run_govulncheck:
54
58
  description: 'Run govulncheck for vulnerability reachability analysis (requires Go and govulncheck installed)'
55
59
  required: false
@@ -66,6 +70,10 @@ inputs:
66
70
  description: 'Maximum concurrent sub-agents for delegation (1-10, default: 3)'
67
71
  required: false
68
72
  default: '3'
73
+ delegation_verify_findings:
74
+ description: 'Verify each sub-agent finding against the actual codebase via a lightweight AI check to reduce false positives (default: false)'
75
+ required: false
76
+ default: 'false'
69
77
  github_token:
70
78
  description: 'GitHub token for API access'
71
79
  required: false
@@ -109,10 +109,12 @@ export SLACK_WEBHOOK_URL="${INPUT_SLACK_WEBHOOK_URL}"
109
109
  export GITHUB_TOKEN="${INPUT_GITHUB_TOKEN:-$GITHUB_TOKEN}"
110
110
  export POST_PR_COMMENT="${INPUT_POST_PR_COMMENT:-false}"
111
111
  export SUBMIT_REVIEW="${INPUT_SUBMIT_REVIEW:-false}"
112
+ export INLINE_REVIEW_COMMENTS="${INPUT_INLINE_REVIEW_COMMENTS:-false}"
112
113
  export RUN_GOVULNCHECK="${INPUT_RUN_GOVULNCHECK:-false}"
113
114
  export DEP_REVIEW_SEVERITY_THRESHOLD="${INPUT_DEP_REVIEW_SEVERITY_THRESHOLD:-minor}"
114
115
  export DELEGATION_MODE="${INPUT_DELEGATION_MODE:-none}"
115
116
  export MAX_SUB_AGENTS="${INPUT_MAX_SUB_AGENTS:-3}"
117
+ export DELEGATION_VERIFY_FINDINGS="${INPUT_DELEGATION_VERIFY_FINDINGS:-false}"
116
118
 
117
119
  # Validate MAX_SUB_AGENTS is a number
118
120
  if [[ -n "${MAX_SUB_AGENTS}" ]] && ! [[ "${MAX_SUB_AGENTS}" =~ ^[0-9]+$ ]]; then
@@ -4,11 +4,11 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cicaddy-github"
7
- version = "0.7.1"
7
+ version = "0.8.0"
8
8
  description = "GitHub Actions plugin for cicaddy AI agent framework"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
11
- license = {text = "Apache-2.0"}
11
+ license = "Apache-2.0"
12
12
  authors = [{name = "Wayne Sun"}]
13
13
  dependencies = [
14
14
  "cicaddy>=0.11.0",
@@ -1,3 +1,3 @@
1
1
  """cicaddy-github: GitHub Actions plugin for cicaddy AI agent framework."""
2
2
 
3
- __version__ = "0.7.1"
3
+ __version__ = "0.8.0"
@@ -66,6 +66,11 @@ class Settings(CoreSettings):
66
66
  validation_alias=AliasChoices("SUBMIT_REVIEW"),
67
67
  description="Whether to submit a formal PR review (APPROVE or REQUEST_CHANGES)",
68
68
  )
69
+ inline_review_comments: bool = Field(
70
+ default=False,
71
+ validation_alias=AliasChoices("INLINE_REVIEW_COMMENTS"),
72
+ description="Post findings as inline comments on PR diff lines.",
73
+ )
69
74
  run_govulncheck: bool = Field(
70
75
  default=False,
71
76
  validation_alias=AliasChoices("RUN_GOVULNCHECK"),
@@ -115,6 +120,11 @@ def load_settings() -> Settings:
115
120
  if submit_review:
116
121
  env_data["submit_review"] = submit_review.lower() in ("true", "1", "yes")
117
122
 
123
+ # Inline review comments flag
124
+ inline_review = os.getenv("INLINE_REVIEW_COMMENTS", "").strip()
125
+ if inline_review:
126
+ env_data["inline_review_comments"] = inline_review.lower() in ("true", "1", "yes")
127
+
118
128
  # Dep review configuration
119
129
  run_govulncheck = os.getenv("RUN_GOVULNCHECK", "").strip()
120
130
  if run_govulncheck:
@@ -1,5 +1,6 @@
1
1
  """GitHub AI Agents for PR review and task execution."""
2
2
 
3
+ import html
3
4
  import os
4
5
  import re
5
6
  import textwrap
@@ -346,7 +347,7 @@ Please provide your comprehensive analysis in markdown format.
346
347
  )
347
348
 
348
349
  # Post PR comment if enabled (updates existing bot comment in-place)
349
- post_comment = getattr(self.settings, "post_pr_comment", False)
350
+ post_comment = self.settings.post_pr_comment
350
351
  if post_comment and self.platform_analyzer and self.pr_number:
351
352
  try:
352
353
  comment = self._format_pr_comment(analysis_result)
@@ -360,9 +361,17 @@ Please provide your comprehensive analysis in markdown format.
360
361
  )
361
362
  logger.debug("PR comment post traceback:", exc_info=True)
362
363
 
363
- # Submit formal PR review if enabled
364
- submit_review = getattr(self.settings, "submit_review", False)
365
- if submit_review and self.platform_analyzer and self.pr_number:
364
+ # Check if inline review comments will handle the formal review
365
+ inline_enabled = self.settings.inline_review_comments
366
+ findings = analysis_result.get("findings") or []
367
+ inline_findings = [f for f in findings if f.get("line") and f.get("file")]
368
+ inline_will_post = (
369
+ inline_enabled and inline_findings and self.platform_analyzer and self.pr_number
370
+ )
371
+
372
+ # Submit formal PR review if enabled — skip when inline review will handle it
373
+ submit_review = self.settings.submit_review
374
+ if submit_review and self.platform_analyzer and self.pr_number and not inline_will_post:
366
375
  try:
367
376
  ai_text = analysis_result.get("ai_analysis", "")
368
377
  verdict = extract_review_verdict(ai_text)
@@ -377,9 +386,190 @@ Please provide your comprehensive analysis in markdown format.
377
386
  )
378
387
  logger.debug("PR review submit traceback:", exc_info=True)
379
388
 
389
+ # Post inline review comments if enabled
390
+ if inline_enabled:
391
+ if not findings:
392
+ logger.warning(
393
+ "INLINE_REVIEW_COMMENTS is enabled but no structured findings "
394
+ "were produced. Inline comments require DELEGATION_MODE=auto."
395
+ )
396
+ skipped = len(findings) - len(inline_findings)
397
+ logger.info(
398
+ f"Inline review for PR #{self.pr_number}: "
399
+ f"{len(inline_findings)} findings with line+file, "
400
+ f"{skipped} without (total {len(findings)})"
401
+ )
402
+ if inline_will_post:
403
+ try:
404
+ await self._post_inline_review(analysis_result, inline_findings)
405
+ except Exception as e:
406
+ logger.error(
407
+ f"Failed to post inline review: {self.leak_detector.sanitize_text(str(e))}"
408
+ )
409
+ logger.debug("Inline review post traceback:", exc_info=True)
410
+ # Fallback: submit standalone review since inline failed
411
+ if submit_review and self.platform_analyzer:
412
+ try:
413
+ ai_text = analysis_result.get("ai_analysis", "")
414
+ verdict = extract_review_verdict(ai_text)
415
+ review_body = self._format_review_body(analysis_result)
416
+ await self.platform_analyzer.submit_pr_review(
417
+ int(self.pr_number), review_body, event=verdict
418
+ )
419
+ logger.info(f"Submitted fallback {verdict} review on PR #{self.pr_number}")
420
+ except Exception as fallback_e:
421
+ logger.error(
422
+ f"Fallback review also failed: "
423
+ f"{self.leak_detector.sanitize_text(str(fallback_e))}"
424
+ )
425
+
380
426
  # Send Slack notification using parent class
381
427
  await super().send_notifications(report, analysis_result)
382
428
 
429
+ @staticmethod
430
+ def _format_inline_comment(finding: dict, leak_detector: Any = None) -> str:
431
+ """Format a finding dict into a markdown inline comment body.
432
+
433
+ Args:
434
+ finding: Finding dict with severity, message, and optional
435
+ suggestion and agent_source fields.
436
+ leak_detector: Optional leak detector to sanitize secrets.
437
+
438
+ Returns:
439
+ Formatted markdown string for the inline comment.
440
+ """
441
+ severity = finding.get("severity", "info").upper()
442
+ emoji_map = {
443
+ "CRITICAL": "\U0001f534",
444
+ "MAJOR": "\U0001f7e0",
445
+ "MINOR": "\U0001f7e1",
446
+ "NIT": "\U0001f535",
447
+ }
448
+ emoji = emoji_map.get(severity, "ℹ️")
449
+ message = finding.get("message", "No description provided")
450
+ suggestion = finding.get("suggestion", "")
451
+ if leak_detector:
452
+ message = leak_detector.sanitize_text(message)
453
+ if suggestion:
454
+ suggestion = leak_detector.sanitize_text(suggestion)
455
+ body = f"{emoji} **{severity}**: {message}"
456
+ if suggestion:
457
+ body += f"\n\n**Suggestion**: {suggestion}"
458
+ if finding.get("agent_source"):
459
+ agent_source = finding["agent_source"]
460
+ if leak_detector:
461
+ agent_source = leak_detector.sanitize_text(agent_source)
462
+ body += f"\n\n<sub>Source: {html.escape(agent_source)}</sub>"
463
+ return body
464
+
465
+ MAX_INLINE_COMMENTS = 25
466
+
467
+ async def _post_inline_review(
468
+ self, analysis_result: dict[str, Any], findings: list[dict]
469
+ ) -> None:
470
+ """Post findings as inline review comments on the PR diff.
471
+
472
+ Args:
473
+ analysis_result: Full analysis result dict.
474
+ findings: Pre-filtered findings that have both file and line.
475
+ """
476
+ pr_files = await self.platform_analyzer.get_pr_changed_files(int(self.pr_number))
477
+
478
+ # Build patch lookup: file path -> patch string
479
+ patch_map: dict[str, str | None] = {}
480
+ for f in pr_files:
481
+ patch_map[f.filename] = f.patch
482
+
483
+ severity_order = {"critical": 0, "major": 1, "minor": 2, "nit": 3}
484
+ sorted_findings = sorted(
485
+ findings,
486
+ key=lambda f: severity_order.get(f.get("severity", "nit").lower(), 4),
487
+ )
488
+
489
+ # Build inline comments, validating each line is in the diff
490
+ comments: list[dict] = []
491
+ skipped = 0
492
+ for finding in sorted_findings:
493
+ file_path = finding["file"]
494
+ try:
495
+ line = int(finding["line"])
496
+ except (ValueError, TypeError):
497
+ skipped += 1
498
+ continue
499
+ patch = patch_map.get(file_path)
500
+
501
+ if patch is None or not GitHubAnalyzer._is_line_in_diff_range(patch, line):
502
+ skipped += 1
503
+ continue
504
+
505
+ comment: dict[str, Any] = {
506
+ "path": file_path,
507
+ "body": self._format_inline_comment(finding, leak_detector=self.leak_detector),
508
+ "line": line,
509
+ "side": "RIGHT",
510
+ }
511
+ start_line = finding.get("start_line")
512
+ try:
513
+ start_line = int(start_line) if start_line else None
514
+ except (ValueError, TypeError):
515
+ start_line = None
516
+ if (
517
+ start_line
518
+ and start_line < line
519
+ and GitHubAnalyzer._is_line_in_diff_range(patch, start_line)
520
+ ):
521
+ comment["start_line"] = start_line
522
+ comment["start_side"] = "RIGHT"
523
+
524
+ comments.append(comment)
525
+ if len(comments) >= self.MAX_INLINE_COMMENTS:
526
+ truncated = len(sorted_findings) - skipped - self.MAX_INLINE_COMMENTS
527
+ if truncated > 0:
528
+ logger.warning(
529
+ f"Capped inline comments at {self.MAX_INLINE_COMMENTS}, "
530
+ f"{truncated} lower-severity finding(s) omitted"
531
+ )
532
+ break
533
+
534
+ if not comments:
535
+ logger.info(
536
+ f"No findings survived diff validation on PR #{self.pr_number}; "
537
+ "skipping inline review submission"
538
+ )
539
+ if self.settings.submit_review and self.platform_analyzer:
540
+ ai_text = analysis_result.get("ai_analysis", "")
541
+ verdict = extract_review_verdict(ai_text)
542
+ review_body = self._format_review_body(analysis_result)
543
+ await self.platform_analyzer.submit_pr_review(
544
+ int(self.pr_number), review_body, event=verdict
545
+ )
546
+ logger.info(f"Submitted standalone {verdict} review (no inline comments)")
547
+ return
548
+
549
+ # Determine review event
550
+ submit_review = self.settings.submit_review
551
+ if submit_review:
552
+ ai_text = analysis_result.get("ai_analysis", "")
553
+ event = extract_review_verdict(ai_text)
554
+ else:
555
+ event = "COMMENT"
556
+
557
+ # Build review body
558
+ review_body = self._format_review_body(analysis_result)
559
+
560
+ await self.platform_analyzer.submit_review_with_comments(
561
+ pr_number=int(self.pr_number),
562
+ body=review_body,
563
+ comments=comments,
564
+ event=event,
565
+ )
566
+
567
+ posted = len(comments)
568
+ logger.info(
569
+ f"Inline review: {posted} comment(s) posted, {skipped} skipped "
570
+ f"(not in diff range) on PR #{self.pr_number}"
571
+ )
572
+
383
573
  def _format_review_body(self, analysis_result: dict[str, Any]) -> str:
384
574
  """Format analysis results as a PR review body.
385
575