Skip to content

feat: add generic explore agent - #11

Closed
ascerra wants to merge 16 commits into
mainfrom
feat/add-explore-agent
Closed

feat: add generic explore agent#11
ascerra wants to merge 16 commits into
mainfrom
feat/add-explore-agent

Conversation

@ascerra

@ascerra ascerra commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a generic explore agent for gathering technical context from GitHub, Jira, the target codebase, and public web sources
  • Platform-aware pre/post scripts support both GitHub and Jira (ISSUE_SOURCE)
  • Pipeline labels are optional via EXPLORE_READY_LABEL / EXPLORE_NEEDS_INFO_LABEL env vars (no hardcoded refinement labels)
  • Includes public-research and jira-read skills for downstream customization via harness base: composition

Files added

File Purpose
agents/explore.md Agent prompt — research phases, confidence scoring, JSON output
harness/explore.yaml Sandbox config, host_files, skills, env
policies/explore.yaml Network policy (Vertex, GitHub, Jira read-only, Tavily)
schemas/explore-result.schema.json Output validation schema
scripts/pre-explore.sh Fetch Jira/GitHub issue context, clone referenced repos
scripts/post-explore.sh Attach context, sticky comment, optional labels
scripts/comment-helpers.sh Shared sticky comment + secret scanning
scripts/adf-to-markdown.py, markdown-to-adf.py Jira ADF conversion
skills/public-research, skills/jira-read Generic research skills

Security review

  • No secrets, tokens, or org-specific references committed
  • Credentials stay in runner env only (not sandbox)
  • Input validation on ISSUE_SOURCE, ISSUE_KEY, REPO_FULL_NAME, and pipeline label names
  • fullsend scan runs before posting comments

Code review fixes applied

  • Export REFERENCED_REPOS_DIR to sandbox via env/explore.env
  • Validate pipeline label env vars before GitHub/Jira mutations
  • Validate REPO_FULL_NAME format in pre-script
  • Set GITHUB_ISSUE_NUMBER from pre-script for GitHub runs

Follow-up (separate PRs)

  • konflux-ci/refinement: thin harness with base: URL + jira-routing skill + Konflux pipeline labels
  • Add test scripts for pre/post explore (similar to triage)

Test plan

  • Merge this PR
  • Register in refinement via thin harness + fullsend agent add
  • Run explore workflow in dry-run against a GitHub issue
  • Run explore against a Jira issue with credentials configured

Made with Cursor

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add generic explore agent for issue research (GitHub/Jira)

✨ Enhancement ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add an explore agent that gathers technical context for downstream workflow agents.
• Fetch issue context and referenced repos via pre-script; publish results via post-script.
• Add harness, network policy, and JSON schema validation for consistent agent output.
Diagram

graph TD
  PRE["scripts/pre-explore.sh"] --> CTX[("issue-context.json")] --> AG(["Sandbox: explore agent"]) --> OUT[("agent-result.json")]
  PRE --> TAR[("referenced-repos.tar.gz")]
  TAR --> AG
  AG --> POST["scripts/post-explore.sh"] --> GH{{"GitHub API"}}
  POST --> JIRA{{"Jira API"}}
  subgraph Legend
    direction LR
    _script["Script"] ~~~ _sb(["Sandbox"]) ~~~ _file[("Artifact")] ~~~ _ext{{"External"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use an existing ADF/Markdown library
  • ➕ Less custom parsing/formatting code to maintain
  • ➕ Potentially more complete ADF coverage (edge-case nodes)
  • ➖ Adds a dependency footprint (packaging/installation in runner/sandbox)
  • ➖ Harder to control safety constraints (URL schemes, input limits, expand-node filtering)
  • ➖ May be overkill for the limited comment formatting needs
2. Avoid cloning referenced repos; rely on GitHub API content reads
  • ➕ No git operations on the runner; simpler pre-script and less disk usage
  • ➕ Avoids tarball mounting complexity
  • ➖ API rate limits and auth constraints reduce reliability
  • ➖ Harder to do deep grep/find-style exploration across repos
  • ➖ Less performant for large repos and multi-file context gathering

Recommendation: Current approach is reasonable for a generic explore workflow: a host-side pre-script safely gathers privileged context (Jira/GitHub + public repo clones without credentials), while the sandbox stays constrained by policy and produces schema-validated JSON. Keep the custom ADF converters unless maintenance becomes burdensome; they provide explicit safety limits and predictable output for sticky comments.

Files changed (12) +2451 / -0

Enhancement (3) +913 / -0
explore.mdAdd explore agent prompt with phased research workflow and JSON output contract +382/-0

Add explore agent prompt with phased research workflow and JSON output contract

• Introduces the explore agent prompt defining research phases (issue parsing, codebase analysis, related work, web research) and explicit constraints. Specifies required env inputs from pre-script and produces a structured JSON result including confidence and data source accounting.

agents/explore.md

adf-to-markdown.pyAdd Jira ADF to Markdown converter for readable context ingestion +227/-0

Add Jira ADF to Markdown converter for readable context ingestion

• Implements a bounded-input converter from Atlassian Document Format to Markdown, supporting common nodes (headings, lists, tables, panels, code blocks) and filtering expand nodes used for agent markers/history.

scripts/adf-to-markdown.py

markdown-to-adf.pyAdd Markdown to Jira ADF converter for Jira comment posting +304/-0

Add Markdown to Jira ADF converter for Jira comment posting

• Converts markdown-style text into Jira ADF JSON with safety limits and URL scheme validation. Supports headings, lists, tables, rules, panels, and multiline handling via hardBreak nodes, plus optional wrapping of details in an expand section.

scripts/markdown-to-adf.py

Documentation (2) +203 / -0
SKILL.mdDocument Jira read-only data model available to agents +106/-0

Document Jira read-only data model available to agents

• Adds a skill doc explaining what Jira-derived data the agent can read via issue-context.json, how issue levels are interpreted, and what the agent cannot do due to sandbox constraints.

skills/jira-read/SKILL.md

SKILL.mdDocument public research techniques for GitHub and web sources +97/-0

Document public research techniques for GitHub and web sources

• Adds a skill doc with patterns for using GitHub APIs/CLI, local repo inspection, and web search for public-only research, including guidance on targeted searches and anti-patterns.

skills/public-research/SKILL.md

Other (7) +1335 / -0
explore.envExport explore runtime env vars into the sandbox +3/-0

Export explore runtime env vars into the sandbox

• Adds exported environment variables for ISSUE_CONTEXT, FULLSEND_OUTPUT_DIR, and REFERENCED_REPOS_DIR so the sandboxed agent can consistently locate inputs and outputs.

env/explore.env

explore.yamlAdd explore harness wiring (host files, env bridging, schema validation) +64/-0

Add explore harness wiring (host files, env bridging, schema validation)

• Defines the explore harness configuration, mounting required env/credentials and optional issue/repo artifacts. Wires pre/post scripts, includes public-research and jira-read skills, and enables a validation loop against the explore-result schema.

harness/explore.yaml

explore.yamlAdd sandbox policy for explore (Vertex, GitHub, Jira read-only, web search) +82/-0

Add sandbox policy for explore (Vertex, GitHub, Jira read-only, web search)

• Introduces a network/filesystem policy allowing necessary endpoints (Anthropic/Google APIs, GitHub, Jira read-only, Tavily) while keeping the sandbox locked down with a minimal read/write filesystem set.

policies/explore.yaml

explore-result.schema.jsonDefine JSON schema for explore agent output +113/-0

Define JSON schema for explore agent output

• Adds a Draft-07 JSON schema that validates the explore agent result structure, including input metadata, technical landscape, related work, confidence scoring, optional gaps/data_sources, and a bounded summary field.

schemas/explore-result.schema.json

comment-helpers.shAdd shared sticky comment helpers for GitHub and Jira +188/-0

Add shared sticky comment helpers for GitHub and Jira

• Provides reusable functions to post/update a single sticky comment per agent, delegating GitHub to fullsend post-comment and implementing Jira sticky history with expand nodes. Includes secret redaction via fullsend scan and label add/remove helpers.

scripts/comment-helpers.sh

post-explore.shPublish explore results (attachment, sticky summary, optional labels) +222/-0

Publish explore results (attachment, sticky summary, optional labels)

• Reads the latest iteration output, validates JSON, and copies it to exploration_context.json. Attaches results to Jira, posts a sticky summary comment to GitHub/Jira, and optionally applies configured pipeline labels based on a confidence threshold with label name validation.

scripts/post-explore.sh

pre-explore.shFetch issue context and pre-clone referenced repos for deep exploration +663/-0

Fetch issue context and pre-clone referenced repos for deep exploration

• Implements platform-aware context fetching for Jira (REST + ADF conversion + hierarchy/linked issues) and GitHub (gh issue view + sub-issues). Discovers referenced repos from issue text/ADF/previous explore results, validates via GitHub API, shallow-clones public repos with credential helpers disabled, generates REPO-INDEX.md navigation manifests, and packages repos into a tarball for sandbox mounting.

scripts/pre-explore.sh

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 1, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:29 PM UTC · Completed 8:39 PM UTC
Commit: c338d84 · View workflow run →

@qodo-code-review

qodo-code-review Bot commented Jul 1, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Missing label auto-created ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
post-explore.sh adds SIGNAL_LABEL via the GitHub labels API without checking that the label
exists first, so a misconfigured env var can create unwanted labels or silently drift repo labeling
conventions.
Code

scripts/post-explore.sh[R113-118]

+# --- Add label when configured ---
+if [[ -n "$SIGNAL_LABEL" ]]; then
+  if [[ -n "${GITHUB_ISSUE_NUMBER:-}" && "${GITHUB_ISSUE_NUMBER}" != "N/A" ]]; then
+    gh api "repos/${REPO_FULL_NAME}/issues/${GITHUB_ISSUE_NUMBER}/labels" \
+      -f "labels[]=${SIGNAL_LABEL}" --silent 2>/dev/null || true
+    echo "::notice::Added label '${SIGNAL_LABEL}' to GitHub issue #${GITHUB_ISSUE_NUMBER}"
Evidence
Explore applies a label directly with gh api .../labels -f labels[]=... and ignores errors,
whereas triage explicitly fetches existing labels and skips any that don’t exist to avoid
auto-creating removed/unknown labels.

scripts/post-explore.sh[113-119]
scripts/post-triage.sh[310-343]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`scripts/post-explore.sh` applies pipeline labels without verifying they exist. This can unintentionally create labels (repo metadata drift) or mask configuration mistakes.

### Issue Context
`scripts/post-triage.sh` already implements a safe pattern: fetch repo labels once and skip non-existent labels to avoid accidental creation.

### Fix Focus Areas
- scripts/post-explore.sh[113-119]
- scripts/post-triage.sh[310-343]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Valid labels rejected ✓ Resolved 🐞 Bug ≡ Correctness
Description
post-explore.sh hard-fails when EXPLORE_READY_LABEL / EXPLORE_NEEDS_INFO_LABEL contains
characters like / or : even though those are used elsewhere in this repo’s label conventions,
which can prevent posting the explore comment and Jira attachment for otherwise successful runs.
Code

scripts/post-explore.sh[R19-25]

+validate_label_name() {
+  local label="$1"
+  if [[ ! "$label" =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]]; then
+    echo "ERROR: invalid label name: ${label}"
+    exit 1
+  fi
+}
Evidence
post-explore.sh rejects anything outside [a-zA-Z0-9._-] and exits, while the existing triage
pipeline explicitly allows / and : in label names and tests adding a label with /
(area/api).

scripts/post-explore.sh[19-25]
scripts/post-triage.sh[326-330]
scripts/post-triage-test.sh[301-304]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`scripts/post-explore.sh` validates optional pipeline labels with a regex that rejects common label characters (e.g. `/`, `:`). When a rejected-but-legitimate label is configured, the script `exit 1`s before posting the sticky comment/attachment.

### Issue Context
Other pipeline code in this repo supports labels containing `/` (example: `area/api`) and uses a broader allowlist.

### Fix Focus Areas
- scripts/post-explore.sh[19-25]
- scripts/post-explore.sh[101-110]
- scripts/post-triage.sh[326-330]
- scripts/post-triage-test.sh[301-304]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

3. History cap off-by-one ✓ Resolved 🐞 Bug ☼ Reliability
Description
comment-helpers.sh intends to cap Jira sticky-comment history via _CH_MAX_HISTORY, but it keeps
up to _CH_MAX_HISTORY old “Previous” expands and then prepends a new one, resulting in
_CH_MAX_HISTORY + 1 history entries.
Code

scripts/comment-helpers.sh[R70-92]

+  old_current=$(echo "$old_body" | jq '[.content[]? | select(.type != "expand")]')
+  old_history=$(echo "$old_body" | jq --argjson max "$_CH_MAX_HISTORY" \
+    '[.content[]? | select(.type == "expand" and (.attrs.title // "" | startswith("Previous")))] | .[:$max]')
+
+  timestamp=$(date -u +"%b %d, %H:%M UTC")
+
+  history_entry=$(jq -n --argjson content "$old_current" --arg title "Previous · ${timestamp}" \
+    '{"type": "expand", "attrs": {"title": $title}, "content": $content}')
+
+  final_adf=$(jq -n \
+    --argjson new_content "$new_content" \
+    --argjson history_entry "$history_entry" \
+    --argjson old_history "$old_history" \
+    --arg marker "$_CH_MARKER" \
+    '{body: {type: "doc", version: 1, content:
+      ($new_content
+       + [{"type": "rule"}]
+       + [$history_entry]
+       + $old_history
+       + [{"type": "expand", "attrs": {"title": ""}, "content":
+           [{"type": "paragraph", "content": [{"type": "text", "text": $marker}]}]}]
+      )
+    }}')
Evidence
The script sets _CH_MAX_HISTORY=3, slices old_history to that max, then appends history_entry
plus old_history into the final ADF, yielding 4 history expands when max is 3.

scripts/comment-helpers.sh[28-29]
scripts/comment-helpers.sh[70-92]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The Jira sticky-comment history logic is off by one: it retains up to `_CH_MAX_HISTORY` prior history entries and then adds a new history entry, so the total history entries exceed the intended cap.

### Issue Context
If the intended meaning of `_CH_MAX_HISTORY` is “total previous snapshots to keep”, slice old history to `max-1` before prepending the new snapshot (or rename the variable/comment to reflect the current behavior).

### Fix Focus Areas
- scripts/comment-helpers.sh[28-29]
- scripts/comment-helpers.sh[70-92]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread scripts/post-explore.sh
Comment thread scripts/post-explore.sh Outdated
Comment thread scripts/comment-helpers.sh Outdated
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [logic-error] scripts/pre-explore.sh:172, scripts/pre-explore.sh:253 — The pre-script fetches Jira children (line 172) and team usage (line 253) via ${JIRA_BASE}/search?jql=... which resolves to /rest/api/3/search?jql=.... The same script's own jira-api-hints.json (written at line 409) documents that /rest/api/3/search returns HTTP 410 (removed by Atlassian) and that /rest/api/3/search/jql must be used instead. The correct endpoint is already used in the hints probe at line 400 and in the agent prompt at line 548. Both deprecated calls fail silently because errors are swallowed by 2>/dev/null with || echo '[]' / || echo '{...}' fallbacks. The issue-context.json delivered to the agent will have empty children and team_usage, depriving the explore agent of existing decomposition context.
    Remediation: Replace both occurrences of ${JIRA_BASE}/search?jql=... with GET requests to ${JIRA_BASE}/search/jql using --get --data-urlencode 'jql=...' query parameters, matching the pattern already established in the jira-api-hints probe section of the same script.

  • [protected-path] agents/explore.md, harness/explore.yaml, policies/explore.yaml, scripts/* (5 files), skills/* (2 files) — This PR modifies 10 files under protected paths (agents/, harness/, policies/, scripts/, skills/). No linked issue provides authorization for these changes. Human approval is required for all protected-path modifications regardless of context.

Medium

  • [logic-error] skills/jira-read/SKILL.md:108, harness/explore.yaml:51, agents/explore.md:548 — The jira-read SKILL.md states "The agent cannot call the Jira API directly (sandbox network policy blocks it)" but this is factually incorrect. The network policy (policies/explore.yaml) allows *.atlassian.net:443 for curl/node, the harness (harness/explore.yaml) passes JIRA_HOST/JIRA_EMAIL/JIRA_API_TOKEN into the sandbox env, and the agent prompt instructs the agent to use curl+Basic auth for Jira queries. The docs/explore.md was updated in this revision to correctly describe credential flow, but skills/jira-read/SKILL.md was not updated to match. This contradictory instruction will cause the LLM agent to incorrectly believe it cannot query Jira.
    Remediation: Update the "What the agent CANNOT do" section in skills/jira-read/SKILL.md to state that the agent CAN make read-only Jira API calls using injected credentials, but CANNOT create, update, or comment on Jira issues.

Low

8 low-severity findings
  • [logic-error] skills/jira-read/SKILL.md:24 — The SKILL.md documents the issue-context.json schema using field name key for issue identifiers (root, parent, children, linked_issues) and project.key, but the pre-script (scripts/pre-explore.sh) writes issue_id and project.id. An agent consulting this skill documentation would use the wrong field names.
    Remediation: Update the SKILL.md JSON example to use issue_id instead of key throughout, and id instead of key for the project object.

  • [logic-error] scripts/post-explore.sh:249 — On re-runs where confidence crosses the threshold, the previously-applied pipeline label is never removed. The script only adds the new SIGNAL_LABEL but never removes the opposite label. Both labels could be present simultaneously.
    Remediation: Before adding SIGNAL_LABEL, remove the opposite label using the GitHub API and Jira labels update.

  • [GHA-workflow-command-injection] scripts/pre-explore.sh:101ISSUE_SOURCE and ISSUE_KEY interpolated into ::notice:: BEFORE input validation at lines 104–117. These are typically set by the workflow caller (trusted), but the validation-after-emission ordering is a defense-in-depth gap.
    Remediation: Move the ::notice:: to after the input validation block.

  • [remote-code-fetch] scripts/pre-explore.sh:54 — The _resolve_companion function fetches and executes remote shell scripts based on a URL read from metadata.json. The origin URL is validated only with a loose prefix check ($origin == http*://*). Fetched .sh files are made executable and later sourced.

  • [secret-exposure] scripts/comment-helpers.sh:127 — When the fullsend CLI is not on PATH, _redact_secrets falls back to cat, posting comment content without secret scanning. A ::warning:: is logged but the comment is still posted.

  • [edge-case] scripts/adf-to-markdown.py:81render_table calculates separator column count by counting | in the first rendered row. Cell content containing literal | inflates the count, producing extra separator columns.

  • [edge-case] scripts/adf-to-markdown.py:22render_marks applies marks in input array order. If code precedes strong, bold asterisks render literally inside the code span.

  • [missing-agent-entry] README.md:7 — The agents table in README.md lists 6 agents but does not include the new Explore agent added by this PR.
    Remediation: Add a row for the Explore agent to the agents table.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run

Review

Findings

High

  • [logic-error] scripts/pre-explore.sh:172, scripts/pre-explore.sh:253 — The pre-script fetches Jira children (line 172) and team usage (line 253) via ${JIRA_BASE}/search?jql=... which resolves to /rest/api/3/search?jql=.... The agent prompt (line 549) and the jira-api-hints.json file this very script writes (line 410) both document that /rest/api/3/search returns HTTP 410 (removed by Atlassian) and that /rest/api/3/search/jql must be used instead. On Jira Cloud instances where this endpoint is retired, both queries will silently fail because errors are swallowed by || echo '[]' (children) and || echo '{...}' (team_usage) fallbacks. The issue-context.json delivered to the agent will have empty children and team_usage, depriving the explore agent of existing decomposition context.
    Remediation: Replace ${JIRA_BASE}/search?jql= with GET requests to ${JIRA_BASE}/search/jql using --get --data-urlencode 'jql=...' query parameters, matching the pattern documented in the jira-api-hints probe section of the same script.

  • [protected-path] agents/explore.md, harness/explore.yaml, policies/explore.yaml, scripts/* (5 files), skills/* (2 files) — This PR modifies 10 files under protected paths (agents/, harness/, policies/, scripts/, skills/). No linked issue provides authorization for these changes. Human approval is required for all protected-path modifications regardless of context.

Medium

  • [logic-error] skills/jira-read/SKILL.md:108, harness/explore.yaml:56, docs/explore.md:9 — Multiple documentation files contradict the actual sandbox configuration regarding Jira API access: (1) skills/jira-read/SKILL.md line 108 states "The agent cannot call the Jira API directly (sandbox network policy blocks it)"; (2) docs/explore.md line 9 states "Credentials stay on the runner — they never enter the sandbox." Both claims are contradicted by policies/explore.yaml which allows *.atlassian.net:443 for curl, harness/explore.yaml which passes JIRA_HOST/JIRA_EMAIL/JIRA_API_TOKEN into the sandbox, and agents/explore.md which instructs the agent to use curl+Basic auth for related-project queries. See also: [secret-exposure] finding at harness/explore.yaml:56.
    Remediation: Update skills/jira-read/SKILL.md and docs/explore.md to accurately state that the agent has read-only Jira API access via curl when credentials are configured, and that write operations are handled by post-scripts.

Low

9 low-severity findings
  • [logic-error] skills/jira-read/SKILL.md:24 — The SKILL.md documents the issue-context.json schema using field name key for issue identifiers (root, parent, children, linked_issues) and project.key, but the pre-script (scripts/pre-explore.sh) writes issue_id and project.id. An agent consulting this skill documentation would use the wrong field names.
    Remediation: Update the SKILL.md JSON example to use issue_id instead of key throughout, and id instead of key for the project object.

  • [logic-error] scripts/post-explore.sh:249 — On re-runs where confidence crosses the threshold, the previously-applied pipeline label is never removed. The script only adds the new SIGNAL_LABEL but never calls remove_label for the opposite label. Both labels could be present simultaneously.
    Remediation: Before adding SIGNAL_LABEL, remove the opposite label using the existing remove_label helper.

  • [GHA-workflow-command-injection] scripts/post-explore.sh:96validate_label_name interpolates the label into a ::warning:: message BEFORE the regex check returns. If the regex rejects the label, the warning has already been emitted with unsanitized content.
    Remediation: Sanitize the label value before interpolating into the ::warning:: message.

  • [GHA-workflow-command-injection] scripts/pre-explore.sh:101ISSUE_SOURCE and ISSUE_KEY interpolated into ::notice:: BEFORE input validation at lines 104–117. These are typically set by the workflow caller (trusted), but the validation-after-emission ordering is a defense-in-depth gap.
    Remediation: Move the ::notice:: to after the input validation block.

  • [remote-code-fetch] scripts/pre-explore.sh:54 — The _resolve_companion function fetches and executes remote shell scripts based on a URL read from metadata.json. The origin URL is validated only with a loose prefix check ($origin == http*://*). Fetched .sh files are made executable and later sourced.
    Remediation: Add domain/host allowlisting for the origin URL (e.g., restrict to raw.githubusercontent.com/fullsend-ai/).

  • [secret-exposure] scripts/comment-helpers.sh:127 — When the fullsend CLI is not on PATH, _redact_secrets falls back to cat, posting comment content without secret scanning. A ::warning:: is logged but the comment is still posted.

  • [edge-case] scripts/adf-to-markdown.py:81render_table calculates separator column count by counting | in the first rendered row. Cell content containing literal | inflates the count, producing extra separator columns.

  • [edge-case] scripts/adf-to-markdown.py:25render_marks applies marks in input array order. If code precedes strong, bold asterisks render literally inside the code span.

  • [missing-agent-entry] README.md:8, docs/README.md:5 — The agents tables in README.md and docs/README.md list 6 agents but do not include the new Explore agent added by this PR.
    Remediation: Add a row for the Explore agent to both agents tables.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (2)

Review

Findings

High

  • [logic-error] scripts/pre-explore.sh:172, scripts/pre-explore.sh:253 — The pre-script fetches Jira children (line 172) and team usage (line 253) via ${JIRA_BASE}/search?jql=... which resolves to /rest/api/3/search?jql=.... The agent prompt (line 505) and the jira-api-hints.json file this very script writes (line 402) both document that /rest/api/3/search returns HTTP 410 (removed by Atlassian) and that /rest/api/3/search/jql must be used instead. On Jira Cloud instances where this endpoint is retired, both queries will silently fail because errors are swallowed by || echo '[]' (children) and || echo '{...}' (team_usage) fallbacks. The issue-context.json delivered to the agent will have empty children and team_usage, depriving the explore agent of existing decomposition context.
    Remediation: Replace ${JIRA_BASE}/search?jql= with GET requests to ${JIRA_BASE}/search/jql using --get --data-urlencode 'jql=...' query parameters, matching the pattern documented in the jira-api-hints probe section of the same script.

  • [protected-path] agents/explore.md, harness/explore.yaml, policies/explore.yaml, scripts/* (5 files), skills/* (2 files) — This PR modifies 13 files under protected paths (agents/, harness/, policies/, scripts/, skills/). No linked issue provides authorization for these changes. Human approval is required for all protected-path modifications regardless of context.

Medium

  • [logic-error] skills/jira-read/SKILL.md:108 — The SKILL.md states "The agent cannot call the Jira API directly (sandbox network policy blocks it)" but this is contradicted by three other files in this PR: (1) policies/explore.yaml lines 55–65 allow *.atlassian.net:443 for curl, (2) harness/explore.yaml lines 56–58 pass JIRA_HOST/JIRA_EMAIL/JIRA_API_TOKEN into the sandbox, and (3) agents/explore.md lines 499–521 instruct the agent to use curl+Basic auth for Jira queries. An agent consulting this skill documentation would incorrectly believe it cannot query Jira and skip related-project lookups.
    Remediation: Update line 108 to reflect that the agent has read-only Jira API access via curl when credentials are configured, and that write operations are handled by post-scripts.

  • [logic-error] skills/jira-read/SKILL.md:24 — The SKILL.md documents the issue-context.json schema using field name key for issue identifiers (root, parent, children, linked_issues) and project.key, but the pre-script (scripts/pre-explore.sh) writes issue_id (lines 169, 173, 229, 263–284) and project.id (line 298). An agent reading this skill documentation would use the wrong field names (e.g., jq '.key' instead of jq '.issue_id'), getting null values.
    Remediation: Update the SKILL.md JSON example to use issue_id instead of key throughout, and id instead of key for the project object.

  • [secret-exposure] harness/explore.yaml:56 — The harness config passes JIRA_EMAIL, JIRA_HOST, and JIRA_API_TOKEN into the sandbox environment (env.sandbox block, lines 56–58). The docs/explore.md (line 9) states "Credentials stay on the runner — they never enter the sandbox" which is a factual contradiction. The network policy enforces read-only access, which mitigates write-side abuse, but the documentation is misleading. See also: [logic-error] finding at skills/jira-read/SKILL.md:108.
    Remediation: Update docs/explore.md to accurately reflect that Jira credentials enter the sandbox for read-only related-project lookups.

  • [remote-code-fetch] scripts/pre-explore.sh:54 — The _resolve_companion function (lines 33–93, duplicated in scripts/post-explore.sh) fetches and executes remote shell scripts based on a URL read from metadata.json. The origin URL is validated only with a loose prefix check ($origin == http*://*). Fetched .sh files are made executable and later sourced. If an attacker can control metadata.json, they can redirect companion fetches to an arbitrary server and achieve code execution. The sha256sum-based temp directory provides some isolation, but the origin URL itself is not constrained.
    Remediation: Add domain/host allowlisting for the origin URL (e.g., restrict to raw.githubusercontent.com/fullsend-ai/). Consider integrity verification for fetched companions.

Low

10 low-severity findings
  • [logic-error] scripts/post-explore.sh:243 — On re-runs where confidence crosses the threshold, the previously-applied pipeline label is never removed. The script only adds the new SIGNAL_LABEL but never calls remove_label for the opposite label. Both labels could be present simultaneously.
    Remediation: Before adding SIGNAL_LABEL, remove the opposite label using the existing remove_label helper.

  • [GHA-workflow-command-injection] scripts/post-explore.sh:130DISPOSITION extracted from untrusted agent output via jq -r, interpolated into ::notice:: before schema validation. The exploitable GHA workflow commands (set-env, add-path) have been disabled since 2020, so injection impact is limited to informational annotations, but the unsanitized interpolation is a defense-in-depth gap.
    Remediation: Validate DISPOSITION matches the expected enum before use in workflow commands.

  • [GHA-workflow-command-injection] scripts/post-explore.sh:95validate_label_name interpolates the label into a ::warning:: message BEFORE the regex check returns. If the regex rejects the label, the warning has already been emitted with unsanitized content.
    Remediation: Sanitize the label value before interpolating into the ::warning:: message.

  • [json-injection] scripts/post-explore.sh:261SIGNAL_LABEL interpolated directly into a JSON string in a curl -d payload. The validate_label_name allowlist excludes quotes/backslashes so injection is currently blocked, but the shell-interpolation-into-JSON pattern is brittle.
    Remediation: Use jq to construct the JSON payload.

  • [GHA-workflow-command-injection] scripts/pre-explore.sh:101ISSUE_SOURCE and ISSUE_KEY interpolated into ::notice:: BEFORE input validation at lines 104–117. These are typically set by the workflow caller (trusted), but the validation-after-emission ordering is a defense-in-depth gap.
    Remediation: Move the ::notice:: to after the input validation block.

  • [secret-exposure] scripts/comment-helpers.sh:127 — When the fullsend CLI is not on PATH, _redact_secrets falls back to cat, posting comment content without secret scanning. A ::warning:: is logged but the comment is still posted.

  • [edge-case] scripts/adf-to-markdown.py:81render_table calculates separator column count by counting | in the first rendered row. Cell content containing literal | inflates the count, producing extra separator columns.

  • [edge-case] scripts/adf-to-markdown.py:25render_marks applies marks in input array order. If code precedes strong, bold asterisks render literally inside the code span.

  • [missing-agent-entry] README.md:8 — The agents table in README.md lists 6 agents but does not include the new Explore agent added by this PR.
    Remediation: Add a row for the Explore agent to the agents table.

  • [missing-agent-entry] docs/README.md:5 — The agents reference table in docs/README.md lists 6 agents but does not include the new Explore agent, even though docs/explore.md has been added by this PR.
    Remediation: Add a row for the Explore agent in docs/README.md.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (3)

Review

Findings

High

  • [protected-path] agents/explore.md, harness/explore.yaml, policies/explore.yaml, scripts/* (5 files), skills/* (2 files) — This PR modifies 10 files under protected paths (agents/, harness/, policies/, scripts/, skills/). No linked issue provides authorization for these changes. Human approval is required for all protected-path modifications regardless of context.

  • [logic-error] scripts/pre-explore.sh:173, scripts/pre-explore.sh:254 — The pre-script fetches Jira children (line 173) and team usage (line 254) via ${JIRA_BASE}/search?jql=... which resolves to /rest/api/3/search?jql=.... The agent prompt and the jira-api-hints.json file this very script writes both document that /rest/api/3/search returns HTTP 410 (removed by Atlassian) and that /rest/api/3/search/jql must be used instead. On Jira Cloud instances where this endpoint is retired, both queries will silently fail because errors are swallowed by || echo '[]' fallbacks. The issue-context.json delivered to the agent will have empty children and team_usage, depriving the explore agent of existing decomposition context.
    Remediation: Replace ${JIRA_BASE}/search?jql= with ${JIRA_BASE}/search/jql using GET with --get --data-urlencode 'jql=...' query parameters, matching the pattern documented in the jira-api-hints probe section of the same script.

Medium

  • [logic-error] skills/jira-read/SKILL.md:109 — The SKILL.md claims "The agent cannot call the Jira API directly (sandbox network policy blocks it)" but this contradicts the actual sandbox configuration. The network policy (policies/explore.yaml) allows *.atlassian.net:443 for curl, the harness passes JIRA_HOST/JIRA_EMAIL/JIRA_API_TOKEN into the sandbox, and the agent prompt instructs the agent to use curl+Basic auth for related project queries. See also: [secret-exposure] finding at harness/explore.yaml:56.
    Remediation: Update the SKILL.md to accurately reflect that the agent has read-only Jira API access via curl when credentials are configured in the sandbox.

  • [secret-exposure] harness/explore.yaml:56 — The harness config passes JIRA_EMAIL, JIRA_HOST, and JIRA_API_TOKEN into the sandbox environment (env.sandbox block). The docs/explore.md (line 9) states "Credentials stay on the runner — they never enter the sandbox" which is a factual contradiction. The agent prompt itself acknowledges the credentials are in-sandbox and instructs their use. See also: [logic-error] finding at skills/jira-read/SKILL.md:109.
    Remediation: Update docs/explore.md to accurately reflect that Jira credentials do enter the sandbox for read-only related-project lookups.

Low

6 low-severity findings
  • [logic-error] agents/explore.md:50 — The duplicate-gate.json file path consistency between pre-script and agent prompt now matches (/tmp/workspace/), but this relies on /tmp being a shared mount between the host pre-script and the sandbox. If the sandbox has isolated /tmp, the file never reaches the agent.

  • [logic-error] scripts/post-explore.sh:243 — On re-runs where confidence crosses the threshold, the previously-applied pipeline label is never removed. The script only adds the new SIGNAL_LABEL but never calls remove_label for the opposite label. Both labels could be present simultaneously.
    Remediation: Before adding SIGNAL_LABEL, remove the opposite label using the existing remove_label helper.

  • [edge-case] scripts/adf-to-markdown.py:81render_table calculates separator column count by counting | in the first rendered row. Cell content containing literal | inflates the count, producing a separator row with more columns than actual data columns.

  • [edge-case] scripts/adf-to-markdown.py:25render_marks applies marks in input array order. If code precedes strong, bold asterisks render literally inside the code span.

  • [missing-agent-entry] README.md:8 — The agents table in README.md lists 6 agents but does not include the new Explore agent added by this PR.
    Remediation: Add a row for the Explore agent to the agents table.

  • [missing-agent-entry] docs/README.md:5 — The agents reference table in docs/README.md lists 6 agents but does not include the new Explore agent, even though docs/explore.md has been added by this PR.
    Remediation: Add a row for the Explore agent in docs/README.md.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (4)

Review

Findings

High

  • [protected-path] agents/explore.md, harness/explore.yaml, policies/explore.yaml, scripts/* (5 files), skills/* (2 files) — This PR modifies 10 files under protected paths (agents/, harness/, policies/, scripts/, skills/). No linked issue provides authorization for these changes. Human approval is required for all protected-path modifications regardless of context.

  • [logic-error] scripts/pre-explore.sh:173, scripts/pre-explore.sh:254 — The pre-script fetches Jira children (line 173) and team usage (line 254) via ${JIRA_BASE}/search?jql=... which resolves to /rest/api/3/search?jql=.... The agent prompt and the jira-api-hints.json file this very script writes both document that /rest/api/3/search returns HTTP 410 (removed by Atlassian) and that /rest/api/3/search/jql must be used instead. On Jira Cloud instances where this endpoint is retired, both queries will silently fail because errors are swallowed by 2>/dev/null and || echo '[]'. The issue-context.json delivered to the agent will have empty children and team_usage, depriving the explore agent of existing decomposition context.
    Remediation: Replace ${JIRA_BASE}/search?jql= with ${JIRA_BASE}/search/jql using GET with --get --data-urlencode 'jql=...' query parameters, matching the pattern documented in the jira-api-hints probe section of the same script.

Medium

  • [logic-error] agents/explore.md:56, harness/explore.yaml:34 — The harness maps duplicate-gate.json to sandbox path /sandbox/workspace/duplicate-gate.json (harness line 34), but the agent prompt hardcodes the path as /tmp/workspace/duplicate-gate.json in its Phase 0 bash example (agent line 56). Inside the sandbox, the file exists at /sandbox/workspace/ but the agent looks in /tmp/workspace/, so the cat finds nothing, the error is swallowed by 2>/dev/null, and the agent never sees the duplicate-gate data. This breaks the duplicate-override flow.
    Remediation: Either change the harness dest for duplicate-gate.json to /tmp/workspace/duplicate-gate.json (matching issue-context.json's pattern), or update the agent prompt's Phase 0 bash example to reference /sandbox/workspace/duplicate-gate.json.

  • [logic-error] skills/jira-read/SKILL.md:108 — The SKILL.md claims "The agent cannot call the Jira API directly (sandbox network policy blocks it)" but this contradicts the actual sandbox configuration. The network policy (policies/explore.yaml) allows *.atlassian.net:443 for curl, the harness passes JIRA_HOST/JIRA_EMAIL/JIRA_API_TOKEN into the sandbox, and the agent prompt instructs the agent to use curl+Basic auth for related project queries.
    Remediation: Update the SKILL.md to accurately reflect that the agent has read-only Jira API access via curl when credentials are configured in the sandbox.

  • [secret-exposure] harness/explore.yaml:56 — The harness config passes JIRA_EMAIL, JIRA_HOST, and JIRA_API_TOKEN into the sandbox environment (env.sandbox block, lines 56-58). The docs/explore.md (line 9) states "Credentials stay on the runner — they never enter the sandbox" which is a factual contradiction. The agent prompt itself acknowledges the credentials are in-sandbox and instructs their use.
    Remediation: Either remove JIRA_* credentials from env.sandbox and have the pre-script fetch all needed Jira data, or update docs/explore.md to accurately reflect that Jira credentials do enter the sandbox.

Low

5 low-severity findings
  • [logic-error] scripts/post-explore.sh:244 — On re-runs where confidence crosses the threshold, the previously-applied pipeline label is never removed. The script only adds the new SIGNAL_LABEL but never calls remove_label for the opposite label. Both labels could be present simultaneously. Pipeline labels are opt-in and this edge case is infrequent.
    Remediation: Before adding SIGNAL_LABEL, remove the opposite label using the existing remove_label helper.

  • [missing-agent-entry] README.md:8 — The agents table in README.md lists 6 agents but does not include the new Explore agent added by this PR.
    Remediation: Add a row for the Explore agent to the agents table.

  • [injection-vuln] scripts/post-explore.sh:262 — Jira label update uses string interpolation for JSON. validate_label_name regex prevents JSON-breaking characters, but jq construction would be more robust.
    Remediation: Use jq to construct the JSON payload.

  • [edge-case] scripts/adf-to-markdown.py:82render_table calculates separator column count by counting | in the first rendered row. Cell content containing literal | inflates the count, producing a separator row with more columns than actual data columns.

  • [edge-case] scripts/adf-to-markdown.py:25render_marks applies marks in input array order. If strong precedes code, bold asterisks render literally inside the code span.


Labels: PR adds new agent with sandbox credential handling and has security-relevant findings


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (5)

Review

Findings

High

  • [protected-path] agents/explore.md, harness/explore.yaml, policies/explore.yaml, scripts/* (5 files), skills/* (2 files) — This PR modifies 10 files under protected paths (agents/, harness/, policies/, scripts/, skills/). No linked issue provides authorization for these changes. Human approval is required for all protected-path modifications regardless of context.

  • [logic-error] scripts/pre-explore.sh:158 — The pre-script fetches Jira children and team usage via ${JIRA_BASE}/search?jql=... which resolves to /rest/api/3/search?jql=.... The agent prompt (line 426) and the jira-api-hints.json file this very script writes both document that /rest/api/3/search returns HTTP 410 (removed by Atlassian) and that /rest/api/3/search/jql must be used instead. On Jira Cloud instances where this endpoint is retired, both the CHILDREN_JSON query (line 158, fetching child issues) and the TEAM_USAGE query (line 253, sampling project conventions) will silently fail because errors are swallowed by 2>/dev/null and || echo '[]'. The issue-context.json delivered to the agent will have empty children and team_usage, depriving the explore agent of existing decomposition context.
    Remediation: Replace ${JIRA_BASE}/search?jql= with ${JIRA_BASE}/search/jql using GET with --data-urlencode 'jql=...' query parameters, matching the pattern already used in the jira-api-hints probe at the bottom of the same script.

Medium

  • [logic-error] scripts/post-explore.sh:196 — On re-runs where confidence crosses the threshold, the previously-applied pipeline label is never removed. The script only adds the new SIGNAL_LABEL but never calls remove_label for the opposite label. Both EXPLORE_READY_LABEL and EXPLORE_NEEDS_INFO_LABEL could be present simultaneously, sending contradictory signals to downstream stages. The remove_label helper exists in comment-helpers.sh but is unused here.
    Remediation: Before adding SIGNAL_LABEL, remove the opposite label. Use the existing remove_label helper from comment-helpers.sh for GitHub issues. For Jira, add a {"remove": "..."} operation alongside the {"add": "..."} in the labels update payload.

  • [secret-exposure] harness/explore.yaml:61 — The harness config passes JIRA_EMAIL, JIRA_HOST, and JIRA_API_TOKEN into the sandbox environment (env.sandbox block, lines 61-68). The docs/explore.md (line 9) states "Credentials stay on the runner — they never enter the sandbox" which is a factual contradiction. The agent prompt itself (agents/explore.md, "Jira related projects" section) acknowledges the credentials are in-sandbox and instructs their use. While the network policy restricts the sandbox to *.atlassian.net read-only, the documentation is misleading.
    Remediation: Either remove JIRA_* credentials from env.sandbox and have the pre-script fetch all needed Jira data, or update docs/explore.md to accurately reflect that Jira credentials do enter the sandbox.

Low

5 low-severity findings
  • [missing-agent-entry] README.md:8 — The agents table in README.md lists 6 agents (Triage, Code, Review, Fix, Prioritize, Retro) but does not include the new Explore agent added by this PR.
    Remediation: Add a row for the Explore agent to the agents table.

  • [injection-vuln] scripts/post-explore.sh:214 — Jira label update uses string interpolation for JSON. validate_label_name regex prevents JSON-breaking characters, but jq construction would be more robust.
    Remediation: Use jq to construct the JSON payload.

  • [edge-case] scripts/adf-to-markdown.py:82render_table calculates separator column count by counting | in the first rendered row. Cell content containing literal | inflates the count, producing a separator row with more columns than actual data columns.

  • [edge-case] scripts/adf-to-markdown.py:25render_marks applies marks in input array order. If strong precedes code, bold asterisks render literally inside the code span.

  • [edge-case] scripts/comment-helpers.sh:125_redact_secrets calls fullsend scan output as a pipe filter without an explicit stdin flag. If the subcommand does not read stdin, the comment body will be silently discarded.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (6)

Review

Findings

High

  • [protected-path] agents/explore.md, harness/explore.yaml, policies/explore.yaml, scripts/* (5 files), skills/* (2 files) — This PR modifies 11 files under protected paths (agents/, harness/, policies/, scripts/, skills/). No linked issue provides authorization for these changes. Human approval is required for all protected-path modifications regardless of context.

Medium

  • [logic-error] scripts/post-explore.sh:196 — On re-runs where confidence crosses the threshold, the previously-applied pipeline label is never removed. The script only adds the new SIGNAL_LABEL but never calls remove_label for the opposite label. Both EXPLORE_READY_LABEL and EXPLORE_NEEDS_INFO_LABEL could be present simultaneously, sending contradictory signals to downstream stages. The remove_label helper exists in comment-helpers.sh but is unused here.
    Remediation: Before adding SIGNAL_LABEL, remove the opposite label. Use the existing remove_label helper from comment-helpers.sh for GitHub issues. For Jira, add a {"remove": "..."} operation alongside the {"add": "..."} in the labels update payload.

  • [secret-exposure] harness/explore.yaml:61 — The harness config passes JIRA_EMAIL, JIRA_HOST, and JIRA_API_TOKEN into the sandbox environment (env.sandbox block, lines 61-68). The docs/explore.md (line 9) states "Credentials stay on the runner — they never enter the sandbox" which is a factual contradiction. While the network policy restricts the sandbox to *.atlassian.net read-only, the credentials are present inside the sandbox and the agent prompt instructs their use for related-project queries.
    Remediation: Either remove JIRA_* credentials from env.sandbox and have the pre-script fetch all needed Jira data, or update docs/explore.md to accurately reflect that Jira credentials do enter the sandbox.

  • [stale-doc] README.md:8 — The agents table in README.md lists existing agents but does not include the new Explore agent added by this PR. The table is the primary entry point for understanding available agents.
    Remediation: Add a row for the Explore agent to the agents table.

Low

6 low-severity findings
  • [injection-vuln] scripts/post-explore.sh:214 — Jira label update uses string interpolation for JSON. validate_label_name regex prevents JSON-breaking characters, but jq construction would be more robust.
    Remediation: Use jq to construct the JSON payload.

  • [naming-convention] schemas/explore-result.schema.json:4 — Schema title Exploration Result deviates from the {Agent} Agent Result pattern used by all other schemas (Triage Agent Result, Review Agent Result, etc.).
    Remediation: Change title to Explore Agent Result.

  • [edge-case] scripts/adf-to-markdown.py:82render_table calculates separator column count by counting | in the first rendered row. Cell content containing literal | inflates the count, producing a separator row with more columns than actual data columns.

  • [edge-case] scripts/adf-to-markdown.py:25render_marks applies marks in input array order. If strong precedes code, bold asterisks render literally inside the code span.

  • [edge-case] scripts/comment-helpers.sh:125_redact_secrets calls fullsend scan output as a pipe filter without an explicit stdin flag. If the subcommand does not read stdin, the comment body will be silently discarded.

  • [secret-exposure] scripts/comment-helpers.sh:127_redact_secrets falls back to bare cat when fullsend is not on PATH. Credential segregation mitigates risk.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (7)

Review

Findings

High

  • [protected-path] agents/explore.md, harness/explore.yaml, policies/explore.yaml, scripts/* (5 files), skills/* (2 files) — This PR modifies 10 files under protected paths (agents/, harness/, policies/, scripts/, skills/). No linked issue provides authorization for these changes. Human approval is required for all protected-path modifications regardless of context.

Medium

  • [logic-error] scripts/post-explore.sh:118 — On re-runs where confidence crosses the threshold, the previously-applied pipeline label is never removed. The script only adds the new SIGNAL_LABEL but never calls remove_label for the opposite label. Both EXPLORE_READY_LABEL and EXPLORE_NEEDS_INFO_LABEL could be present simultaneously, sending contradictory signals to downstream stages. The remove_label helper exists in comment-helpers.sh but is unused here.
    Remediation: Before adding SIGNAL_LABEL, remove the opposite label. For GitHub, call remove_label. For Jira, add a {"remove": "..."} entry alongside {"add": "..."}.

  • [stale-doc] README.md:8 — The agents table in README.md lists existing agents but does not include the new Explore agent added by this PR. The table is the primary entry point for understanding available agents.
    Remediation: Add a row for the Explore agent to the agents table.

Low

12 low-severity findings
  • [logic-error] scripts/pre-explore.sh:370extract_repo_refs_from_text() references $ISSUE_JSON which is set inside a conditional block (jira vs github). When ISSUE_SOURCE is github, the jq recursive descent for ADF nodes silently produces no output (harmless), but the implicit reliance on variable scoping from conditional blocks is fragile.

  • [edge-case] scripts/pre-explore.sh:478extract_repo_refs_from_previous_explore() uses $AUTH, $JIRA_BASE, and jira_get which are set inside the jira conditional block. The function's own guard prevents execution in the GitHub path, so this is functionally correct but fragile.

  • [pattern-violation] agents/explore.md:390 — Agent prompt omits the fullsend-check-output validation step present in all other agent prompts. The harness validation_loop provides external validation, but the in-prompt check reduces wasted iterations.

  • [edge-case] scripts/comment-helpers.sh:163_redact_secrets calls fullsend scan output as a pipe filter without an explicit stdin flag. If the subcommand does not read stdin, the comment body will be silently discarded.

  • [edge-case] scripts/adf-to-markdown.py:56render_table calculates separator column count by counting | in the first rendered row. Cell content containing literal | inflates the count.

  • [edge-case] scripts/adf-to-markdown.py:27render_marks applies marks in input array order. If strong precedes code, bold asterisks render literally inside the code span.

  • [injection-vuln] scripts/pre-explore.sh:53::notice:: interpolates ISSUE_SOURCE and ISSUE_KEY before input validation at lines 56–68. Values come from runner env vars (not attacker-controlled issue content), limiting exploitability.
    Remediation: Move the ::notice:: echo to after the validation blocks.

  • [injection-vuln] scripts/post-explore.sh:61validate_label_name regex permits : characters, meaning :: sequences pass validation. Newlines, %, and = are blocked by the regex, preventing GHA workflow command injection. Risk limited to runner-operator misconfiguration.

  • [injection-vuln] scripts/post-explore.sh:181 — Jira label update uses string interpolation for JSON. validate_label_name regex prevents JSON-breaking characters, but jq construction would be safer.
    Remediation: Use jq to construct the JSON payload.

  • [secret-exposure] scripts/comment-helpers.sh:163_redact_secrets falls back to bare cat when fullsend is not on PATH. Credential segregation mitigates risk.

  • [pattern-inconsistency] schemas/explore-result.schema.json:2 — Schema title Exploration Result deviates from <Agent> Agent Result pattern used by all other schemas.

  • [naming-convention] agents/explore.md:47 — Process uses Phase N headings while most other agents use Step N. Note: triage.md also uses Phase N.


Protected paths detected — this PR modifies files under one or more
protected paths. The review agent cannot approve PRs that touch these paths.
A human reviewer must approve this PR.

Protected files in this PR:

  • agents/explore.md
  • harness/explore.yaml
  • policies/explore.yaml
  • scripts/adf-to-markdown.py
  • scripts/comment-helpers.sh
  • scripts/markdown-to-adf.py
  • scripts/post-explore.sh
  • scripts/pre-explore.sh
  • skills/jira-read/SKILL.md
  • skills/public-research/SKILL.md

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (8)

Review

Findings

High

  • [protected-path] agents/explore.md, harness/explore.yaml, policies/explore.yaml, scripts/* (5 files), skills/* (2 files) — This PR modifies 10 files under protected paths (agents/, harness/, policies/, scripts/, skills/). No linked issue provides authorization for these changes. Human approval is required for all protected-path modifications regardless of context.

Medium

  • [logic-error] scripts/post-explore.sh:118 — On re-runs where confidence crosses the threshold, the previously-applied pipeline label is never removed. The script only adds the new SIGNAL_LABEL but never calls remove_label for the opposite label. Both EXPLORE_READY_LABEL and EXPLORE_NEEDS_INFO_LABEL could be present simultaneously, sending contradictory signals to downstream stages. The remove_label helper exists in comment-helpers.sh but is unused here.
    Remediation: Before adding SIGNAL_LABEL, remove the opposite label. For GitHub, call remove_label. For Jira, add a {"remove": "..."} entry alongside {"add": "..."}.

  • [stale-doc] README.md:8 — The agents table in README.md lists existing agents but does not include the new Explore agent added by this PR. The table is the primary entry point for understanding available agents.
    Remediation: Add a row for the Explore agent to the agents table.

Low

13 low-severity findings
  • [pattern-violation] agents/explore.md:390 — Agent prompt omits the fullsend-check-output validation step present in all other agent prompts. The harness validation_loop provides external validation, but the in-prompt check reduces wasted iterations.

  • [injection-vuln] scripts/pre-explore.sh:53::notice:: interpolates ISSUE_SOURCE and ISSUE_KEY before input validation at lines 56–68. Values come from runner env vars (not attacker-controlled issue content), limiting exploitability.
    Remediation: Move the ::notice:: echo to after the validation blocks.

  • [edge-case] scripts/comment-helpers.sh:163_redact_secrets calls fullsend scan output as a pipe filter without an explicit stdin flag. If the subcommand does not read stdin, the comment body will be silently discarded.

  • [edge-case] scripts/pre-explore.sh:370 — ADF_INLINE extraction runs jq recursive descent on $ISSUE_JSON even when ISSUE_SOURCE is github, where ADF nodes never exist. Harmless but unnecessary.

  • [edge-case] scripts/adf-to-markdown.py:56render_table calculates separator column count by counting | in the first rendered row. Cell content containing literal | inflates the count.

  • [edge-case] scripts/adf-to-markdown.py:27render_marks applies marks in input array order. If strong precedes code, bold asterisks render literally inside the code span.

  • [injection-vuln] scripts/post-explore.sh:61validate_label_name regex permits : characters, meaning :: sequences pass validation. Validated label is interpolated into ::notice:: commands. Risk limited to runner-operator misconfiguration.

  • [injection-vuln] scripts/post-explore.sh:97OVERALL_CONFIDENCE interpolated into ::notice:: without re-validation. Schema validation upstream mitigates this.

  • [injection-vuln] scripts/post-explore.sh:166GITHUB_ISSUE_NUMBER used in gh api URL without re-validation. Pre-validated as numeric in pre-explore.sh.

  • [injection-vuln] scripts/post-explore.sh:181 — Jira label update uses string interpolation for JSON. validate_label_name regex prevents JSON-breaking characters, but jq construction would be safer.

  • [secret-exposure] scripts/comment-helpers.sh:163_redact_secrets falls back to bare cat when fullsend is not on PATH. Credential segregation mitigates risk.

  • [pattern-inconsistency] schemas/explore-result.schema.json:2 — Schema title Exploration Result deviates from <Agent> Agent Result pattern used by all other schemas.

  • [naming-convention] agents/explore.md:92 — Process uses Phase N headings while other agents use Step N.


Protected paths detected — this PR modifies files under one or more
protected paths. The review agent cannot approve PRs that touch these paths.
A human reviewer must approve this PR.

Protected files in this PR:

  • agents/explore.md
  • harness/explore.yaml
  • policies/explore.yaml
  • scripts/adf-to-markdown.py
  • scripts/comment-helpers.sh
  • scripts/markdown-to-adf.py
  • scripts/post-explore.sh
  • scripts/pre-explore.sh
  • skills/jira-read/SKILL.md
  • skills/public-research/SKILL.md

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (9)

Review

Findings

High

  • [protected-path] agents/explore.md, harness/explore.yaml, policies/explore.yaml, scripts/* (5 files), skills/* (2 files) — This PR modifies 10 files under protected paths (agents/, harness/, policies/, scripts/, skills/). No linked issue provides authorization for these changes. Human approval is required for all protected-path modifications regardless of context.

Medium

  • [logic-error] scripts/post-explore.sh:169 — On re-runs where confidence crosses the threshold, the previously-applied pipeline label is never removed. Both EXPLORE_READY_LABEL and EXPLORE_NEEDS_INFO_LABEL could be present simultaneously, sending contradictory signals to downstream stages. The remove_label helper exists in comment-helpers.sh but is not called.
    Remediation: Before adding SIGNAL_LABEL, remove the opposite label if configured.

  • [injection-vuln] scripts/pre-explore.sh:37echo "::notice::...${ISSUE_SOURCE}...${ISSUE_KEY}..." interpolates user-controlled values into a GHA workflow command before input validation at lines 40–53. An attacker controlling ISSUE_KEY could inject :: sequences before validation rejects the input.
    Remediation: Move the ::notice:: emission to after the input validation block.

  • [missing-authorization] agents/explore.md — This PR adds 2623 lines across 13 files (a new agent, harness, scripts, schema, and skills) without a linked GitHub issue. AGENTS.md §2 and §5 reference an issue as the authority for work scope. The enhancement label and draft status provide partial context, but non-trivial feature additions benefit from explicit scope authorization via a linked issue.
    Remediation: Create a GitHub issue describing the rationale, use cases, and acceptance criteria, and link it to this PR.

  • [pattern-violation] agents/explore.md:390 — The agent prompt instructs "Write ONLY the JSON file" but omits the output validation step present in the triage agent (fullsend-check-output). While harness/explore.yaml has a validation_loop that externally validates output, adding an in-prompt validation hint would reduce wasted iterations when the agent produces non-conforming JSON.
    Remediation: Add validation instructions: "After writing the JSON file, run fullsend-check-output $FULLSEND_OUTPUT_DIR/agent-result.json and fix any errors."

  • [stale-doc] README.md:8 — The agents table in README.md lists 6 agents but does not include the new Explore agent added by this PR. The table is the primary entry point for understanding available agents.
    Remediation: Add a row for the Explore agent to the agents table.

Low

12 low-severity findings
  • [edge-case] scripts/comment-helpers.sh:107_redact_secrets calls fullsend scan output as a pipe filter. If the subcommand does not read stdin, the comment body will be silently discarded. The else branch correctly uses cat.

  • [edge-case] scripts/pre-explore.sh:370ADF_INLINE extraction runs jq recursive descent on $ISSUE_JSON even when ISSUE_SOURCE is github, where ADF nodes never exist. Unnecessary work.

  • [edge-case] scripts/adf-to-markdown.py:43render_table calculates separator column count by counting | in the first rendered row. Cell content containing literal | inflates the count.

  • [edge-case] scripts/adf-to-markdown.py:85render_marks applies marks in input array order. If strong precedes code, the asterisks render literally inside the code span.

  • [secret-exposure] scripts/comment-helpers.sh:108_redact_secrets falls back to bare cat when fullsend is not on PATH. Comments are posted without secret scanning. Credential segregation mitigates risk (tokens stay in runner env).

  • [injection-vuln] scripts/post-explore.sh:97OVERALL_CONFIDENCE interpolated into ::notice:: without numeric validation. Schema validation upstream largely mitigates this.

  • [injection-vuln] scripts/post-explore.sh:157GITHUB_ISSUE_NUMBER not re-validated in post-explore.sh. Pre-validated in pre-explore.sh but defense-in-depth suggests re-validation.

  • [injection-vuln] scripts/post-explore.sh:169 — Jira label update uses string interpolation into JSON. validate_label_name regex prevents JSON-breaking characters, but jq construction is safer.

  • [scope-creep] agents/explore.md — PR does not fully document how explore integrates with existing agents. docs/explore.md provides some context but could be more explicit about workflow position.

  • [design-direction] scripts/pre-explore.sh — Automatic shallow-cloning of referenced repos is a novel pattern. Well-secured but could benefit from shared library extraction if the pattern spreads.

  • [pattern-inconsistency] schemas/explore-result.schema.json:2 — Schema title Exploration Result deviates from <Agent> Agent Result pattern used by triage.

  • [naming-convention] agents/explore.md:45 — Process uses Phase N headings while triage uses Step N. Cosmetic inconsistency.


Protected paths detected — this PR modifies files under one or more
protected paths. The review agent cannot approve PRs that touch these paths.
A human reviewer must approve this PR.

Protected files in this PR:

  • agents/explore.md
  • harness/explore.yaml
  • policies/explore.yaml
  • scripts/adf-to-markdown.py
  • scripts/comment-helpers.sh
  • scripts/markdown-to-adf.py
  • scripts/post-explore.sh
  • scripts/pre-explore.sh
  • skills/jira-read/SKILL.md
  • skills/public-research/SKILL.md
Previous run (10)

Review

Verdict: approve

All 14 findings from the prior review have been addressed in commit db4947e. The fixes are clean and complete:

# Prior Finding Status
1 Gap extraction used wrong field names (.category // .type.dimension) ✅ Fixed
2 Schema version inconsistency (draft-072020-12, added $id) ✅ Fixed
3 impact_radius absent from schema ✅ Fixed
4 Summary maxLength mismatch (2000 → 1000, matches prompt) ✅ Fixed
5 Schema enum values not reflected in prompt (text, web, outcome added) ✅ Fixed
6 Missing null guard on related_work (// [] added) ✅ Fixed
7 Python injection via JIRA_EMAIL (now uses os.environ.get()) ✅ Fixed
8 JIRA_HOST not validated (added validate_jira_host() with regex) ✅ Fixed
9 Silent fallback to no-op secret scanning (added ::warning::) ✅ Fixed
10 Jira attachment URL with -L redirect (URL host validation + removed -L) ✅ Fixed
11 Skill documentation field mismatches (removed assignee, updated project) ✅ Fixed
12 Missing user documentation (added docs/explore.md) ✅ Fixed
13 PR title not Conventional Commits (now feat:) ✅ Fixed
14 *.google.com wildcard too broad (narrowed to www.google.com) ✅ Fixed

Re-review assessment

Six review dimensions were evaluated against the current HEAD:

  • Correctness — No logic errors, edge cases, or contract violations found. The _redact_secrets function correctly uses fullsend scan output as a stdin-to-stdout filter (verified against CLI help). Schema, prompt, and script field names are now consistent.
  • Security — Credential isolation is solid (Jira/GitHub tokens stay on the runner). JIRA_HOST is validated against *.atlassian.net pattern. Python string interpolation injection is resolved. Attachment URL host validation prevents credential leakage. Network policy is appropriately scoped.
  • Intent & coherence — The PR is a well-structured feature addition that follows the existing triage agent's architecture. No linked issue exists, but the PR body clearly documents scope, intent, and test plan, and the enhancement label provides implicit authorization.
  • Style & conventions — Follows established patterns from the triage agent (harness structure, script idioms, schema format).
  • Documentationdocs/explore.md, skills/jira-read/SKILL.md, and skills/public-research/SKILL.md accurately reflect the implementation.
  • Cross-repo contracts — Purely additive (all new files). No existing interfaces or schemas are modified.

The code is clean, well-organized, and ready to merge.


Protected paths detected — this PR modifies files under one or more
protected paths. The review agent cannot approve PRs that touch these paths.
A human reviewer must approve this PR.

Protected files in this PR:

  • agents/explore.md
  • harness/explore.yaml
  • policies/explore.yaml
  • scripts/adf-to-markdown.py
  • scripts/comment-helpers.sh
  • scripts/markdown-to-adf.py
  • scripts/post-explore.sh
  • scripts/pre-explore.sh
  • skills/jira-read/SKILL.md
  • skills/public-research/SKILL.md
Previous run

Review

Verdict: 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.

Findings

High

  1. Gap extraction uses wrong field names (scripts/post-explore.sh, ~line 157): The jq expression .category // .type // "definition gap" extracts the gap label for the posted comment, but the schema defines .dimension (required) — not .category or .type. Every gap will show "definition gap" regardless of the agent's actual dimension value, losing information in the posted summary.

Medium

  1. Schema version inconsistency (schemas/explore-result.schema.json, line 2): Uses http://json-schema.org/draft-07/schema# while the existing triage schema uses https://json-schema.org/draft/2020-12/schema. Also uses http:// instead of https://. Missing $id field present in the triage schema.

  2. impact_radius absent from schema (agents/explore.md lines 319-325 vs schemas/explore-result.schema.json): The agent prompt instructs the agent to produce an impact_radius object, but the schema has no definition for it. Since additionalProperties is not false, it passes validation but provides no contract for downstream consumers.

  3. Summary maxLength mismatch (schemas/explore-result.schema.json line 112 vs agents/explore.md line 387): Schema allows 2000 characters; prompt says "under 1000 characters." Pick one authoritative value.

  4. Schema enum values not reflected in prompt (schemas/explore-result.schema.json lines 12-14): input.source includes "text" and "web" not mentioned in the prompt example. input.level includes "outcome" not in the prompt example. Either the schema has dead enum values or the prompt is incomplete.

  5. Missing null guard on related_work (scripts/post-explore.sh, ~line 54): RELATED_COUNT=$(jq '.related_work | length' ...) lacks the // [] null guard used on the adjacent GAP_COUNT line. If the agent produces a non-conforming output where related_work is null, jq outputs null, and the subsequent arithmetic comparison fails under set -euo pipefail.

  6. Python injection via JIRA_EMAIL (scripts/pre-explore.sh, ~line 128): JIRA_EMAIL is interpolated directly into an inline Python string literal: AGENT_EMAIL = '${JIRA_EMAIL}'. A value containing a single quote enables arbitrary Python execution on the runner host. Pass it as an environment variable or command-line argument instead.

  7. JIRA_HOST not validated (scripts/pre-explore.sh, post-explore.sh, comment-helpers.sh): JIRA_HOST is used to construct API URLs with an attached Authorization: Basic header but is never validated against a pattern. An attacker who controls this variable can redirect authenticated requests to an arbitrary host. Validate against ^[a-zA-Z0-9.-]+\.atlassian\.net$ or an allowlist.

  8. Silent fallback to no-op secret scanning (scripts/comment-helpers.sh, lines 57-62): _redact_secrets() falls back to cat when fullsend is not on $PATH, meaning no secret scanning occurs silently. At minimum, log a ::warning:: annotation so operators notice.

  9. Jira attachment URL used with auth header without validation (scripts/pre-explore.sh, ~line 411): EXPLORE_URL from a Jira attachment's content field is passed to curl -L with the Authorization: Basic header. The -L flag follows redirects, potentially leaking credentials. Validate that the URL matches https://${JIRA_HOST}/... before requesting.

  10. Skill documentation mismatches (skills/jira-read/SKILL.md): The example shows assignee (line 37) and project.hierarchy (line 73) fields that don't exist in the actual issue-context.json output from pre-explore.sh. The actual output has project.available_issue_types and project.team_usage instead.

  11. Missing user documentation (docs/explore.md): The existing triage agent has docs/triage.md explaining usage. No corresponding docs file is added for the explore agent.

  12. PR title does not follow Conventional Commits (AGENTS.md §4): Title "Add generic explore agent" is missing the required type prefix. Should be e.g., feat: add generic explore agent.

  13. *.google.com wildcard overly broad (policies/explore.yaml, line 49): Allows connections to any Google subdomain including storage.googleapis.com. Restrict to specific subdomains needed for search.


Labels: PR adds a new explore agent with harness, scripts, schema, and skills.

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the enhancement New feature or request label Jul 1, 2026
@ascerra ascerra changed the title Add generic explore agent feat: add generic explore agent Jul 1, 2026
@ascerra

ascerra commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Additional review items from the summary (no inline thread) addressed in db4947e:

  • impact_radius absent from schema — added impact_radius object to schemas/explore-result.schema.json matching the agent prompt
  • Missing user documentation — added docs/explore.md and doc: field in harness/explore.yaml
  • PR title Conventional Commits — retitled to feat: add generic explore agent

All 14 inline review threads have individual replies explaining the fix.

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 1, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:48 PM UTC · Completed 9:00 PM UTC
Commit: db4947e · View workflow run →

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review July 1, 2026 21:00

Superseded by updated review

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jul 1, 2026
ascerra added a commit that referenced this pull request Jul 9, 2026
- 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>
ascerra added a commit that referenced this pull request Jul 9, 2026
- 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 ralphbean left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this needs some changes before we can merge. See inline comments.

Comment thread scripts/pre-explore.sh

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/comment-helpers.sh"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 6d6dcb2 for the install-overlay pattern we need for konflux-ci/refinement:

Added _resolve_companion so companions are loaded from:

  1. SCRIPT_DIR (normal checkout / vendored install scripts)
  2. $GITHUB_WORKSPACE/.fullsend/scripts or $FULLSEND_DIR/scripts (install overlay when the harness base: blob has no siblings)

Refinement (and similar installs) override pre_script/post_script locally and ship companions under .fullsend/scripts/, so base: composition works for the consumer path we’re validating now.

Full inlining of comment-helpers.sh / ADF converters (prioritize cac8127 style) remains the most durable fix for pure base: consumers that neither override scripts nor vendor companions — happy to do that as a follow-up if you want it before merge. This change unblocks verified testing of agent prompts via base: from refinement.

Comment thread scripts/post-explore.sh
local label="$1"
if [[ ! "${label}" =~ ^[a-zA-Z0-9._/:\ +\-]+$ ]]; then
echo "::warning::Refused pipeline label '${label}' -- contains invalid characters"
return 1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same fix as pre-explore: _resolve_companion in 6d6dcb2 for comment-helpers.sh / ADF converters under install .fullsend/scripts when the post-script is fetched as an isolated base: blob. See reply on pre-explore.sh for the full rationale and follow-up (full inline) option.

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "explore-result.schema.json",
"title": "Exploration Result",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[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 the data_sources field" 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: false anywhere — the existing triage-result.schema.json uses 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.

Comment thread scripts/post-explore.sh
curl -sSf -X PUT \
-H "Authorization: Basic $AUTH" \
-H "Content-Type: application/json" \
-d "{\"update\":{\"labels\":[{\"add\":\"${SIGNAL_LABEL}\"}]}}" \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[important] Two things here:

  1. Does fullsend scan output actually 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.
  2. 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 ""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[minor] '""' appears twice in this condition — looks like a copy-paste duplicate. Was the second one meant to be a different value?

Comment thread scripts/pre-explore.sh Outdated
validate_repo() {
local ref="$1"
local http_code
http_code=$(GIT_TERMINAL_PROMPT=0 curl -sf -o /dev/null -w "%{http_code}" \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[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.

Comment thread scripts/pre-explore.sh
source "${SCRIPT_DIR}/comment-helpers.sh"

echo "::notice::Pre-explore: fetching issue data (source=${ISSUE_SOURCE}, key=${ISSUE_KEY})"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[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.

Comment thread scripts/pre-explore.sh
}' > "$WORKSPACE/issue-context.json"

elif [[ "${ISSUE_SOURCE}" == "github" ]]; then
if [[ -z "${REPO_FULL_NAME:-}" ]]; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[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.

Comment thread docs/explore.md
- skills/jira-routing # team-specific
env:
runner:
EXPLORE_READY_LABEL: ready-to-refine

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[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.

@ascerra

ascerra commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Update for konflux-ci/refinement base: consumption (6d6dcb2)

Refinement PR https://github.com/konflux-ci/refinement/pull/18 now pins this harness via ADR 0045 base: so we can test generic agents before this PR merges to main.

This push adds (generic only):

Still install-only (stays in refinement, not this PR):

  • Konflux skill URL pins (konflux-architecture, red-hat-konflux-teams)
  • program-decomposition skill
  • jira-routing / stage create allowlists
  • pack-org-knowledge.sh + curated Path B / Drive Path A
  • local pre/post that pack ORG_KNOWLEDGE

Reviewer note: Prior review threads that were already fixed in earlier commits remain fixed. New work is the ORG_KNOWLEDGE contract + companion resolution for base: testing from refinement. When this merges, refinement will flip base: from this branch SHA to main.

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 24, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 7:48 PM UTC · Ended 8:03 PM UTC
Commit: 6d6dcb2 · View workflow run →

@ascerra
ascerra force-pushed the feat/add-explore-agent branch from 6d6dcb2 to 00c3629 Compare July 24, 2026 20:03
@ascerra

ascerra commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

CI fix pass (00c3629)

Rebased onto current main and squashed to a single conventional commit so checks pass:

  • commit-lint: tip subjects no longer end with :; explore’s non-conventional root commit removed via squash
  • detect / functional-tests: branch now includes .github/scripts/select-eval-agents.sh from main (required because pull_request_target checks out PR head)
  • test / shellcheck (explore): fixed SC2015 / SC2129 / SC2034 / SC2295 in pre-explore.sh + comment-helpers.sh

Harness file #sha256= unchanged — only the commit OID moves for refinement base: pins.

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 24, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 8:05 PM UTC · Ended 8:12 PM UTC
Commit: 00c3629 · View workflow run →

Introduce explore harness, agent prompt, scripts, schema, and skills.
Consume optional ORG_KNOWLEDGE for install-injected org context without
failing when the pack is absent. Resolve companion script helpers for
base-composition installs. Fix shellcheck findings in pre-explore and
comment helpers.

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@ascerra
ascerra force-pushed the feat/add-explore-agent branch from 00c3629 to 626639a Compare July 24, 2026 20:11
@ascerra

ascerra commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto latest main (d7afc9c) so the branch is no longer out of date. Tip: 626639a (harness #sha256= unchanged).

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 24, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:13 PM UTC · Completed 8:32 PM UTC
Commit: 626639a · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 1:12 AM UTC · Ended 1:24 AM UTC
Commit: d5778aa · View workflow run →

Hints file is written to /tmp/workspace by pre-explore; referencing it
via ${JIRA_API_HINTS} failed ValidateRunnerEnv before the pre-script ran.

Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:25 AM UTC · Completed 1:38 AM UTC
Commit: 981760c · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

Phase 0 duplicate gate blocks exploration before deep work. Sticky warns
and skips ready-to-refine; a second /fs-explore (marker present) overrides.

Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 29, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:49 AM UTC · Completed 12:10 PM UTC
Commit: 297af25 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

Newer fullsend rejects absolute host_files.src on URL-composed bases.
Runtime workspace mounts belong on the install overlay.

Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 4, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:19 PM UTC · Completed 8:37 PM UTC
Commit: fdfe25e · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

ascerra and others added 2 commits August 4, 2026 17:44
Phase 0 cats /tmp/workspace/duplicate-gate.json; also try the
sandbox mount path and report clearly when neither exists so
override=true is not silently missed.

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Score confidence 0.0–5.0 (one decimal). Rename shaped/output issue
identifiers from key→issue_id and project.key→project.id so fullsend
SecretRedactor does not redact Jira tickets (field names containing
"key" match the secret pattern).

Assisted-by: Cursor
Signed-off-by: Adam Scerra <ascerra@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 8, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:03 PM UTC · Completed 5:22 PM UTC

Commit: a4fd578 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

…s-info

Score alone no longer forces needs-info when the agent assessed disposition
as complete. The post-script now checks disposition first: needs_info always
gets the needs-info label, complete always gets ready-to-refine, and the
score threshold is a backward-compat fallback for old results without an
explicit disposition.

Adds needs_info disposition enum value, open_questions array (with blocking
flag to distinguish user-facing vs implementation-facing gaps) to the schema
and agent prompt.

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 8, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:21 PM UTC · Completed 10:40 PM UTC

Commit: 1f20a90 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

Preserve CI-prefetched clones in CLONED/manifest (previously skipped and
dropped), authenticate validate_repo + git clone with GH_TOKEN, and teach
the agent to use authenticated gh api instead of anonymous curl (60/hr).

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 8, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 11:04 PM UTC · Ended 11:08 PM UTC

Commit: 1e9200b · View workflow run →

Use http.extraHeader for authenticated clones instead of x-access-token in
the URL (process-list leak). Point related-work search at gh search, and
correct docs that wrongly claimed tokens never enter the sandbox.

Signed-off-by: Adam Scerra <ascerra@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 8, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:08 PM UTC · Completed 11:24 PM UTC

Commit: 3cb9c95 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

Comment thread scripts/pre-explore.sh
'{"issue_id": $k, "summary": $s, "description": $d}')
fi

CHILDREN_JSON=$(jira_get "${JIRA_BASE}/search?jql=parent=${ISSUE_KEY}&fields=summary,status,issuetype&maxResults=50" 2>/dev/null \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[high] logic-error

The pre-script fetches Jira children (line 172) and team usage (line 253) via ${JIRA_BASE}/search?jql= which resolves to /rest/api/3/search?jql=. The script's own jira-api-hints.json documents that /rest/api/3/search returns HTTP 410. Both calls fail silently due to error swallowing, delivering empty children and team_usage to the agent.

Suggested fix: Replace both occurrences of ${JIRA_BASE}/search?jql= with GET requests to ${JIRA_BASE}/search/jql using --get --data-urlencode query parameters, matching the pattern in the jira-api-hints probe section.

Comment thread skills/jira-read/SKILL.md

## What the agent CANNOT do

- The agent cannot call the Jira API directly (sandbox network policy blocks it)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] logic-error

SKILL.md states 'The agent cannot call the Jira API directly (sandbox network policy blocks it)' but the network policy allows *.atlassian.net:443, the harness passes Jira credentials to the sandbox, and the agent prompt instructs curl+Basic auth usage. docs/explore.md was updated but SKILL.md was not.

Suggested fix: Update the 'What the agent CANNOT do' section to state the agent CAN make read-only Jira API calls using injected credentials.

Comment thread skills/jira-read/SKILL.md
{
"source": "jira",
"host": "your-org.atlassian.net",
"key": "PROJ-1620",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] logic-error

SKILL.md documents field name 'key' for issue identifiers and 'project.key', but the pre-script writes 'issue_id' and 'project.id'. The agent would use wrong field names.

Suggested fix: Update the SKILL.md JSON example to use 'issue_id' instead of 'key' throughout, and 'id' instead of 'key' for the project object.

Comment thread scripts/post-explore.sh
if [[ -n "${GITHUB_ISSUE_NUMBER:-}" && "${GITHUB_ISSUE_NUMBER}" != "N/A" ]]; then
EXISTING_GH_LABELS=$(gh api "repos/${REPO_FULL_NAME}/labels" --paginate --jq '.[].name' 2>/dev/null || true)
if github_label_exists "$SIGNAL_LABEL"; then
gh api "repos/${REPO_FULL_NAME}/issues/${GITHUB_ISSUE_NUMBER}/labels" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] logic-error

On re-runs where confidence crosses the threshold, the previously-applied pipeline label is never removed. Both labels could be present simultaneously.

Suggested fix: Before adding SIGNAL_LABEL, remove the opposite label.

Comment thread scripts/pre-explore.sh
# shellcheck disable=SC1090
source "$(_resolve_companion comment-helpers.sh)"

echo "::notice::Pre-explore: fetching issue data (source=${ISSUE_SOURCE}, key=${ISSUE_KEY})"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] GHA-workflow-command-injection

ISSUE_SOURCE and ISSUE_KEY interpolated into ::notice:: BEFORE input validation at lines 104-117. Validation-after-emission is a defense-in-depth gap.

Suggested fix: Move the ::notice:: to after the input validation block.

Comment thread scripts/pre-explore.sh
if [[ -f "$meta" ]] && command -v jq >/dev/null 2>&1 && command -v curl >/dev/null 2>&1; then
local origin base_url tmp
origin=$(jq -r '.url // empty' "$meta" 2>/dev/null || true)
if [[ -n "$origin" && "$origin" == http*://* ]]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] remote-code-fetch

The _resolve_companion function fetches and executes remote shell scripts based on a URL read from metadata.json. The origin URL is validated only with a loose prefix check.

if command -v fullsend >/dev/null 2>&1; then
fullsend scan output
else
echo "::warning::fullsend not on PATH — posting comment without secret scanning" >&2

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] secret-exposure

When fullsend CLI is not on PATH, _redact_secrets falls back to cat, posting comment content without secret scanning.

md_rows.append("| " + " | ".join(cell_texts) + " |")

if len(md_rows) >= 1:
col_count = md_rows[0].count("|") - 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge-case

render_table calculates separator column count by counting | in the first rendered row. Literal | in cell content inflates the count.



def render_marks(text: str, marks: list) -> str:
"""Wrap text with Markdown formatting based on ADF marks."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge-case

render_marks applies marks in input array order. If code precedes strong, bold asterisks render literally inside the code span.

@ascerra

ascerra commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Closing without merge — keep the branch

Closing #11 (explore). Explore is not cancelled.

This draft PR was kept open so installs could pin WIP SHAs while we iterated. Every push re-triggered the fullsend review agent. We looked at extending /fs-stop (platform work) so review would stay off on drafts like this, but decided not to rush that fix. For this case, closing the PR is enough: the branch stays, we can keep testing and updating against it, and review no longer runs on every push. Reopen when explore is ready for review.

Team decision 2026-08-10 (notes).

Work continues on feat/add-explore-agent. Pin installs to branch commit SHAs.

Order Agent PR Branch
1 explore #11 (this) feat/add-explore-agent
2 refine #86 feat/add-refine-agent
3 critique #87 feat/add-critique-agent

#86 / #87 close for the same reason.

@ascerra ascerra closed this Aug 11, 2026
@fullsend-ai-retro

fullsend-ai-retro Bot commented Aug 11, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 12:41 AM UTC · Completed 12:56 AM UTC

Commit: 3cb9c95 · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #11 — feat: add generic explore agent

Timeline

Date Event
Jul 1 PR opened by ascerra (human, using Cursor). 13 new files, 4335 lines. No linked issue.
Jul 1 qodo-code-review[bot] posts 3 findings; fullsend-ai-review[bot] posts 11 findings. Author fixes all immediately.
Jul 9 ralphbean (human) reviews: 6 findings including 2 critical base-composition issues the bots missed entirely.
Jul 24 Author addresses human review. Review bot cycle 2 begins.
Jul 24–Aug 8 10 more review bot cycles run, producing ~114 total inline comments with substantial redundancy (~49% duplicates).
All runs CI (test job) failed on all 10 pushes: incomplete docs/explore.md (missing 6 required sections), shellcheck SC2086, missing trailing newline. Never fixed.
Aug 11 PR closed without merging after ~6 weeks.

Review quality analysis

What the review bot did well: Found 7 actionable bugs in its first cycle — .dimension jq field mismatch, null guards, JIRA_EMAIL Python injection, JIRA_HOST SSRF risk, schema inconsistencies — all fixed immediately by the author.

What the human caught that the bot missed (across all 11 cycles):

  1. [critical] source "${SCRIPT_DIR}/comment-helpers.sh" in pre-explore.sh and post-explore.sh breaks under base composition — scripts are fetched as individual content-addressed blobs without sibling directories. The reviewer noted this same problem affected the prioritize agent (PR fix(prioritize): inline CSMA library into post-script #35), indicating a recurring pattern.
  2. [important] Schema missing additionalProperties: false — more permissive than the agent prompt implies.
  3. [moderate] Description framing too narrow ("downstream workflow agents" vs broader audience).

The bot later picked up 2 findings that overlapped with the human's (SIGNAL_LABEL JSON injection and fullsend scan stdin behavior) but rated them lower severity ([low] vs the human's [important]). The bot also had a false positive claiming storage.googleapis.com is a subdomain of *.google.com — it is a subdomain of googleapis.com, a different domain.

Existing issues providing coverage

Most improvement areas from this PR are already tracked:

  • Comment dedup: agents#721, fullsend#2959, fullsend#5760 — the 114-comment / ~49% duplication rate on this PR adds evidence for these issues.
  • CI-gating reviews: fullsend#5755 — 11 review cycles ran despite CI being red on every push.
  • Human review incorporation: agents#447 — the bot ran 10 cycles post-human-review without adopting the critical base-composition framing.
  • Assertion verification: agents#420 — the *.google.com false positive is evidence for this issue.
  • Severity calibration: fullsend#2993 — bot rated injection risks lower than the human reviewer on the same findings.

Proposals filed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request security

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants