feat: add generic explore agent#11
Conversation
Platform-aware pre/post scripts support GitHub and Jira, with optional pipeline labels via env vars. Includes public-research and jira-read skills for downstream customization via harness base: composition. Signed-off-by: Adam Scerra <ascerra@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Adam Scerra <ascerra@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
PR Summary by QodoAdd generic explore agent for issue research (GitHub/Jira)
AI Description
Diagram
High-Level Assessment
Files changed (12)
|
|
🤖 Finished Review · ✅ Success · Started 8:29 PM UTC · Completed 8:39 PM UTC |
Code Review by Qodo
1.
|
ReviewVerdict: approve All 14 findings from the prior review have been addressed in commit db4947e. The fixes are clean and complete:
Re-review assessmentSix review dimensions were evaluated against the current HEAD:
The code is clean, well-organized, and ready to merge.
Previous runReviewVerdict: request-changes This PR adds a well-structured exploration agent that follows the existing triage agent's architecture. The credential isolation (keeping Jira/GitHub tokens out of the sandbox), input validation, and repo cloning safeguards are well done. However, there are several issues that should be addressed before merging — one logic error that silently breaks gap reporting in posted comments, schema/prompt mismatches that will cause downstream confusion, and security defense-in-depth gaps. FindingsHigh
Medium
Labels: PR adds a new explore agent with harness, scripts, schema, and skills. |
Align schema with prompt (draft 2020-12, impact_radius, maxLength 1000), fix gap dimension extraction, harden Jira/GitHub label and host handling, and add user docs plus security defense-in-depth fixes from review bots. Signed-off-by: Adam Scerra <ascerra@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
|
Additional review items from the summary (no inline thread) addressed in
All 14 inline review threads have individual replies explaining the fix. |
|
🤖 Finished Review · ✅ Success · Started 8:48 PM UTC · Completed 9:00 PM UTC |
- Block all gh api calls in sandbox (match fix agent pattern) - Remove FULLSEND_OUTPUT_FILE override and GH_TOKEN from sandbox - Guard comment-helpers.sh dependency with clear PR #11 message - Sanitize GHA workflow command output to prevent injection - Check label mutation success before logging success notices - Document proposed_description body updates in user docs - Remove missing icon reference from docs Signed-off-by: Adam Scerra <ascerra@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
- Block all gh api calls in sandbox (match fix agent pattern) - Remove FULLSEND_OUTPUT_FILE override and GH_TOKEN from sandbox - Guard shared script dependencies with clear PR #11 messages - Fix cross-platform GitHub parent linking via resolve_github_parent_number - Default null acceptance_criteria to empty array in create-children.sh - Add GitHub sub-issue deduplication alongside existing Jira dedup - Use refine-escalated label instead of refine-approved on max rounds - Preserve original verdict in critique history on escalation - Clarify sandbox network capabilities in user docs Signed-off-by: Adam Scerra <ascerra@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
ralphbean
left a comment
There was a problem hiding this comment.
I think this needs some changes before we can merge. See inline comments.
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| source "${SCRIPT_DIR}/comment-helpers.sh" | ||
|
|
There was a problem hiding this comment.
[critical] We ran into this exact problem with the prioritize agent (PR #35, cac8127): base composition fetches scripts as individual content-addressed blobs without their sibling directories. source "${SCRIPT_DIR}/comment-helpers.sh" will break under base composition because comment-helpers.sh won't exist at SCRIPT_DIR.
Same problem applies to python3 "${SCRIPT_DIR}/adf-to-markdown.py" further down.
The fix for prioritize was to inline the shared functions. That's not great for code reuse, but it's how the platform works today. Worth thinking about whether there's a better resolution mechanism we could pursue, but in the meantime these scripts need to be self-contained.
| local label="$1" | ||
| if [[ ! "${label}" =~ ^[a-zA-Z0-9._/:\ +\-]+$ ]]; then | ||
| echo "::warning::Refused pipeline label '${label}' -- contains invalid characters" | ||
| return 1 |
There was a problem hiding this comment.
[critical] Same base-composition issue as pre-explore.sh — source "${SCRIPT_DIR}/comment-helpers.sh" won't resolve when the harness is consumed via base: URL. See the inline comment on pre-explore.sh:27 for context.
| { | ||
| "$schema": "https://json-schema.org/draft/2020-12/schema", | ||
| "$id": "explore-result.schema.json", | ||
| "title": "Exploration Result", |
There was a problem hiding this comment.
[important] The schema is quite a bit more permissive than the prompt implies. A few things I noticed:
data_sources— the prompt says "Important: Include thedata_sourcesfield" but the schema doesn't require it- The individual confidence dimensions (
technical_landscape,related_work, etc.) are optional in the schema even though the prompt presents them as a mandatory table - No
additionalProperties: falseanywhere — the existingtriage-result.schema.jsonuses it at the top level
If the validation loop is supposed to catch malformed output, the schema needs to match what the prompt actually asks for.
| curl -sSf -X PUT \ | ||
| -H "Authorization: Basic $AUTH" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d "{\"update\":{\"labels\":[{\"add\":\"${SIGNAL_LABEL}\"}]}}" \ |
There was a problem hiding this comment.
[important] SIGNAL_LABEL is interpolated directly into JSON via shell string interpolation here. Even with validate_label_name, the regex allows spaces and backslashes which could cause JSON parsing issues. Using jq -n --arg label "$SIGNAL_LABEL" '...' to construct the payload (like pre-explore.sh does elsewhere) would eliminate the injection surface.
|
|
||
| _redact_secrets() { | ||
| if command -v fullsend >/dev/null 2>&1; then | ||
| fullsend scan output |
There was a problem hiding this comment.
[important] Two things here:
- Does
fullsend scan outputactually read from stdin? The subcommand name suggests it might scan a directory called "output" rather than reading a pipe. If so, the body gets silently lost. - The fallback (
cat) posts content with no secret scanning at all. If fullsend isn't on PATH in a misconfigured environment, secrets in the comment body go straight to Jira/GitHub.
Would it be safer to refuse to post rather than post unscanned?
| """Convert a full ADF document to Markdown.""" | ||
| if not isinstance(adf, dict): | ||
| return str(adf) if adf else "" | ||
|
|
There was a problem hiding this comment.
[minor] '""' appears twice in this condition — looks like a copy-paste duplicate. Was the second one meant to be a different value?
| validate_repo() { | ||
| local ref="$1" | ||
| local http_code | ||
| http_code=$(GIT_TERMINAL_PROMPT=0 curl -sf -o /dev/null -w "%{http_code}" \ |
There was a problem hiding this comment.
[minor] lang_ext is set here and updated based on detected language, but never referenced after this block. The find on line 437 always searches all extensions regardless. Dead code.
| source "${SCRIPT_DIR}/comment-helpers.sh" | ||
|
|
||
| echo "::notice::Pre-explore: fetching issue data (source=${ISSUE_SOURCE}, key=${ISSUE_KEY})" | ||
|
|
There was a problem hiding this comment.
[minor] (non-blocking) Under set -u, if ISSUE_SOURCE or ISSUE_KEY is unset, this line aborts with a cryptic "unbound variable" error before the helpful validation messages on lines 32-42 can run. Using ${ISSUE_SOURCE:-} here or moving the notice after validation would give better error messages.
| }' > "$WORKSPACE/issue-context.json" | ||
|
|
||
| elif [[ "${ISSUE_SOURCE}" == "github" ]]; then | ||
| if [[ -z "${REPO_FULL_NAME:-}" ]]; then |
There was a problem hiding this comment.
[minor] (non-blocking) This line uses "${GITHUB_ENV}" without the :-/dev/null fallback that lines 319 and 505 use. Running outside GitHub Actions would fail here.
| - skills/jira-routing # team-specific | ||
| env: | ||
| runner: | ||
| EXPLORE_READY_LABEL: ready-to-refine |
There was a problem hiding this comment.
[nit] (non-blocking) The example label ready-to-refine bakes in a pipeline-specific name. Since the docs emphasize labels are generic and opt-in, something like explore-complete would reinforce that.
Summary
ISSUE_SOURCE)EXPLORE_READY_LABEL/EXPLORE_NEEDS_INFO_LABELenv vars (no hardcoded refinement labels)public-researchandjira-readskills for downstream customization via harnessbase:compositionFiles added
agents/explore.mdharness/explore.yamlpolicies/explore.yamlschemas/explore-result.schema.jsonscripts/pre-explore.shscripts/post-explore.shscripts/comment-helpers.shscripts/adf-to-markdown.py,markdown-to-adf.pyskills/public-research,skills/jira-readSecurity review
ISSUE_SOURCE,ISSUE_KEY,REPO_FULL_NAME, and pipeline label namesfullsend scanruns before posting commentsCode review fixes applied
REFERENCED_REPOS_DIRto sandbox viaenv/explore.envREPO_FULL_NAMEformat in pre-scriptGITHUB_ISSUE_NUMBERfrom pre-script for GitHub runsFollow-up (separate PRs)
base:URL +jira-routingskill + Konflux pipeline labelsTest plan
fullsend agent addMade with Cursor